0

I have various contours extracted by cv2.findContours(cv2.CHAINE_APPROX_NONE). After that I also extracted the extreme points. Now I´d like to find the point in the contour opposite to the leftmost/rightmost. The point in the contour with the same y-coordinates as the leftmost/rightmost point, so to say. As the format of the coordinates is hardly readable by most functions, maybe the trick is to reorder them.

Any idea how to solve that?

    ###contour extraction
    cnts = cv2.findContours(img_thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    cnts = cnts[0]
    (cnts, _) = contours.sort_contours(cnts)

    cnt_number = 0

    for cnt in cnts:
        if cv2.contourArea(cnt) > 500:
        contour_lenght = cv2.arcLength(cnt, True)
        approx = cv2.approxPolyDP(cnt, 0.005 * contour_lenght, True)
        obj_cor = len(approx)

        if obj_cor > 4:
            cv2.drawContours(blank_image, cnt, -1, (0, 0, 0), 2)
            leftmost = tuple(cnt[cnt[:, :, 0].argmin()][0])
            rightmost = tuple(cnt[cnt[:, :, 0].argmax()][0])

Also thaught about doing a line-contour-intersection (like suggested by mathematical.coffee here Line intersecting contour in openCv) but not sure how...

            ###bounding box
            x, y, w, h = cv2.boundingRect(approx)
            ###end of line from leftmost
            left_y = leftmost[1]
            left_x = leftmost[0] + w
            left_line_endpoint = (left_x, left_y)
            ###line intersect function suggested by mathematical.coffee
            numpy.logical_and(...)
ArchCode
  • 11
  • 2

1 Answers1

0

if i got your idea correctly this will be something like this (please excuse my poor command over linear algebra)

import cv2
import numpy as np

def intersection(l0, l1):   # (origin, drection), (origin, drection) -> point in scene coords
    l0 = np.float32(l0)
    l1 = np.float32(l1)

    dOrg = l1[0] -l0[0]
    g1ort = np.float32([-l1[1][1], l1[1][0]])
    g0 = np.float32([l0[1][0], l0[1][1]])

    if abs(np.dot(g0, g1ort)) < 1e-32:
        return None
    else:
        t0 = np.dot(dOrg, g1ort)/np.dot(g0, g1ort)
        return l0[0] +l0[1]*t0

img = np.zeros((450, 450, 3), dtype=np.uint8)

# badpoints = [[10,100], [180,250], [380,160], [220,360], [180,360], [70,210]]
points = [[200,100], [180,250], [380,160], [220,360], [180,360], [70,210]]
cont = [np.int32(points).reshape(len(points), 1, 2)]  # convert it into this weird format cv so wants
cv2.drawContours(img, cont, -1, (0,0xff,0), 3)

leftmost = points[np.argmin(np.array(points)[:,0])]
hor_line = [leftmost, [100, 0]]
cont_lines = np.float32(list(zip(points, points[1:]+[points[0]])))  # make segment list (scene coords)
cont_lines = [[segment[0], segment[1]-segment[0]] for segment in cont_lines]    # convert to parametric
intersections = [intersection(hor_line, line) for line in cont_lines]   # cache intersection results for each segt

for pt in intersections:
    if pt is not None:
        cv2.circle(img, tuple(np.int32(pt)), 7, (0,0,255), 3)

rightmost = None
for isect, segt in zip(intersections, cont_lines):
    if isect is not None:
        gline = segt[1]
        dOrg = isect -segt[0]
        t = np.dot(gline, dOrg)/np.dot(gline, gline)
        if 0 <= t <= 1:  # check that only those intersection points that lie in boundaries of segments are used
            if rightmost is None:
                rightmost = isect
            elif rightmost[0] < isect[0]:
                rightmost = isect

cv2.circle(img, tuple(np.int32(rightmost)), 11, (255,0,0), 3)

cv2.imshow('test', img)
cv2.waitKey(0)

consider replacing points array with badpoints. this will result in leftmost point also being topmost, so that there is no matching segment to the right of it to intersect

JVod
  • 151
  • 5
  • Thanks, JVod. Your code worked fine and is close to what I need. I´m trying to apply it to my code. Makes trouble as I´m not sure how to insert my contours to where you used the points. – ArchCode Jan 27 '21 at 09:44
  • @ArchCode try using this this function to convert single contour into array of points `def cv_cont2points(cont): cont = np.array(cont) return cont.reshape(cont.shape[0], 2)` – JVod Jan 27 '21 at 10:12
  • sorry for the late response. I wasn´t able to get your suggestion to run for me. I came up with a bit more unsophisticated solution, where I take the known points x-value, add the width of the bounding box to it, run the cv2.pointPolygontest() and substracted the result from the x-value. Here is the code, if you´re interested: `leftmost = tuple(cnt[cnt[:, :, 0].argmin()][0]) left_end_x = leftmost[0] + w left_end = (left_end_x, leftmost[1]) dist_to_cnt_left = abs(cv2.pointPolygonTest(cnt, left_end, True)) left_end_new = (int(left_end_x - dist_to_cnt_left), left_end[1])` – ArchCode Feb 04 '21 at 14:14