-2

I am trying to detect the contour of the die edge in the picture, but there is an obstacle on the right top of the image.

Original picture:https://pan.quark.cn/s/0cdf6fe9784f

Whole dataset: https://pan.quark.cn/s/8dfd9b5494e1

Here is what I tried so far:

import numpy as np
import cv2
import time
import glob
import os

def process_img(img):
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    gray = cv2.GaussianBlur(gray, (15, 15), 1)
    ret, th1 = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
    
    #th1 = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 17, 2)

    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
    th1 = cv2.morphologyEx(th1, cv2.MORPH_OPEN, kernel)
    th1 = cv2.morphologyEx(th1, cv2.MORPH_CLOSE, kernel)

    edge = cv2.Canny(th1, 150, 255)
    return th1, img, edge


def get_roi(img, binary):
    """
    img: source pic
    binary: canny
    """
    # 寻找轮廓
    contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

    max_area = 0
    temp = 0
    for cnt in range(len(contours)):
        xxx = cv2.contourArea(contours[cnt])
        if xxx > max_area:
            max_area = xxx
            temp = cnt

    series = contours[temp]
    x, y, w, h = cv2.boundingRect(series)
    p = cv2.arcLength(series, True)
    cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)
    cv2.drawContours(img, [series], 0, (255, 0, 255), 2)

    return img


def die_select(img: np.ndarray, img_template: np.ndarray = None) -> np.ndarray:
    th1, img, edge = process_img(img)
    img = get_roi(img, edge)
    return img

So far, my results look like this:

picture 1

My desired results look like this: picture 2

0

2 Answers 2

0

Here is my approach, first read the image and convert to grayscale. Use OTSU thresholding to get the region of interest. After that, get contours and get the largest by area, this should correspond to the object:

im = cv2.imread("example.png") # read the iamge
imGray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY) # convert to gray
imGray = cv2.equalizeHist(imGray) # equalize hist, maybe not necessary
imOTSU = cv2.threshold(imGray, 0, 255, cv2.THRESH_OTSU+cv2.THRESH_BINARY_INV)[1] # get otsu with inner as positive
imOTSUOpen =  cv2.morphologyEx(imOTSU, cv2.MORPH_OPEN, np.ones((3,3), np.uint8)) # open
contours, _ = cv2.findContours(imOTSUOpen, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) # get contours
largestContour = max(contours, key = cv2.contourArea) # get the largest
# get X, Y coordinates
X, Y = largestContour.T
X = X[0]
Y = Y[0]

From here, I played around with quantiles and I managed to identify the upper left and lower right corners, which is all you need for a bounding rectangle:

plt.figure() # new fiugre
plt.imshow(im) # show image
plt.axvline(min(X)) # draw verticle line at minimum x
plt.axhline(max(Y)) # draw horizontal line at minimum y
upperLeft = (int(np.quantile(X, 0.1)), int(np.quantile(Y, 0.25))) # get quantiles as corner
lowerRight = (int(np.quantile(X, 0.55)), int(np.quantile(Y, 0.9))) # get quantiles as corner
plt.scatter(upperLeft[0], upperLeft[1]) # scatter the corner
plt.scatter(lowerRight[0], lowerRight[1]) # scatter the corner

The plot looks like this:

results in between

And now that you have this, it's quite easy to draw the rectangle:

cv2.rectangle(im, (upperLeft[0], upperLeft[1]), (lowerRight[0], lowerRight[1]), (0, 255, 0), 2) # draw rectangle as green
cv2.imwrite("exampleContoured.png", im)

contoured

I would still check on stack, there should be plenty example of protruding contours and there are definitely robuster ways of solving this.

Edit 1: another variation, identifying the protrusion and subtracting it from the mask:

im = cv2.imread("example2.png") # read the iamge
imGray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY) # convert to gray
imGray = cv2.equalizeHist(imGray) # equalize hist, maybe not necessary
mask = imGray<10 # get pixels under 10
mask = cv2.morphologyEx(mask.astype(np.uint8), cv2.MORPH_OPEN, np.ones((5,5), np.uint8)) # open
mask = cv2.dilate(mask, np.ones((10,10), np.uint8)) # dilate
imOTSU = cv2.threshold(imGray, 0, 1, cv2.THRESH_OTSU+cv2.THRESH_BINARY_INV)[1] # get otsu with inner as positive
imOTSUOpen =  cv2.morphologyEx(imOTSU, cv2.MORPH_OPEN, np.ones((3,3), np.uint8)) # open
imOTSUOpen = np.clip(imOTSUOpen-mask, 0 , 1)
contours, _ = cv2.findContours(imOTSUOpen, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) # get contours
largestContour = max(contours, key = cv2.contourArea) # get the largest
imContoured = cv2.drawContours(im.copy(), largestContour, -1, (0,255,0), 5) # draw contour
x, y, w, h = cv2.boundingRect(largestContour) # get bounding rect
cv2.rectangle(imContoured, (x, y), (x + w, y + h), (0, 0, 255), 2) # draw rectangle
cv2.imwrite("exampleContoured3.png", imContoured) # save image

