Source files in libcamera start by a comment block header, which includes the file name and a one-line description of the file contents. While the latter is useful to get a quick overview of the file contents at a glance, the former is mostly a source of inconvenience. The name in the comments can easily get out of sync with the file name when files are renamed, and copy & paste during development have often lead to incorrect names being used to start with. Readers of the source code are expected to know which file they're looking it. Drop the file name from the header comment block. The change was generated with the following script: ---------------------------------------- dirs="include/libcamera src test utils" declare -rA patterns=( ['c']=' \* ' ['cpp']=' \* ' ['h']=' \* ' ['py']='# ' ['sh']='# ' ) for ext in ${!patterns[@]} ; do files=$(for dir in $dirs ; do find $dir -name "*.${ext}" ; done) pattern=${patterns[${ext}]} for file in $files ; do name=$(basename ${file}) sed -i "s/^\(${pattern}\)${name} - /\1/" "$file" done done ---------------------------------------- This misses several files that are out of sync with the comment block header. Those will be addressed separately and manually. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
181 lines
6.1 KiB
Python
181 lines
6.1 KiB
Python
# SPDX-License-Identifier: BSD-2-Clause
|
|
#
|
|
# Copyright (C) 2019, Raspberry Pi Ltd
|
|
#
|
|
# camera tuning tool for GEQ (green equalisation)
|
|
|
|
from ctt_tools import *
|
|
import matplotlib.pyplot as plt
|
|
import scipy.optimize as optimize
|
|
|
|
|
|
"""
|
|
Uses green differences in macbeth patches to fit green equalisation threshold
|
|
model. Ideally, all macbeth chart centres would fall below the threshold as
|
|
these should be corrected by geq.
|
|
"""
|
|
def geq_fit(Cam, plot):
|
|
imgs = Cam.imgs
|
|
"""
|
|
green equalisation to mitigate mazing.
|
|
Fits geq model by looking at difference
|
|
between greens in macbeth patches
|
|
"""
|
|
geqs = np.array([geq(Cam, Img)*Img.againQ8_norm for Img in imgs])
|
|
Cam.log += '\nProcessed all images'
|
|
geqs = geqs.reshape((-1, 2))
|
|
"""
|
|
data is sorted by green difference and top half is selected since higher
|
|
green difference data define the decision boundary.
|
|
"""
|
|
geqs = np.array(sorted(geqs, key=lambda r: np.abs((r[1]-r[0])/r[0])))
|
|
|
|
length = len(geqs)
|
|
g0 = geqs[length//2:, 0]
|
|
g1 = geqs[length//2:, 1]
|
|
gdiff = np.abs(g0-g1)
|
|
"""
|
|
find linear fit by minimising asymmetric least square errors
|
|
in order to cover most of the macbeth images.
|
|
the philosophy here is that every macbeth patch should fall within the
|
|
threshold, hence the upper bound approach
|
|
"""
|
|
def f(params):
|
|
m, c = params
|
|
a = gdiff - (m*g0+c)
|
|
"""
|
|
asymmetric square error returns:
|
|
1.95 * a**2 if a is positive
|
|
0.05 * a**2 if a is negative
|
|
"""
|
|
return(np.sum(a**2+0.95*np.abs(a)*a))
|
|
|
|
initial_guess = [0.01, 500]
|
|
"""
|
|
Nelder-Mead is usually not the most desirable optimisation method
|
|
but has been chosen here due to its robustness to undifferentiability
|
|
(is that a word?)
|
|
"""
|
|
result = optimize.minimize(f, initial_guess, method='Nelder-Mead')
|
|
"""
|
|
need to check if the fit worked correectly
|
|
"""
|
|
if result.success:
|
|
slope, offset = result.x
|
|
Cam.log += '\nFit result: slope = {:.5f} '.format(slope)
|
|
Cam.log += 'offset = {}'.format(int(offset))
|
|
"""
|
|
optional plotting code
|
|
"""
|
|
if plot:
|
|
x = np.linspace(max(g0)*1.1, 100)
|
|
y = slope*x + offset
|
|
plt.title('GEQ Asymmetric \'Upper Bound\' Fit')
|
|
plt.plot(x, y, color='red', ls='--', label='fit')
|
|
plt.scatter(g0, gdiff, color='b', label='data')
|
|
plt.ylabel('Difference in green channels')
|
|
plt.xlabel('Green value')
|
|
|
|
"""
|
|
This upper bound asymmetric gives correct order of magnitude values.
|
|
The pipeline approximates a 1st derivative of a gaussian with some
|
|
linear piecewise functions, introducing arbitrary cutoffs. For
|
|
pessimistic geq, the model parameters have been increased by a
|
|
scaling factor/constant.
|
|
|
|
Feel free to tune these or edit the json files directly if you
|
|
belive there are still mazing effects left (threshold too low) or if you
|
|
think it is being overcorrected (threshold too high).
|
|
We have gone for a one size fits most approach that will produce
|
|
acceptable results in most applications.
|
|
"""
|
|
slope *= 1.5
|
|
offset += 201
|
|
Cam.log += '\nFit after correction factors: slope = {:.5f}'.format(slope)
|
|
Cam.log += ' offset = {}'.format(int(offset))
|
|
"""
|
|
clamp offset at 0 due to pipeline considerations
|
|
"""
|
|
if offset < 0:
|
|
Cam.log += '\nOffset raised to 0'
|
|
offset = 0
|
|
"""
|
|
optional plotting code
|
|
"""
|
|
if plot:
|
|
y2 = slope*x + offset
|
|
plt.plot(x, y2, color='green', ls='--', label='scaled fit')
|
|
plt.grid()
|
|
plt.legend()
|
|
plt.show()
|
|
|
|
"""
|
|
the case where for some reason the fit didn't work correctly
|
|
|
|
Transpose data and then least squares linear fit. Transposing data
|
|
makes it robust to many patches where green difference is the same
|
|
since they only contribute to one error minimisation, instead of dragging
|
|
the entire linear fit down.
|
|
"""
|
|
|
|
else:
|
|
print('\nError! Couldn\'t fit asymmetric lest squares')
|
|
print(result.message)
|
|
Cam.log += '\nWARNING: Asymmetric least squares fit failed! '
|
|
Cam.log += 'Standard fit used could possibly lead to worse results'
|
|
fit = np.polyfit(gdiff, g0, 1)
|
|
offset, slope = -fit[1]/fit[0], 1/fit[0]
|
|
Cam.log += '\nFit result: slope = {:.5f} '.format(slope)
|
|
Cam.log += 'offset = {}'.format(int(offset))
|
|
"""
|
|
optional plotting code
|
|
"""
|
|
if plot:
|
|
x = np.linspace(max(g0)*1.1, 100)
|
|
y = slope*x + offset
|
|
plt.title('GEQ Linear Fit')
|
|
plt.plot(x, y, color='red', ls='--', label='fit')
|
|
plt.scatter(g0, gdiff, color='b', label='data')
|
|
plt.ylabel('Difference in green channels')
|
|
plt.xlabel('Green value')
|
|
"""
|
|
Scaling factors (see previous justification)
|
|
The model here will not be an upper bound so scaling factors have
|
|
been increased.
|
|
This method of deriving geq model parameters is extremely arbitrary
|
|
and undesirable.
|
|
"""
|
|
slope *= 2.5
|
|
offset += 301
|
|
Cam.log += '\nFit after correction factors: slope = {:.5f}'.format(slope)
|
|
Cam.log += ' offset = {}'.format(int(offset))
|
|
|
|
if offset < 0:
|
|
Cam.log += '\nOffset raised to 0'
|
|
offset = 0
|
|
|
|
"""
|
|
optional plotting code
|
|
"""
|
|
if plot:
|
|
y2 = slope*x + offset
|
|
plt.plot(x, y2, color='green', ls='--', label='scaled fit')
|
|
plt.legend()
|
|
plt.grid()
|
|
plt.show()
|
|
|
|
return round(slope, 5), int(offset)
|
|
|
|
|
|
""""
|
|
Return green channels of macbeth patches
|
|
returns g0, g1 where
|
|
> g0 is green next to red
|
|
> g1 is green next to blue
|
|
"""
|
|
def geq(Cam, Img):
|
|
Cam.log += '\nProcessing image {}'.format(Img.name)
|
|
patches = [Img.patches[i] for i in Img.order][1:3]
|
|
g_patches = np.array([(np.mean(patches[0][i]), np.mean(patches[1][i])) for i in range(24)])
|
|
Cam.log += '\n'
|
|
return(g_patches)
|