Reputation: 19
I'm working on a CV tool for image analysis and use 4 points to form a transformation matrix (map) for image correction (perspective projection - cv.four_point_transform
).
I have a set of source images obtained from a thermal imager with the presence of distortion. I'm successfully applying a pipeline of standard OpenCV functions. You can see it in the pictures below (Fig. 1). Please note that the main pipeline consists of the following steps:
Unfortunately, I come across cases where the Harris corner detector doesn't cope and doesn't detect obtuse angles. I started testing different approaches such as:
As you can see, some of the approaches can be used, but they form a number of parasitic points. I'm afraid that it will be more difficult to fight them than it seems at first glance (Fig. 2).
Fig. 1 - Successful detection
Fig. 2 - Unsuccessful detection
Upvotes: 0
Views: 1063
Reputation: 19
Not so long ago, Dev Aggarwal (@dev-aggarwal) published an interesting and simple approach for determining a fitted quadrilateral. This approach works for me. See the original post at the link.
The theoretical layout of the algorithm is presented in the article - View Frustum Optimization To Maximize Object’s Image Area.
I duplicate the source code here:
import sympy
def appx_best_fit_ngon(img, n: int = 4) -> list[(int, int)]:
# convex hull of the input mask
img_gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
contours, _ = cv2.findContours(
img_gray, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE
)
hull = cv2.convexHull(contours[0])
hull = np.array(hull).reshape((len(hull), 2))
# to sympy land
hull = [sympy.Point(*pt) for pt in hull]
# run until we cut down to n vertices
while len(hull) > n:
best_candidate = None
# for all edges in hull ( <edge_idx_1>, <edge_idx_2> ) ->
for edge_idx_1 in range(len(hull)):
edge_idx_2 = (edge_idx_1 + 1) % len(hull)
adj_idx_1 = (edge_idx_1 - 1) % len(hull)
adj_idx_2 = (edge_idx_1 + 2) % len(hull)
edge_pt_1 = sympy.Point(*hull[edge_idx_1])
edge_pt_2 = sympy.Point(*hull[edge_idx_2])
adj_pt_1 = sympy.Point(*hull[adj_idx_1])
adj_pt_2 = sympy.Point(*hull[adj_idx_2])
subpoly = sympy.Polygon(adj_pt_1, edge_pt_1, edge_pt_2, adj_pt_2)
angle1 = subpoly.angles[edge_pt_1]
angle2 = subpoly.angles[edge_pt_2]
# we need to first make sure that the sum of the interior angles the edge
# makes with the two adjacent edges is more than 180°
if sympy.N(angle1 + angle2) <= sympy.pi:
continue
# find the new vertex if we delete this edge
adj_edge_1 = sympy.Line(adj_pt_1, edge_pt_1)
adj_edge_2 = sympy.Line(edge_pt_2, adj_pt_2)
intersect = adj_edge_1.intersection(adj_edge_2)[0]
# the area of the triangle we'll be adding
area = sympy.N(sympy.Triangle(edge_pt_1, intersect, edge_pt_2).area)
# should be the lowest
if best_candidate and best_candidate[1] < area:
continue
# delete the edge and add the intersection of adjacent edges to the hull
better_hull = list(hull)
better_hull[edge_idx_1] = intersect
del better_hull[edge_idx_2]
best_candidate = (better_hull, area)
if not best_candidate:
raise ValueError("Could not find the best fit n-gon!")
hull = best_candidate[0]
# back to python land
hull = [(int(x), int(y)) for x, y in hull]
return hull
Use as follows:
hull = appx_best_fit_ngon(img)
# add lines
for idx in range(len(hull)):
next_idx = (idx + 1) % len(hull)
cv2.line(img, hull[idx], hull[next_idx], (0, 255, 0), 1)
# add point markers
for pt in hull:
cv2.circle(img, pt, 2, (255, 0, 0), 2)
Of the minuses, I see only one additional dependency on the SymPy package, but this is not critical.
Upvotes: 1
Reputation: 15576
You could...
compensate lens distortion. Then you wouldn't have stray obtuse corners at all, and all the edges would be straight. I believe that's the best path forward.
see about adjusting the focus of your camera. Yes, I understand it's a thermal camera. They can do that.
adjust your thresholds for approxPolyDP
, so it removes some of the obtuse angles.
simply filter the corners of the "unsuccessful" detection by angle of corner, discarding sufficiently obtuse ones. Either do this by threshold on the angle, or by ordering all corners and discarding all but the best four. You must be sensitive to the lengths of the edges incident to a corner. If those are short, the corner may not be significant anyway.
Upvotes: 0
Reputation: 1890
I think detecting only the necessary 4 corners robustly for all possible image is difficult goal.
Therefore...
I would consider to employ some corner detection process which make "enough" detection result. Where, "enough" means that at least the necessary 4 corners can be detected (Undetected them is the most serious problem).
In other word, allowing "Some unnecessary corners may be additionally detected". (of cause, "too many" cannot be allowed)
And then, I would consider some RANSAC like approach, for obtaining the transformation matrix.
Upvotes: 0