I'm trying use OpenCV (4.4.0) to draw the contours of digits in image. But it doesn't work for me. Below my code:
# Read the input image and deskew it
im = cv2.imread('1742.jpg')
# Convert to grayscale and apply Gaussian filtering to filter noisy pixels
im_gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
im_gray = cv2.GaussianBlur(im_gray, (5, 5), 0)
# Threshold the image
ret, im_th = cv2.threshold(im_gray, 127, 255, cv2.THRESH_BINARY_INV)
# Find contours in the image
contours, hierarchy = cv2.findContours(im_th, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(im_th, contours, -1, (0,255,0), 3)
cv2.imshow("Contours", im_th)
Here is the image and the output of my code. I expect it has a green contour per digit.
1742.jpg
import cv2
im = cv2.imread('1742.jpg')
blur = cv2.GaussianBlur(im, (5, 5), 0)
edged = cv2.Canny(blur, 0, 150)
contours, _ = cv2.findContours(edged, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for contour in contours:
"""
draw a rectangle around those contours on main image
"""
[x,y,w,h] = cv2.boundingRect(contour)
cv2.rectangle(im, (x,y), (x+w,y+h), (0, 255, 0), 1)
cv2.imshow('Final Image with Contours', im)
cv2.waitKey()
cv2.imwrite('final.jpg',im)
I used Canny edge detection prior to the findContours method.Here is the result
If you want to draw the contours on the digits then use
import cv2
im = cv2.imread('1742.jpg')
blur = cv2.GaussianBlur(im, (5, 5), 0)
edged = cv2.Canny(blur, 0, 150)
contours, _ = cv2.findContours(edged, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(im, contours, -1, (0,255,0), 3)
cv2.imshow('Final Image with Contours', im)
cv2.waitKey()
Then the result is this
Related
I defined annulus ROI selection function and i would like to find contours in this area. But contours pixel values are neighbors to the zero and out of masked areas equal to zero. Therefore contours couldn't be catch thresholded image.
How can i define annulus ROI or find the contours if function is ok
def annulusROI(img, center, innerR, outerR):
"""
img: Image matrix
center: ROI center point [px] (x,y tuple)
innerR: ROI inner radius [px]
outerR: ROI outer radius [px]
mode: Mask selection for white (255, 255, 255), for black (0, 0, 0) [BGR tuple]
return roi matrix and left-top start point coordinate
"""
outRoi, rectC = rectangleROI(img, center, outerR*2, outerR*2)
mask1 = np.zeros_like(outRoi)
mask2 = np.zeros_like(outRoi)
mask1 = cv2.circle(mask1, (round(outerR),round(outerR)), innerR, (255, 255, 255), -1)
mask2 = cv2.circle(mask2, (round(outerR),round(outerR)), outerR, (255, 255, 255), -1)
mask = cv2.subtract(mask2, mask1)
roi = cv2.bitwise_and(outRoi, mask)
return roi, (center[0]-outerR, center[1]-innerR)
contour
thresholded
roi returned image
After thresholding and before getting the contours you can separate the region of interest from the outer area. Or even better you can cut your region of interest after thresholding, not before. Finally you can filter out the releavant contours by area size.
import cv2
# get image threshold
img = cv2.imread("img.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 64, 255, 0)
# separate annulus from outer area
h, w, _ = img.shape
center = (round(w / 2), round(h / 2))
innerR = 246
outerR = 306
cv2.circle(thresh, center, innerR, 255)
cv2.circle(thresh, center, outerR, 255)
# filter contours by relevant area size
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cc = [c for c in contours if 100 < cv2.contourArea(c) < 5000]
cv2.drawContours(img, cc, -1, (0, 0, 255))
cv2.imwrite("out.png", img)
Result:
I am using this code to remove this yellow stamp from an image :
import cv2
import numpy as np
# read image
img = cv2.imread('input.jpg')
# threshold on yellow
lower = (0, 200, 200)
upper = (100, 255, 255)
thresh = cv2.inRange(img, lower, upper)
# apply dilate morphology
kernel = np.ones((9, 9), np.uint8)
mask = cv2.morphologyEx(thresh, cv2.MORPH_DILATE, kernel)
# get largest contour
contours = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
big_contour = max(contours, key=cv2.contourArea)
x, y, w, h = cv2.boundingRect(big_contour)
# draw filled white contour on input
result = img.copy()
cv2.drawContours(result, [big_contour], 0, (255, 255, 255), -1)
cv2.imwrite('yellow_removed.png', result)
# show the images
cv2.imshow("RESULT", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
I get the following error:
big_contour = max(contours, key=cv2.contourArea) ValueError: max() arg
is an empty sequence
Obviously, it is not detecting any contours, and the contours array is empty, but I could not figure out why that is or how to fix it.
Help is appreciated!
Check your lower thresholds. It worked for me for both images when I changed the lower threshold to lower = (0, 120, 120).
The thresholds is the reason due to the second image being darker. Lowering these thresholds captures more of the yellow area, but will still leave some holes when drawing the contour.
lower = (0, 130, 130)
You can fix this by drawing the bounding rectangle instead.
cv2.rectangle(result,(x,y),(x+w,y+h),(255,255,255),-1)
Using HSV color space is great for figuring out a particular shade/tone of color. When you have dominant colors to isolate, you can opt for the LAB color space. I have explained as to why this is better in this answer.
Code:
img = cv2.imread('bill.jpg')
# create another copy for the result
img2 = img.copy()
# convert to LAB space and store b-channel
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
b_channel = lab[:,:,-1]
Notice how bright the yellow region is above.
# Perform Otsu threshold
th = cv2.threshold(b_channel, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
# Find the contour with largest area
contours, hierarchy = cv2.findContours(th, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
c = max(contours, key = cv2.contourArea)
# draw the contour on plain black image of same shape as original
mask = np.zeros((img.shape[0], img.shape[1]), np.uint8)
mask = cv2.drawContours(mask,[c],0,255, -1)
# dilation to avoid border effects
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
dilate = cv2.dilate(mask, kernel, iterations=1)
img2[dilate == 255] = (255, 255, 255)
Another example:
Input:
Result:
I have samples images of stones present in the images. I need to identify the visible stones only. The approach which I tried is threshold based filtering and detecting cv2.contours. Also, I am looking into ENet Architecture for semantic segmentation based deep learning approach. The samples images are below.
Example image1:
Example image2:
The code which I tried for contour based detection is as below
image = cv2.imread(os.path.join(img_path, img_name2))
# threshold based customization
lower_bound = np.array([0, 0, 0])
upper_bound = np.array([250,55,100])
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
#masking the image using inRange() function
imagemask = cv2.inRange(hsv, lower_bound, upper_bound)
plt.figure(figsize=(20,10))
plt.imshow(imagemask, cmap="gray")
# erode and diluation to smoothen the edeges
final_mask = cv2.erode(imagemask, np.ones((3, 3), dtype=np.uint8))
final_mask = cv2.dilate(imagemask, np.ones((5, 5), dtype=np.uint8))
# find contours based on the mask
contours = cv2.findContours(final_mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# draw contours
img_conts = cv2.drawContours(image.copy(), contours[0], -1, (0,255,0), 3)
plt.figure(figsize=(20,10))
plt.imshow(img_conts, cmap="gray")
The sample contours ouput. I know that the thresholds can be tuned for better results here.
But, what I am looking here for the any better approach or solution can work in this heavy environment for detection small particles like stones. Any ideas to solve in better way?
Here is how you can use the Canny edge detector to detect the rocks in your images:
import cv2
import numpy as np
def process(img):
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(img_gray, 103, 255, cv2.THRESH_BINARY)
img_blur = cv2.GaussianBlur(thresh, (23, 23), 0)
img_canny = cv2.Canny(img_blur, 65, 0)
img_dilate = cv2.dilate(img_canny, None, iterations=2)
return cv2.erode(img_dilate, None, iterations=2)
imgs = [cv2.imread("image1.jpg"), cv2.imread("image2.jpg")]
for i, img in enumerate(imgs):
contours = cv2.findContours(process(img), cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)[0]
cv2.drawContours(img, contours, -1, (0, 255, 0), 1)
cv2.imshow(str(i), img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Output for sample images 1 and 2:
You can also tweak the parameters using OpenCV trackbars using the code below:
import cv2
import numpy as np
from random import randint, sample
def process(img, c_t1, c_t2):
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(img_gray, 103, 255, cv2.THRESH_BINARY)
img_blur = cv2.GaussianBlur(thresh, (23, 23), 0)
img_canny = cv2.Canny(img_blur, c_t1, c_t2)
img_dilate = cv2.dilate(img_canny, None, iterations=2)
return cv2.erode(img_dilate, None, iterations=2)
def show(imgs, win="Image", scale=1):
imgs = [cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) if len(img.shape) == 2 else img for img in imgs]
img_concat = np.concatenate(imgs, 1)
h, w = img_concat.shape[:2]
cv2.imshow(win, cv2.resize(img_concat, (int(w * scale), int(h * scale))))
d = {"Canny Threshold 1": (65, 500),
"Canny Threshold 2": (0, 500)}
imgs = [cv2.imread("image1.jpg"), cv2.imread("image2.jpg")]
cv2.namedWindow("Track Bars")
for i in d:
cv2.createTrackbar(i, "Track Bars", *d[i], id)
while True:
c_t1, c_t2 = (cv2.getTrackbarPos(i, "Track Bars") for i in d)
for i, img in enumerate(imgs):
img_copy = img.copy()
processed = process(img, c_t1, c_t2)
contours = cv2.findContours(processed, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)[0]
cv2.drawContours(img_copy, contours, -1, (0, 255, 0), 1)
show([img_copy, processed], str(i))
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cv2.destroyAllWindows()
Output:
(Click image to expand)
I want to detect a rectangle shape in the digital meter, to detect the shape contour approximation, but not able to find the exact contour of rectangle .I don't know where is the mistake .please have a look and suggest
digitalMeter.jpg
required-Output-digitalMeter-contour
import imutils
import cv2
image = cv2.imread('C:\\digitalMeter.jpg')
image = imutils.resize(image, height=500)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
edged = cv2.Canny(blurred, 50, 200, 255)
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
displayCnt = None
for c in (cnts):
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.02 * peri, True)
if len(approx) == 4:
print(displayCnt)[enter image description here][2]
displayCnt = approx
break
cv2.drawContours(image, [displayCnt], -1, (0, 230, 255), 6)
cv2.imshow('cnts', image)
cv2.waitKey(0)
Here is one way to do that in Python/OpenCV.
Read the input
Convert to gray
Threshold
Apply morphology to clean up the threshold image
Invert so that the meter is white on a black background
Find the contours and extract the largest (actually only) contour
Draw the contour on the input image
Save the results
Input:
import cv2
import numpy as np
# read image
img = cv2.imread('digital_meter.jpg')
hh, ww = img.shape[:2]
# convert to grayscale
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# threshold
thresh = cv2.threshold(gray,30,255,cv2.THRESH_BINARY)[1]
# apply close and open morphology
kernel = np.ones((3,3), np.uint8)
mask = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
kernel = np.ones((11,11), np.uint8)
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
# invert
mask = 255 - mask
# get largest contour
contours = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
big_contour = max(contours, key=cv2.contourArea)
# draw green contour on input
contour_img = img.copy()
cv2.drawContours(contour_img,[big_contour],0,(0,255,0),2)
# save cropped image
cv2.imwrite('digital_meter_thresh.png',thresh)
cv2.imwrite('digital_meter_mask.png',mask)
cv2.imwrite('digital_meter_contour.png',contour_img)
# show the images
cv2.imshow("THRESH", thresh)
cv2.imshow("MASK", mask)
cv2.imshow("CONTOUR", contour_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Threshold Image:
Morphology cleaned and inverted image:
Resulting contour on input:
I have a test image (see the 1st image below), and a very simple code to blur and make canny edge detection of this image, then use findcontours to get contours.
image = cv2.imread("testimage.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(image, (11,11), 0)
cv2.imshow("Blurred", blurred)
canny = cv2.Canny(blurred, 50, 130)
cv2.imshow("Canny", canny)
(_, conts, _) = cv2.findContours(canny.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
img = image.copy()
for c in conts:
M = cv2.moments(c)
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
cv2.drawContours(img, [c], -1, (0,255,0), 1)
cv2.putText(img, "area:"+str(cv2.contourArea(c)), (cX-20,cY-20),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)
cv2.imshow("Contours", img)
cv2.waitKey(0)
I put the value of each contour on the image. As you may see, the contours look similar, but there is one contour with area value extremely low (only 16.0).
What might be the reason for this? And how to get consistent values among these contours?