The result looks like this, with the contour in green and the bounding rectangle in red:

results

About the code and how it works:

  • get the threshold like before with OTSU
  • get the protrusion mask as it is distinct from other pixels due to its' low value. Do some morphology to emphasize that region.

You get the following:

masks

Again, probably not the best method. I'm not gonna take a look at 10-20 images and make sure it works for all of them, as you will find that future images might not work. Use my answer as a base for your actual implementation, as you have the domain knowledge and you are aware of limitations with your equipment and task.

Sign up to request clarification or add additional context in comments.

2 Comments

This is not a solution for all images. This is one solution for one image. As I said, it's the result of some trial and error with the selection of the qunatiles... I'll take a look later
please include this in your question so it's easier for others to readily see the data
0

Okay here is a weird solution for you:

  • Firstly, you need to use cv2.THRESH_BINARY_INV instead of cv2.THRESH_BINARY because opencv detects contours from black-to-white passings.

  • Secondly, if you close with small kernel and than open with a big kernel you will get a better result here.

This is thresholded image:

Thresholded

This is closed image with kernel size 11: Closed

This is opened image with kernel size 33: enter image description here

Now the weird part begins:

After filtering the image there are still imperfections due noise (look top right corner). On the final filtered image i found the maximum area contour and get its bounding rectangle. The bounding rectangle is not a perfect fit due to noise at the corner.To fit it better:

  • Choose an edge, take 3 points see if all of them are white pixels in filtered image if not move the edge towards the center and repeat.
  • Repeat for all four edges, you will get better fitted rectangle.

On this image red rectangle is the bounding rectangle and the yellow is the max contour which corresponds to your die: Contour-Rect

On this image red rectangle is the bounding rectangle again and the green rectangle is the better fitted version.

Rect-BetterRect

This is the complete code:

import cv2


img = cv2.imread('o7oNM.png')

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (7, 7), 1)

ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)
cv2.imshow('Thresholded',thresh)

thresh = cv2.morphologyEx(thresh,cv2.MORPH_CLOSE,cv2.getStructuringElement(cv2.MORPH_RECT,(11,11)))
cv2.imshow('Closed',thresh)

thresh = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,cv2.getStructuringElement(cv2.MORPH_RECT,(33,33)))
cv2.imshow('Opened',thresh)


contours,hierarchy = cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
max_cnt = max(contours,key=cv2.contourArea)
x,y,w,h = cv2.boundingRect(max_cnt)
cv2.rectangle(img,(x,y),(x+w,y+h),(0,0,255),1) #old rect

#Improve top edge
print('Old y:',y)
while not (thresh[y+1][x+w//4] and thresh[y+1][x+2*w//4] and thresh[y+1][x+3*w//4]):
    y = y+1
print('New y:',y)

#Improve bot edge
print('Old h:',h)
while not (thresh[y+h-1][x+w//4] and thresh[y+h-1][x+2*w//4] and thresh[y+h-1][x+3*w//4]):
    h = h-1
print('New h:',h)

#Improve left edge
print('Old x:',x)
while not (thresh[y+h//4][x+1] and thresh[y+2*h//4][x+1] and thresh[y+3*h//4][x+1]):
    x = x+1
print('New x:',x)

#Improve right edge
print('Old w:',w)
while not (thresh[y+h//4][x+w-1] and thresh[y+2*h//4][x+w-1] and thresh[y+3*h//4][x+w-1]):
    w = w-1
print('New w:',w)

cv2.drawContours(img,[max_cnt],-1,(0,255,255))
cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),1) #new rect
cv2.imshow('Image',img)
cv2.waitKey()

4 Comments

Thank you very much, but I get the result like: pan.quark.cn/s/d4e6de1178b1.I don't know if the reason for this is the use of compressed images or a different version of OPENCV. Please make sure use the original images from pan.quark.cn/s/8dfd9b5494e1 instead of the compressed image. Best wishes!
My bad. I know the reason. You use the compressed image directly from this website. Here is the original image: pan.quark.cn/s/0cdf6fe9784f . Please have a try! Thank you very much and best wishes!
I cant download these images from your link, the website wants registration.
How can I share the data with you?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.