4

I have a problem finding the contours of black objects on white background.

example

here I added an example of an image. Now I need to find center of black regions and I use following code.

im = cv2.imread(img)
plt.imshow(im)
gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
thresh = cv2.threshold(im, 60, 255, cv2.THRESH_BINARY_INV)
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)

However it gives an error like

TypeError: Expected Ptr for argument 'image' " for the image argument of cv2.findeContours().

How can I solve this? or if you have any other idea how to find center of black regions I would like to hear. Thanks.

nathancy
  • 42,661
  • 14
  • 115
  • 137
Kanan Yagublu
  • 119
  • 3
  • 11
  • Use `thresh = cv2.threshold(im, 60, 255, cv2.THRESH_BINARY_INV)[1]`, cf. the return values of [`threshold`](https://docs.opencv.org/4.2.0/d7/d1b/group__imgproc__misc.html#gae8a4a146d1ca78c626a53577199e9c57). – HansHirse Mar 02 '20 at 09:54
  • Sorry, It is fist time for me using Opencv, what do you mean cf. the return values of threshold ? – Kanan Yagublu Mar 02 '20 at 10:06
  • 1
    But you do know, what [return statements](https://www.geeksforgeeks.org/python-return-statement/) are in general? When calling `cv2.threshold(...)` and forwarding the result(s) to a single variable `thresh`, that'll be a tuple consisting of `retval` and `dst`. When calling `cv2.threshold(...)[1]`, you only get `dst`, thus forwarding to `thresh`, your `thresh` is the actual thresholded image. – HansHirse Mar 02 '20 at 10:14
  • Thanks I understood what you mean , it kinda solved error related to threshold thanks for that. but there is new error for cv2.findContours() " error: OpenCV(4.2.0) C:\projects\opencv-python\opencv\modules\imgproc\src\contours.cpp:197: error: (-210:Unsupported format or combination of formats) [Start]FindContours supports only CV_8UC1 images when mode != CV_RETR_FLOODFILL otherwise supports CV_32SC1 images only in function 'cvStartFindContours_Impl' " as I understood I should change the mode for image right ? – Kanan Yagublu Mar 02 '20 at 10:23
  • So, what's the type of your original image `img` (and following `gray`, `blurred`, and `thresh`), i.e. what's the output of `print(img.dtype)`? If it's anything other than `uint8`, you need to convert your image appropriately to use `cv2.findContours(...)` as intended. – HansHirse Mar 02 '20 at 11:31
  • I checked all of the are unit8, should I change smth else ? – Kanan Yagublu Mar 02 '20 at 12:00
  • 1
    Your original image is a three-channel image, thus `thresh` is three-channel also, which is not supported in `cv2.findContours(...)`. Use `thresh = cv2.threshold(gray, ...)` instead of `thresh = cv2.threshold(im, ...)` for example. (Haven't noticed that before...) – HansHirse Mar 02 '20 at 12:07
  • Thanks it solved the problem. I have one more question that I want to get coordinates. how Can I get coordinates of each contour ? – Kanan Yagublu Mar 02 '20 at 12:34

1 Answers1

4

You're on the right track. When you perform find contours on your image, OpenCV expects that the foreground objects to detect are in white with the background in black. To do this, you can Otsu's threshold with the cv2.THRESH_BINARY_INV parameter to get the objects in white. From here we can find contours and find the center of each region by calculating for the centroid with cv2.moments. Here's the results:

Binary image

enter image description here

Center of object drawn in blue with the bounding box in green. You can get the coordinates of each contour by checking the cX and cY variables.

enter image description here

You can crop each individual ROI using Numpy slicing then save with cv2.imwrite

enter image description here

Code

import cv2
import numpy as np

# Load image, grayscale, Gaussian blur, Otsu's threshold
image = cv2.imread('1.png')
original = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (3,3), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

# Find contours
ROI_number = 0
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    # Obtain bounding rectangle to get measurements
    x,y,w,h = cv2.boundingRect(c)

    # Find centroid
    M = cv2.moments(c)
    cX = int(M["m10"] / M["m00"])
    cY = int(M["m01"] / M["m00"])

    # Crop and save ROI
    ROI = original[y:y+h, x:x+w]
    cv2.imwrite('ROI_{}.png'.format(ROI_number), ROI)
    ROI_number += 1

    # Draw the contour and center of the shape on the image
    cv2.rectangle(image,(x,y),(x+w,y+h),(36,255,12), 4)
    cv2.circle(image, (cX, cY), 10, (320, 159, 22), -1) 

cv2.imwrite('image.png', image)
cv2.imwrite('thresh.png', thresh)
cv2.waitKey()
nathancy
  • 42,661
  • 14
  • 115
  • 137
  • Thanks, I solved the problem. Now I need to crop those images with the same size , I used numpy slicing but it gave an error like " **TypeError: string indices must be integers** " . how can I solve this or if there is any other method that I can use for cropping? – Kanan Yagublu Mar 03 '20 at 11:57
  • 1
    Check the update you can use Numpy slicing and save images with `cv2.imwrite` – nathancy Mar 03 '20 at 20:55