Search code examples
pythonopencvimage-processingcomputer-visionobject-detection

How can I locate this chemical test strip in the picture? OpenCV canny edge detection does not draw the bounding box


I have an image that i would like to do an edge detection and draw a bounding box to it, my problem is my python code does not draw the bounding box and im not sure if its because it was not able to detect any objects in it or im just drawing the rectangle wrong.

1

Here is my attempt

import cv2
import numpy as np

img = cv2.imread("image1.jpg")
(B, G, R) = cv2.split(img)
img = cv2.Canny(B, 20, 100)   # Blue channel gives the best box so far
# img = cv2.Canny(R, 20, 100)
# img = cv2.Canny(R, 20, 100)


ret,thresh = cv2.threshold(img,20,100,0)
contours,hierarchy = cv2.findContours(thresh, 1, 2)


cnt = contours[0]
M = cv2.moments(cnt)

for c in contours:
    rect = cv2.minAreaRect(c)
    box = cv2.boxPoints(rect)
    box = np.intp(box)
    img = cv2.drawContours(img,[box],0,(0,0,255),2)



# display the image with bounding rectangle drawn on it 
# cv2.namedWindow('Bounding Rectangle', cv2.WINDOW_KEEPRATIO)
cv2.imshow('Bounding Rectangle', img) 

cv2.waitKey(0) 
cv2.destroyAllWindows() 

This produces this image

2

and I am expecting an image that is something like this:

3


Solution

  • My approach:

    • optional: correct white balance so that the background loses its tint
    • convert to color space with saturation channel, threshold that
    • contours, minAreaRect

    This approach detects all four colored squares.

    # you can skip this step by defining `balanced = im * np.float32(1/255)`
    
    # gray = im.mean(axis=(0,1), dtype=np.float32) # gray world
    gray = np.median(im, axis=(0,1)).astype(np.float32) # majority vote
    print(gray) # [137. 127. 140.]
    balanced = (im / gray) * 0.8 # some moderate scaling so it's not "overexposed"
    

    enter image description here

    # transform into color space that has a "saturation" axis
    hsv = cv.cvtColor(balanced, cv.COLOR_BGR2HSV)
    H,S,V = cv.split(hsv)
    # squares nicely visible in S
    

    enter image description here

    for comparison, the saturation channel of the source, without white balance:

    enter image description here

    # Otsu finds threshold level automatically
    (_, mask) = cv.threshold((S * 255).astype(np.uint8), 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
    
    # To remove minor debris/noise. Increase iterations as needed.
    mask = cv.morphologyEx(mask, cv.MORPH_OPEN, kernel=None, iterations=5)
    

    mask

    (contours, _) = cv.findContours(mask, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
    hull = cv.convexHull(np.concatenate(contours))
    rrect = cv.minAreaRect(hull)
    
    # visualization
    canvas = im.copy()
    cv.drawContours(image=canvas, contours=contours, contourIdx=-1, color=(255,255,0), thickness=7)
    # cv.drawContours(image=canvas, contours=[hull], contourIdx=-1, color=(255,255,0), thickness=7)
    rrect_points = cv.boxPoints(rrect).round().astype(int)
    cv.polylines(canvas, [rrect_points], isClosed=True, color=(0,255,255), thickness=3)
    

    result


    The code in one piece, minus some imshow/imwrite:

    import numpy as np
    import cv2 as cv
    
    im = cv.imread("picture.jpg")
    
    # gray = im.mean(axis=(0,1), dtype=np.float32) # gray world
    gray = np.median(im, axis=(0,1)).astype(np.float32) # majority vote
    print(gray) # [137. 127. 140.]
    balanced = (im / gray) * 0.8 # some moderate scaling so it's not "overexposed"
    
    # transform into color space that has a "saturation" axis
    hsv = cv.cvtColor(balanced, cv.COLOR_BGR2HSV)
    H,S,V = cv.split(hsv)
    # squares nicely visible in S
    
    # Otsu finds threshold level automatically
    (_, mask) = cv.threshold((S * 255).astype(np.uint8), 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
    
    # To remove minor debris/noise. Increase iterations as needed.
    mask = cv.morphologyEx(mask, cv.MORPH_OPEN, kernel=None, iterations=5)
    
    (contours, _) = cv.findContours(mask, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
    hull = cv.convexHull(np.concatenate(contours))
    rrect = cv.minAreaRect(hull)
    
    # visualization
    canvas = im.copy()
    cv.drawContours(image=canvas, contours=contours, contourIdx=-1, color=(255,255,0), thickness=7)
    # cv.drawContours(image=canvas, contours=[hull], contourIdx=-1, color=(255,255,0), thickness=7)
    rrect_points = cv.boxPoints(rrect).round().astype(int)
    cv.polylines(canvas, [rrect_points], isClosed=True, color=(0,255,255), thickness=3)