0

I am trying to identify the boundaries of plant wells in a tray in an image such as this one:

original image

When I run my thresholding process, the image looks like this:

binary plant image

However, when I try to draw the contours onto the original image, the output is incomplete. I believe this image is clear enough to work well, but I can't find where I am going wrong. Here is my code:

def angle_cos(p0, p1, p2):
    d1, d2 = (p0-p1).astype('float'), (p2-p1).astype('float')
    return abs( np.dot(d1, d2) / np.sqrt( np.dot(d1, d1)*np.dot(d2, d2) ) )
def find_squares(img):
    img = cv2.GaussianBlur(img, (5, 5), 0)
    squares = []
    for gray in cv2.split(img):
        for thrs in range(0, 255, 26):
            if thrs == 0:
                bina = cv2.Canny(gray, 0, 50, apertureSize=5)
                bina = cv2.dilate(bina, None)
            else:
                _retval, bina = cv2.threshold(gray, thrs, 255, cv2.THRESH_BINARY)
            contours = cv2.findContours(bina, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
            for cnt in contours[1]:
                cnt_len = cv2.arcLength(cnt, True)
                cnt = cv2.approxPolyDP(cnt, 0.02*cnt_len, True)
                if len(cnt) == 4 and cv2.contourArea(cnt) > 1000 and cv2.isContourConvex(cnt):
                    cnt = cnt.reshape(-1, 2)
                    max_cos = np.max([angle_cos( cnt[i], cnt[(i+1) % 4], cnt[(i+2) % 4] ) for i in range(4)])
                    if max_cos < 0.1:
                        squares.append(cnt)
     return squares
squares = find_squares(ct2)
newim = cv2.drawContours(dupimage, squares, -1, (0, 0, 255), 3)
plt.figure()
plt.imshow(newim)

There are no errors, it is just producing an image that is not correctly identifying all of the very clear squares. Can anyone see the mistake I'm making?

The updated image looks like this:

output image

Jonathan Feenstra
  • 2,534
  • 1
  • 15
  • 22
kitkactus
  • 1
  • 1
  • What do you get when you print `squares`? – DisappointedByUnaccountableMod Nov 19 '19 at 19:59
  • @barny [array([[1584, 2314], [1581, 2578], [1306, 2568], [1307, 2318]], dtype=int32), array([[1586, 2311], [1304, 2316], [1306, 2575], [1584, 2580]], dtype=int32), array([[3504, 2302], [3769, 2308], [3750, 2562], [3481, 2564]], dtype=int32), array([[3501, 2300], [3479, 2567], – kitkactus Nov 19 '19 at 20:19
  • 1
    We can help you better if you upload the original images instead of plots. The reason for the incomplete contours might be caused by scaling of the plot. you can use `cv2.imwrite('newimg.png', newimg)` to save the full sized image. – J.D. Nov 19 '19 at 22:11
  • @J.D. I replaced the plots, but I am facing the same problem still – kitkactus Nov 21 '19 at 14:46
  • Related: [How to remove convexity defects in a Sudoku square?](https://stackoverflow.com/q/10196198/2545927) – kkuilla Nov 22 '19 at 09:33

1 Answers1

2

You can improve the result (and performance) considerably with a some easy modifications.

First, the 'grid' in the thresholded image has a lot of noise and defects in the lines. You can improve that using morhpological opening. Also only searching for external contours suffices for this image (though it might not for other images).

Next, the method to detect a square seems quite complex and I think it's to strict. Since the wells are squares, you can simply divide the width by the height of the boundingRect of contour, which should be close to 1.

Result - found squares are colored in gray:

enter image description here

Code:

import cv2
import numpy as np
# load  threshold image
img = cv2.imread('5XbZN.jpg',0)
# resize image
img = cv2.resize(img,None,None,fx=0.2,fy=0.2, interpolation=cv2.INTER_CUBIC)
# remove noise / close lines
kernel = np.ones((30,30))
img = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
# find contours
contours, hier = cv2.findContours(img,cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# if the contour is square-ish and has a minimum size, draw contours in gray
for cnt in contours:
    (x,y,w,h) = cv2.boundingRect(cnt)
    area = cv2.contourArea(cnt)
    if abs((w/h)-1) < 0.2 and area > 2500:
        cv2.drawContours(img,[cnt],0,(127),-1)

# show image
cv2.imshow('Img',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
J.D.
  • 4,511
  • 2
  • 7
  • 20