libcamera: utils: Raspberry Pi Camera Tuning Tool
Initial implementation of the Raspberry Pi (BCM2835) Camera Tuning Tool. All code is licensed under the BSD-2-Clause terms. Copyright (c) 2019-2020 Raspberry Pi Trading Ltd. Signed-off-by: Naushir Patuck <naush@raspberrypi.com> Acked-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
This commit is contained in:
parent
0db2c8dc75
commit
c01cfe14f5
14 changed files with 3552 additions and 0 deletions
748
utils/raspberrypi/ctt/ctt_macbeth_locator.py
Normal file
748
utils/raspberrypi/ctt/ctt_macbeth_locator.py
Normal file
|
@ -0,0 +1,748 @@
|
|||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
#
|
||||
# Copyright (C) 2019, Raspberry Pi (Trading) Limited
|
||||
#
|
||||
# ctt_macbeth_locator.py - camera tuning tool Macbeth chart locator
|
||||
|
||||
from ctt_ransac import *
|
||||
from ctt_tools import *
|
||||
import warnings
|
||||
|
||||
"""
|
||||
NOTE: some custom functions have been used here to make the code more readable.
|
||||
These are defined in tools.py if they are needed for reference.
|
||||
"""
|
||||
|
||||
"""
|
||||
Some inconsistencies between packages cause runtime warnings when running
|
||||
the clustering algorithm. This catches these warnings so they don't flood the
|
||||
output to the console
|
||||
"""
|
||||
def fxn():
|
||||
warnings.warn("runtime",RuntimeWarning)
|
||||
|
||||
"""
|
||||
Define the success message
|
||||
"""
|
||||
success_msg = 'Macbeth chart located successfully'
|
||||
|
||||
def find_macbeth(Cam,img,mac_config=(0,0)):
|
||||
small_chart,show = mac_config
|
||||
print('Locating macbeth chart')
|
||||
Cam.log += '\nLocating macbeth chart'
|
||||
"""
|
||||
catch the warnings
|
||||
"""
|
||||
warnings.simplefilter("ignore")
|
||||
fxn()
|
||||
|
||||
"""
|
||||
Reference macbeth chart is created that will be correlated with the located
|
||||
macbeth chart guess to produce a confidence value for the match.
|
||||
"""
|
||||
ref = cv2.imread(Cam.path +'ctt_ref.pgm',flags=cv2.IMREAD_GRAYSCALE)
|
||||
ref_w = 120
|
||||
ref_h = 80
|
||||
rc1 = (0,0)
|
||||
rc2 = (0,ref_h)
|
||||
rc3 = (ref_w,ref_h)
|
||||
rc4 = (ref_w,0)
|
||||
ref_corns = np.array((rc1,rc2,rc3,rc4),np.float32)
|
||||
ref_data = (ref,ref_w,ref_h,ref_corns)
|
||||
|
||||
"""
|
||||
locate macbeth chart
|
||||
"""
|
||||
cor,mac,coords,msg = get_macbeth_chart(img,ref_data)
|
||||
|
||||
"""
|
||||
following bits of code tries to fix common problems with simple
|
||||
techniques.
|
||||
If now or at any point the best correlation is of above 0.75, then
|
||||
nothing more is tried as this is a high enough confidence to ensure
|
||||
reliable macbeth square centre placement.
|
||||
"""
|
||||
|
||||
"""
|
||||
brighten image 2x
|
||||
"""
|
||||
if cor < 0.75:
|
||||
a = 2
|
||||
img_br = cv2.convertScaleAbs(img,alpha=a,beta=0)
|
||||
cor_b,mac_b,coords_b,msg_b = get_macbeth_chart(img_br,ref_data)
|
||||
if cor_b > cor:
|
||||
cor,mac,coords,msg = cor_b,mac_b,coords_b,msg_b
|
||||
|
||||
"""
|
||||
brighten image 4x
|
||||
"""
|
||||
if cor < 0.75:
|
||||
a = 4
|
||||
img_br = cv2.convertScaleAbs(img,alpha=a,beta=0)
|
||||
cor_b,mac_b,coords_b,msg_b = get_macbeth_chart(img_br,ref_data)
|
||||
if cor_b > cor:
|
||||
cor,mac,coords,msg = cor_b,mac_b,coords_b,msg_b
|
||||
|
||||
"""
|
||||
In case macbeth chart is too small, take a selection of the image and
|
||||
attempt to locate macbeth chart within that. The scale increment is
|
||||
root 2
|
||||
"""
|
||||
"""
|
||||
These variables will be used to transform the found coordinates at smaller
|
||||
scales back into the original. If ii is still -1 after this section that
|
||||
means it was not successful
|
||||
"""
|
||||
ii = -1
|
||||
w_best = 0
|
||||
h_best = 0
|
||||
d_best = 100
|
||||
"""
|
||||
d_best records the scale of the best match. Macbeth charts are only looked
|
||||
for at one scale increment smaller than the current best match in order to avoid
|
||||
unecessarily searching for macbeth charts at small scales.
|
||||
If a macbeth chart ha already been found then set d_best to 0
|
||||
"""
|
||||
if cor != 0:
|
||||
d_best = 0
|
||||
|
||||
"""
|
||||
scale 3/2 (approx root2)
|
||||
"""
|
||||
if cor < 0.75:
|
||||
imgs = []
|
||||
"""
|
||||
get size of image
|
||||
"""
|
||||
shape = list(img.shape[:2])
|
||||
w,h = shape
|
||||
"""
|
||||
set dimensions of the subselection and the step along each axis between
|
||||
selections
|
||||
"""
|
||||
w_sel = int(2*w/3)
|
||||
h_sel = int(2*h/3)
|
||||
w_inc = int(w/6)
|
||||
h_inc = int(h/6)
|
||||
"""
|
||||
for each subselection, look for a macbeth chart
|
||||
"""
|
||||
for i in range(3):
|
||||
for j in range(3):
|
||||
w_s,h_s = i*w_inc,j*h_inc
|
||||
img_sel = img[w_s:w_s+w_sel,h_s:h_s+h_sel]
|
||||
cor_ij,mac_ij,coords_ij,msg_ij = get_macbeth_chart(img_sel,ref_data)
|
||||
"""
|
||||
if the correlation is better than the best then record the
|
||||
scale and current subselection at which macbeth chart was
|
||||
found. Also record the coordinates, macbeth chart and message.
|
||||
"""
|
||||
if cor_ij > cor:
|
||||
cor = cor_ij
|
||||
mac,coords,msg = mac_ij,coords_ij,msg_ij
|
||||
ii,jj = i,j
|
||||
w_best,h_best = w_inc,h_inc
|
||||
d_best = 1
|
||||
|
||||
|
||||
"""
|
||||
scale 2
|
||||
"""
|
||||
if cor < 0.75:
|
||||
imgs = []
|
||||
shape = list(img.shape[:2])
|
||||
w,h = shape
|
||||
w_sel = int(w/2)
|
||||
h_sel = int(h/2)
|
||||
w_inc = int(w/8)
|
||||
h_inc = int(h/8)
|
||||
for i in range(5):
|
||||
for j in range(5):
|
||||
w_s,h_s = i*w_inc,j*h_inc
|
||||
img_sel = img[w_s:w_s+w_sel,h_s:h_s+h_sel]
|
||||
cor_ij,mac_ij,coords_ij,msg_ij = get_macbeth_chart(img_sel,ref_data)
|
||||
if cor_ij > cor:
|
||||
cor = cor_ij
|
||||
mac,coords,msg = mac_ij,coords_ij,msg_ij
|
||||
ii,jj = i,j
|
||||
w_best,h_best = w_inc,h_inc
|
||||
d_best = 2
|
||||
|
||||
"""
|
||||
The following code checks for macbeth charts at even smaller scales. This
|
||||
slows the code down significantly and has therefore been omitted by default,
|
||||
however it is not unusably slow so might be useful if the macbeth chart
|
||||
is too small to be picked up to by the current subselections.
|
||||
Use this for macbeth charts with side lengths around 1/5 image dimensions
|
||||
(and smaller...?) it is, however, recommended that macbeth charts take up as
|
||||
large as possible a proportion of the image.
|
||||
"""
|
||||
|
||||
if small_chart:
|
||||
|
||||
if cor < 0.75 and d_best > 1 :
|
||||
imgs = []
|
||||
shape = list(img.shape[:2])
|
||||
w,h = shape
|
||||
w_sel = int(w/3)
|
||||
h_sel = int(h/3)
|
||||
w_inc = int(w/12)
|
||||
h_inc = int(h/12)
|
||||
for i in range(9):
|
||||
for j in range(9):
|
||||
w_s,h_s = i*w_inc,j*h_inc
|
||||
img_sel = img[w_s:w_s+w_sel,h_s:h_s+h_sel]
|
||||
cor_ij,mac_ij,coords_ij,msg_ij = get_macbeth_chart(img_sel,ref_data)
|
||||
if cor_ij > cor:
|
||||
cor = cor_ij
|
||||
mac,coords,msg = mac_ij,coords_ij,msg_ij
|
||||
ii,jj = i,j
|
||||
w_best,h_best = w_inc,h_inc
|
||||
d_best = 3
|
||||
|
||||
if cor < 0.75 and d_best > 2:
|
||||
imgs = []
|
||||
shape = list(img.shape[:2])
|
||||
w,h = shape
|
||||
w_sel = int(w/4)
|
||||
h_sel = int(h/4)
|
||||
w_inc = int(w/16)
|
||||
h_inc = int(h/16)
|
||||
for i in range(13):
|
||||
for j in range(13):
|
||||
w_s,h_s = i*w_inc,j*h_inc
|
||||
img_sel = img[w_s:w_s+w_sel,h_s:h_s+h_sel]
|
||||
cor_ij,mac_ij,coords_ij,msg_ij = get_macbeth_chart(img_sel,ref_data)
|
||||
if cor_ij > cor:
|
||||
cor = cor_ij
|
||||
mac,coords,msg = mac_ij,coords_ij,msg_ij
|
||||
ii,jj = i,j
|
||||
w_best,h_best = w_inc,h_inc
|
||||
|
||||
"""
|
||||
Transform coordinates from subselection to original image
|
||||
"""
|
||||
if ii != -1:
|
||||
for a in range(len(coords)):
|
||||
for b in range(len(coords[a][0])):
|
||||
coords[a][0][b][1] += ii*w_best
|
||||
coords[a][0][b][0] += jj*h_best
|
||||
|
||||
"""
|
||||
initialise coords_fit variable
|
||||
"""
|
||||
coords_fit = None
|
||||
# print('correlation: {}'.format(cor))
|
||||
"""
|
||||
print error or success message
|
||||
"""
|
||||
print(msg)
|
||||
Cam.log += '\n' + msg
|
||||
if msg == success_msg:
|
||||
coords_fit = coords
|
||||
Cam.log += '\nMacbeth chart vertices:\n'
|
||||
Cam.log += '{}'.format(2*np.round(coords_fit[0][0]),0)
|
||||
"""
|
||||
if correlation is lower than 0.75 there may be a risk of macbeth chart
|
||||
corners not having been located properly. It might be worth running
|
||||
with show set to true to check where the macbeth chart centres have
|
||||
been located.
|
||||
"""
|
||||
print('Confidence: {:.3f}'.format(cor))
|
||||
Cam.log += '\nConfidence: {:.3f}'.format(cor)
|
||||
if cor < 0.75:
|
||||
print('Caution: Low confidence guess!')
|
||||
Cam.log += 'WARNING: Low confidence guess!'
|
||||
# cv2.imshow('MacBeth',mac)
|
||||
# represent(mac,'MacBeth chart')
|
||||
|
||||
"""
|
||||
extract data from coords_fit and plot on original image
|
||||
"""
|
||||
if show and coords_fit != None:
|
||||
copy = img.copy()
|
||||
verts = coords_fit[0][0]
|
||||
cents = coords_fit[1][0]
|
||||
|
||||
"""
|
||||
draw circles at vertices of macbeth chart
|
||||
"""
|
||||
for vert in verts:
|
||||
p = tuple(np.round(vert).astype(np.int32))
|
||||
cv2.circle(copy,p,10,1,-1)
|
||||
"""
|
||||
draw circles at centres of squares
|
||||
"""
|
||||
for i in range(len(cents)):
|
||||
cent = cents[i]
|
||||
p = tuple(np.round(cent).astype(np.int32))
|
||||
"""
|
||||
draw black circle on white square, white circle on black square an
|
||||
grey circle everywhere else.
|
||||
"""
|
||||
if i == 3:
|
||||
cv2.circle(copy,p,8,0,-1)
|
||||
elif i == 23:
|
||||
cv2.circle(copy,p,8,1,-1)
|
||||
else:
|
||||
cv2.circle(copy,p,8,0.5,-1)
|
||||
copy,_ = reshape(copy,400)
|
||||
represent(copy)
|
||||
|
||||
return(coords_fit)
|
||||
|
||||
def get_macbeth_chart(img,ref_data):
|
||||
"""
|
||||
function returns coordinates of macbeth chart vertices and square centres,
|
||||
along with an error/success message for debugging purposes. Additionally,
|
||||
it scores the match with a confidence value.
|
||||
|
||||
Brief explanation of the macbeth chart locating algorithm:
|
||||
- Find rectangles within image
|
||||
- Take rectangles within percentage offset of median perimeter. The
|
||||
assumption is that these will be the macbeth squares
|
||||
- For each potential square, find the 24 possible macbeth centre locations
|
||||
that would produce a square in that location
|
||||
- Find clusters of potential macbeth chart centres to find the potential
|
||||
macbeth centres with the most votes, i.e. the most likely ones
|
||||
- For each potential macbeth centre, use the centres of the squares that
|
||||
voted for it to find macbeth chart corners
|
||||
- For each set of corners, transform the possible match into normalised
|
||||
space and correlate with a reference chart to evaluate the match
|
||||
- Select the highest correlation as the macbeth chart match, returning the
|
||||
correlation as the confidence score
|
||||
"""
|
||||
|
||||
"""
|
||||
get reference macbeth chart data
|
||||
"""
|
||||
(ref,ref_w,ref_h,ref_corns) = ref_data
|
||||
|
||||
"""
|
||||
the code will raise and catch a MacbethError in case of a problem, trying
|
||||
to give some likely reasons why the problem occred, hence the try/except
|
||||
"""
|
||||
try:
|
||||
"""
|
||||
obtain image, convert to grayscale and normalise
|
||||
"""
|
||||
src = img
|
||||
src,factor = reshape(src,200)
|
||||
original=src.copy()
|
||||
a = 125/np.average(src)
|
||||
src_norm = cv2.convertScaleAbs(src,alpha=a,beta=0)
|
||||
"""
|
||||
This code checks if there are seperate colour channels. In the past the
|
||||
macbeth locator ran on jpgs and this makes it robust to different
|
||||
filetypes. Note that running it on a jpg has 4x the pixels of the
|
||||
average bayer channel so coordinates must be doubled.
|
||||
|
||||
This is best done in img_load.py in the get_patches method. The
|
||||
coordinates and image width,height must be divided by two if the
|
||||
macbeth locator has been run on a demosaicked image.
|
||||
"""
|
||||
if len(src_norm.shape) == 3:
|
||||
src_bw = cv2.cvtColor(src_norm,cv2.COLOR_BGR2GRAY)
|
||||
else:
|
||||
src_bw = src_norm
|
||||
original_bw = src_bw.copy()
|
||||
"""
|
||||
obtain image edges
|
||||
"""
|
||||
sigma=2
|
||||
src_bw = cv2.GaussianBlur(src_bw,(0,0),sigma)
|
||||
t1,t2 = 50,100
|
||||
edges = cv2.Canny(src_bw,t1,t2)
|
||||
"""
|
||||
dilate edges to prevent self-intersections in contours
|
||||
"""
|
||||
k_size = 2
|
||||
kernel = np.ones((k_size,k_size))
|
||||
its = 1
|
||||
edges = cv2.dilate(edges,kernel,iterations=its)
|
||||
"""
|
||||
find Contours in image
|
||||
"""
|
||||
conts,_ = cv2.findContours(edges,
|
||||
cv2.RETR_TREE,
|
||||
cv2.CHAIN_APPROX_NONE)
|
||||
if len(conts) == 0:
|
||||
raise MacbethError(
|
||||
'\nWARNING: No macbeth chart found!'
|
||||
'\nNo contours found in image\n'
|
||||
'Possible problems:\n'
|
||||
'- Macbeth chart is too dark or bright\n'
|
||||
'- Macbeth chart is occluded\n'
|
||||
)
|
||||
"""
|
||||
find quadrilateral contours
|
||||
"""
|
||||
epsilon = 0.07
|
||||
conts_per = []
|
||||
for i in range(len(conts)):
|
||||
per = cv2.arcLength(conts[i],True)
|
||||
poly = cv2.approxPolyDP(conts[i],
|
||||
epsilon*per,True)
|
||||
if len(poly) == 4 and cv2.isContourConvex(poly):
|
||||
conts_per.append((poly,per))
|
||||
|
||||
if len(conts_per) == 0:
|
||||
raise MacbethError(
|
||||
'\nWARNING: No macbeth chart found!'
|
||||
'\nNo quadrilateral contours found'
|
||||
'\nPossible problems:\n'
|
||||
'- Macbeth chart is too dark or bright\n'
|
||||
'- Macbeth chart is occluded\n'
|
||||
'- Macbeth chart is out of camera plane\n'
|
||||
)
|
||||
|
||||
"""
|
||||
sort contours by perimeter and get perimeters within percent of median
|
||||
"""
|
||||
conts_per = sorted(conts_per,key=lambda x:x[1])
|
||||
med_per = conts_per[int(len(conts_per)/2)][1]
|
||||
side = med_per/4
|
||||
perc = 0.1
|
||||
med_low,med_high = med_per*(1-perc),med_per*(1+perc)
|
||||
squares = []
|
||||
for i in conts_per:
|
||||
if med_low <= i[1] and med_high >= i[1]:
|
||||
squares.append(i[0])
|
||||
|
||||
"""
|
||||
obtain coordinates of nomralised macbeth and squares
|
||||
"""
|
||||
square_verts,mac_norm = get_square_verts(0.06)
|
||||
"""
|
||||
for each square guess, find 24 possible macbeth chart centres
|
||||
"""
|
||||
mac_mids = []
|
||||
squares_raw = []
|
||||
for i in range(len(squares)):
|
||||
square = squares[i]
|
||||
squares_raw.append(square)
|
||||
"""
|
||||
convert quads to rotated rectangles. This is required as the
|
||||
'squares' are usually quite irregular quadrilaterls, so performing
|
||||
a transform would result in exaggerated warping and inaccurate
|
||||
macbeth chart centre placement
|
||||
"""
|
||||
rect = cv2.minAreaRect(square)
|
||||
square = cv2.boxPoints(rect).astype(np.float32)
|
||||
"""
|
||||
reorder vertices to prevent 'hourglass shape'
|
||||
"""
|
||||
square = sorted(square,key=lambda x:x[0])
|
||||
square_1 = sorted(square[:2],key=lambda x:x[1])
|
||||
square_2 = sorted(square[2:],key=lambda x:-x[1])
|
||||
square = np.array(np.concatenate((square_1,square_2)),np.float32)
|
||||
square = np.reshape(square,(4,2)).astype(np.float32)
|
||||
squares[i] = square
|
||||
"""
|
||||
find 24 possible macbeth chart centres by trasnforming normalised
|
||||
macbeth square vertices onto candidate square vertices found in image
|
||||
"""
|
||||
for j in range(len(square_verts)):
|
||||
verts = square_verts[j]
|
||||
p_mat = cv2.getPerspectiveTransform(verts,square)
|
||||
mac_guess = cv2.perspectiveTransform(mac_norm,p_mat)
|
||||
mac_guess = np.round(mac_guess).astype(np.int32)
|
||||
"""
|
||||
keep only if candidate macbeth is within image border
|
||||
(deprecated)
|
||||
"""
|
||||
in_border = True
|
||||
# for p in mac_guess[0]:
|
||||
# pptest = cv2.pointPolygonTest(
|
||||
# img_con,
|
||||
# tuple(p),
|
||||
# False
|
||||
# )
|
||||
# if pptest == -1:
|
||||
# in_border = False
|
||||
# break
|
||||
|
||||
if in_border:
|
||||
mac_mid = np.mean(mac_guess,
|
||||
axis=1)
|
||||
mac_mids.append([mac_mid,(i,j)])
|
||||
|
||||
if len(mac_mids) == 0:
|
||||
raise MacbethError(
|
||||
'\nWARNING: No macbeth chart found!'
|
||||
'\nNo possible macbeth charts found within image'
|
||||
'\nPossible problems:\n'
|
||||
'- Part of the macbeth chart is outside the image\n'
|
||||
'- Quadrilaterals in image background\n'
|
||||
)
|
||||
|
||||
"""
|
||||
reshape data
|
||||
"""
|
||||
for i in range(len(mac_mids)):
|
||||
mac_mids[i][0] = mac_mids[i][0][0]
|
||||
|
||||
"""
|
||||
find where midpoints cluster to identify most likely macbeth centres
|
||||
"""
|
||||
clustering = cluster.AgglomerativeClustering(
|
||||
n_clusters=None,
|
||||
compute_full_tree = True,
|
||||
distance_threshold = side*2
|
||||
)
|
||||
mac_mids_list = [x[0] for x in mac_mids]
|
||||
|
||||
if len(mac_mids_list)==1:
|
||||
"""
|
||||
special case of only one valid centre found (probably not needed)
|
||||
"""
|
||||
clus_list = []
|
||||
clus_list.append([mac_mids,len(mac_mids)])
|
||||
|
||||
else:
|
||||
clustering.fit(mac_mids_list)
|
||||
# try:
|
||||
# clustering.fit(mac_mids_list)
|
||||
# except RuntimeWarning as error:
|
||||
# return(0,None,None,error)
|
||||
|
||||
"""
|
||||
create list of all clusters
|
||||
"""
|
||||
clus_list = []
|
||||
if clustering.n_clusters_ >1:
|
||||
for i in range(clustering.labels_.max()+1):
|
||||
indices = [j for j,x in enumerate(clustering.labels_) if x == i]
|
||||
clus = []
|
||||
for index in indices:
|
||||
clus.append(mac_mids[index])
|
||||
clus_list.append([clus,len(clus)])
|
||||
clus_list.sort(key=lambda x:-x[1])
|
||||
|
||||
elif clustering.n_clusters_ == 1:
|
||||
"""
|
||||
special case of only one cluster found
|
||||
"""
|
||||
# print('only 1 cluster')
|
||||
clus_list.append([mac_mids,len(mac_mids)])
|
||||
else:
|
||||
raise MacbethError(
|
||||
'\nWARNING: No macebth chart found!'
|
||||
'\nNo clusters found'
|
||||
'\nPossible problems:\n'
|
||||
'- NA\n'
|
||||
)
|
||||
|
||||
"""
|
||||
keep only clusters with enough votes
|
||||
"""
|
||||
clus_len_max = clus_list[0][1]
|
||||
clus_tol= 0.7
|
||||
for i in range(len(clus_list)):
|
||||
if clus_list[i][1] < clus_len_max * clus_tol:
|
||||
clus_list = clus_list[:i]
|
||||
break
|
||||
cent = np.mean(clus_list[i][0],axis=0)[0]
|
||||
clus_list[i].append(cent)
|
||||
|
||||
"""
|
||||
represent most popular cluster centroids
|
||||
"""
|
||||
# copy = original_bw.copy()
|
||||
# copy = cv2.cvtColor(copy,cv2.COLOR_GRAY2RGB)
|
||||
# copy = cv2.resize(copy,None,fx=2,fy=2)
|
||||
# for clus in clus_list:
|
||||
# centroid = tuple(2*np.round(clus[2]).astype(np.int32))
|
||||
# cv2.circle(copy,centroid,7,(255,0,0),-1)
|
||||
# cv2.circle(copy,centroid,2,(0,0,255),-1)
|
||||
# represent(copy)
|
||||
|
||||
"""
|
||||
get centres of each normalised square
|
||||
"""
|
||||
reference = get_square_centres(0.06)
|
||||
|
||||
"""
|
||||
for each possible macbeth chart, transform image into
|
||||
normalised space and find correlation with reference
|
||||
"""
|
||||
max_cor = 0
|
||||
best_map = None
|
||||
best_fit = None
|
||||
best_cen_fit = None
|
||||
best_ref_mat = None
|
||||
|
||||
for clus in clus_list:
|
||||
clus = clus[0]
|
||||
sq_cents = []
|
||||
ref_cents = []
|
||||
i_list = [p[1][0] for p in clus]
|
||||
for point in clus:
|
||||
i,j = point[1]
|
||||
"""
|
||||
remove any square that voted for two different points within
|
||||
the same cluster. This causes the same point in the image to be
|
||||
mapped to two different reference square centres, resulting in
|
||||
a very distorted perspective transform since cv2.findHomography
|
||||
simply minimises error.
|
||||
This phenomenon is not particularly likely to occur due to the
|
||||
enforced distance threshold in the clustering fit but it is
|
||||
best to keep this in just in case.
|
||||
"""
|
||||
if i_list.count(i) == 1:
|
||||
square = squares_raw[i]
|
||||
sq_cent = np.mean(square,axis=0)
|
||||
ref_cent = reference[j]
|
||||
sq_cents.append(sq_cent)
|
||||
ref_cents.append(ref_cent)
|
||||
|
||||
"""
|
||||
At least three squares need to have voted for a centre in
|
||||
order for a transform to be found
|
||||
"""
|
||||
if len(sq_cents) < 3:
|
||||
raise MacbethError(
|
||||
'\nWARNING: No macbeth chart found!'
|
||||
'\nNot enough squares found'
|
||||
'\nPossible problems:\n'
|
||||
'- Macbeth chart is occluded\n'
|
||||
'- Macbeth chart is too dark of bright\n'
|
||||
)
|
||||
|
||||
ref_cents = np.array(ref_cents)
|
||||
sq_cents = np.array(sq_cents)
|
||||
"""
|
||||
find best fit transform from normalised centres to image
|
||||
"""
|
||||
h_mat,mask = cv2.findHomography(ref_cents,sq_cents)
|
||||
if 'None' in str(type(h_mat)):
|
||||
raise MacbethError(
|
||||
'\nERROR\n'
|
||||
)
|
||||
|
||||
"""
|
||||
transform normalised corners and centres into image space
|
||||
"""
|
||||
mac_fit = cv2.perspectiveTransform(mac_norm,h_mat)
|
||||
mac_cen_fit = cv2.perspectiveTransform(np.array([reference]),h_mat)
|
||||
"""
|
||||
transform located corners into reference space
|
||||
"""
|
||||
ref_mat = cv2.getPerspectiveTransform(
|
||||
mac_fit,
|
||||
np.array([ref_corns])
|
||||
)
|
||||
map_to_ref = cv2.warpPerspective(
|
||||
original_bw,ref_mat,
|
||||
(ref_w,ref_h)
|
||||
)
|
||||
"""
|
||||
normalise brigthness
|
||||
"""
|
||||
a = 125/np.average(map_to_ref)
|
||||
map_to_ref = cv2.convertScaleAbs(map_to_ref,alpha=a,beta=0)
|
||||
"""
|
||||
find correlation with bw reference macbeth
|
||||
"""
|
||||
cor = correlate(map_to_ref,ref)
|
||||
"""
|
||||
keep only if best correlation
|
||||
"""
|
||||
if cor > max_cor:
|
||||
max_cor = cor
|
||||
best_map = map_to_ref
|
||||
best_fit = mac_fit
|
||||
best_cen_fit = mac_cen_fit
|
||||
best_ref_mat = ref_mat
|
||||
|
||||
"""
|
||||
rotate macbeth by pi and recorrelate in case macbeth chart is
|
||||
upside-down
|
||||
"""
|
||||
mac_fit_inv = np.array(
|
||||
([[mac_fit[0][2],mac_fit[0][3],
|
||||
mac_fit[0][0],mac_fit[0][1]]])
|
||||
)
|
||||
mac_cen_fit_inv = np.flip(mac_cen_fit,axis=1)
|
||||
ref_mat = cv2.getPerspectiveTransform(
|
||||
mac_fit_inv,
|
||||
np.array([ref_corns])
|
||||
)
|
||||
map_to_ref = cv2.warpPerspective(
|
||||
original_bw,ref_mat,
|
||||
(ref_w,ref_h)
|
||||
)
|
||||
a = 125/np.average(map_to_ref)
|
||||
map_to_ref = cv2.convertScaleAbs(map_to_ref,alpha=a,beta=0)
|
||||
cor = correlate(map_to_ref,ref)
|
||||
if cor > max_cor:
|
||||
max_cor = cor
|
||||
best_map = map_to_ref
|
||||
best_fit = mac_fit_inv
|
||||
best_cen_fit = mac_cen_fit_inv
|
||||
best_ref_mat = ref_mat
|
||||
|
||||
"""
|
||||
Check best match is above threshold
|
||||
"""
|
||||
cor_thresh = 0.6
|
||||
if max_cor < cor_thresh:
|
||||
raise MacbethError(
|
||||
'\nWARNING: Correlation too low'
|
||||
'\nPossible problems:\n'
|
||||
'- Bad lighting conditions\n'
|
||||
'- Macbeth chart is occluded\n'
|
||||
'- Background is too noisy\n'
|
||||
'- Macbeth chart is out of camera plane\n'
|
||||
)
|
||||
"""
|
||||
Following code is mostly representation for debugging purposes
|
||||
"""
|
||||
|
||||
|
||||
"""
|
||||
draw macbeth corners and centres on image
|
||||
"""
|
||||
copy = original.copy()
|
||||
copy = cv2.resize(original,None,fx=2,fy=2)
|
||||
# print('correlation = {}'.format(round(max_cor,2)))
|
||||
for point in best_fit[0]:
|
||||
point = np.array(point,np.float32)
|
||||
point = tuple(2*np.round(point).astype(np.int32))
|
||||
cv2.circle(copy,point,4,(255,0,0),-1)
|
||||
for point in best_cen_fit[0]:
|
||||
point = np.array(point,np.float32)
|
||||
point = tuple(2*np.round(point).astype(np.int32))
|
||||
cv2.circle(copy,point,4,(0,0,255),-1)
|
||||
copy = copy.copy()
|
||||
cv2.circle(copy,point,4,(0,0,255),-1)
|
||||
|
||||
"""
|
||||
represent coloured macbeth in reference space
|
||||
"""
|
||||
best_map_col = cv2.warpPerspective(
|
||||
original,best_ref_mat,(ref_w,ref_h)
|
||||
)
|
||||
best_map_col = cv2.resize(
|
||||
best_map_col,None,fx=4,fy=4
|
||||
)
|
||||
a = 125/np.average(best_map_col)
|
||||
best_map_col_norm = cv2.convertScaleAbs(
|
||||
best_map_col,alpha=a,beta=0
|
||||
)
|
||||
# cv2.imshow('Macbeth',best_map_col)
|
||||
# represent(copy)
|
||||
|
||||
|
||||
"""
|
||||
rescale coordinates to original image size
|
||||
"""
|
||||
fit_coords = (best_fit/factor,best_cen_fit/factor)
|
||||
|
||||
return(max_cor,best_map_col_norm,fit_coords,success_msg)
|
||||
|
||||
"""
|
||||
catch macbeth errors and continue with code
|
||||
"""
|
||||
except MacbethError as error:
|
||||
return(0,None,None,error)
|
Loading…
Add table
Add a link
Reference in a new issue