I wrote a simple code to search for circles in documents (since seals have a rounded shape).
But due to the poor image quality, the print outline is fuzzy, and opencv cannot always detect it. I edited the picture in photoshop and enhanced the dark colors. I saved the picture and sent it for processing. It helped me. Opencv has identified a circle representing a low-quality print (there are no such problems in high-quality documents). My code:
import numpy as np
import cv2
img = cv2.imread(r"C:\buh\doc.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# I tried experimenting with bluer, but opencv doesn't see circles in this case
# blurred = cv2.bilateralFilter(gray.copy(), 15, 15, 15 )
# imS = cv2.resize(blurred, (960, 540))
# cv2.imshow('img', imS)
# cv2.waitKey(0)
minDist = 100
param1 = 30 #500
param2 = 100 #200 #smaller value-> more false circles
minRadius = 90
maxRadius = 200 #10
# docstring of HoughCircles: HoughCircles(image, method, dp, minDist[, circles[, param1[, param2[, minRadius[, maxRadius]]]]]) -> circles
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, minDist, param1=param1, param2=param2, minRadius=minRadius, maxRadius=maxRadius)
if circles is not None:
circles = np.uint16(np.around(circles))
for i in circles[0, :]:
cv2.circle(img, (i[0], i[1]), i[2], (0, 255, 0), 2)
# Show result for testing:
imS = cv2.resize(img, (960, 540))
cv2.imshow('img', imS)
cv2.waitKey(0)
The seals in the documents are circles as in the photo:
Unfortunately, I cannot add photo of the document where the original seals are located, since this is a private information...
So, I need to enhance the shades of black in the photo before trying to look for circles. How can I do this? I would also listen to other suggestions for improving the contours of seals (stamps), if someone has already encountered this.
Thank you.
Example:
Here's a simple approach:
Obtain binary image. Load image, convert to grayscale, Gaussian blur, then Otsu's threshold.
Merge small contours into a single large contour. We dilate using cv2.dilate to merge circles into a single contour.
Find external contours. Finally we find external contours with the external cv2.RETR_EXTERNAL flag and cv2.drawContours()
Visualization of the image pipeline
Input image
Threshold for binary image
Dilate
Detected contours in green
Code
import cv2
import numpy as np
# Load image, grayscale, Gaussian blur, Otsus threshold, dilate
image = cv2.imread('3.PNG')
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]
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
dilate = cv2.dilate(thresh, kernel, iterations=1)
# Find contours
cnts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
cv2.drawContours(image, [c], -1, (36,255,12), 3)
cv2.imshow('image', image)
cv2.imshow('dilate', dilate)
cv2.imshow('thresh', thresh)
cv2.waitKey()
Related
I try to use python, NumPy, and OpenCV to analyze the image below and just draw a circle on each object found. The idea here is not to identify the bug only identify any object that is different from the background.
Original Image:
Here is the code that I'm using.
import cv2
import numpy as np
img = cv2.imread('per.jpeg', cv2.IMREAD_GRAYSCALE)
if cv2.__version__.startswith('2.'):
detector = cv2.SimpleBlobDetector()
else:
detector = cv2.SimpleBlobDetector_create()
keypoints = detector.detect(img)
print(len(keypoints))
imgKeyPoints = cv2.drawKeypoints(img, keypoints, np.array([]), (0,0,255), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
status = cv2.imwrite('teste.jpeg',imgKeyPoints)
print("Image written to file-system : ",status)
But the problem is that I'm getting only a greyscale image as result without any counting or red circle, as shown below:
Since I'm new to OpenCV and object recognition world I'm not able to identify what is wrong, and any help will be very appreciated.
Here is one way in Python/OpenCV.
Threshold on the bugs color in HSV colorspace. Then use morphology to clean up the threshold. Then get contours. Then find the minimum enclosing circle around each contour. Then bias the radius to make a bit larger and draw the circle around each bug.
Input:
import cv2
import numpy as np
# read image
img = cv2.imread('bugs.jpg')
# convert image to hsv colorspace
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# threshold on bugs color
lower=(0,90,10)
upper=(100,250,170)
thresh = cv2.inRange(hsv, lower, upper)
# apply morphology to clean up
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
morph = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (6,6))
morph = cv2.morphologyEx(morph, cv2.MORPH_CLOSE, kernel)
# get external contours
contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
result = img.copy()
bias = 10
for cntr in contours:
center, radius = cv2.minEnclosingCircle(cntr)
cx = int(round(center[0]))
cy = int(round(center[1]))
rr = int(round(radius)) + bias
cv2.circle(result, (cx,cy), rr, (0, 0, 255), 2)
# save results
cv2.imwrite('bugs_threshold.jpg', thresh)
cv2.imwrite('bugs_cleaned.jpg', morph)
cv2.imwrite('bugs_circled.jpg', result)
# display results
cv2.imshow('thresh', thresh)
cv2.imshow('morph', morph)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Threshold Image:
Morphology Cleaned Image:
Resulting Circles:
Here is the image on which i have been working on
The goal is to detect small circles inside the big one.
currently what i have done is converted the image to gray scale and applied threshold (cv2.THRESH_OTSU) to which resulted in this image
After this i have filtered out large objects using findcontours applied Morph open using elliptical shaped kernel which i found on stackoverflow
The result image is like this
Can someone guide me through the correct path on what to do and where i'm getting wrong.
Below is attached code on which i have been working on
import cv2
import numpy as np
# Load image, grayscale, Otsu's threshold
image = cv2.imread('01.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
#cv2.imwrite('thresh.jpg', thresh)
# Filter out large non-connecting objects
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:
area = cv2.contourArea(c)
#print(area)
if area < 200 and area > 0:
cv2.drawContours(thresh,[c],0,0,-1)
# Morph open using elliptical shaped kernel
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=3)
# Find circles
cnts = cv2.findContours(opening, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
area = cv2.contourArea(c)
if area > 20 and area < 50:
((x, y), r) = cv2.minEnclosingCircle(c)
cv2.circle(image, (int(x), int(y)), int(r), (36, 255, 12), 2)
cv2.namedWindow('orig', cv2.WINDOW_NORMAL)
cv2.imshow('orig', thresh)
cv2.namedWindow('image', cv2.WINDOW_NORMAL)
cv2.imshow('image', image)
cv2.waitKey()
Thank you!
You throw away a lot of useful information by converting your image to grayscale.
Why not use the fact that the spots you are looking for are the only thing that is red/orange?
I multiplied the saturaton channel with the red channel which gave me this image:
Now finding the white blobs becomes trivial.
Experiment with different weights for those channels, or apply thresholds first. There are many ways. Experiment with different illumination, different backgrounds until you get the ideal input for your image processing.
The main problem in your code is the flag that you are using in cv2.findContours() function.
For such a problem in which we have to find contours which can appear inside another contour(the big circle), we should not use the flag cv2.RETR_EXTERNAL, instead use cv2.RETR_TREE. Click here for detailed info..
Also, it is always better to use cv2.CHAIN_APPROX_NONE instead of cv2.CHAIN_APPROX_SIMPLE if memory is not an issue. Click here for detailed info.
Thus, the following simple code can be used to solve this problem.
import cv2
import numpy as np
Image = cv2.imread("Adg5.jpg")
GrayImage = cv2.cvtColor(Image, cv2.COLOR_BGR2GRAY)
# Applying Otsu's Thresholding
Retval, ThreshImage = cv2.threshold(GrayImage, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
# Finding Contours in the image
Contours, Hierarchy = cv2.findContours(ThreshImage, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
# Taking only those contours which have no child contour.
FinalContours = [Contours[i] for i in range(len(Contours)) if Hierarchy[0][i][2] == -1]
# Drawing contours
Image = cv2.drawContours(Image, FinalContours, -1, (0, 255, 0), 1)
cv2.imshow("Contours", Image)
cv2.waitKey(0)
Resulting image
In this method, a lot of noise at the boundary is also coming but the required orange points are also being detected. Now the task is to remove boundary noise.
Another method that removes boundary noise to a great extent is similar to #Piglet 's approach.
Here, I am using HSV image to segment out the orange points and then detecting them using the above approach.
import cv2
import numpy as np
Image = cv2.imread("Adg5.jpg")
HSV_Image = cv2.cvtColor(Image, cv2.COLOR_BGR2HSV)
# Extracting orange colour using HSV Image.
ThreshImage = cv2.inRange(HSV_Image, np.array([0, 81, 0]), np.array([41, 255, 255]))
# Finding Contours
Contours, Hierarchy = cv2.findContours(ThreshImage, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
# Taking only those contours which have no child contour.
FinalContours = [Contours[i] for i in range(len(Contours)) if Hierarchy[0][i][2] == -1]
# Drawing Contours
Image = cv2.drawContours(Image, FinalContours, -1, (0, 255, 0), 1)
cv2.imshow("Contours", Image)
cv2.waitKey(0)
Resultant Image
I have an idea that detet small circles by sliding window. when the small cicle area occupied the sliding window area large than 90%(Inscribed circle and square), and less than 100%(avoiding sliding window move in the bigger cicle). this position is a small circle. the largest sliding windows size is the largest small cicle size. Hope some help.
in addtion, on the result of Piglet, apply k-means, which k = 2, you can get a binary image, and then use findcontours to count the small circles.
I need to figure out the best way to choose a minRadius value for use in the OpenCV cv2.HoughCircles function.
I am working on increasing the accuracy of a Tensorflow CNN that does US rare coin classification. Currently, the CNN is reviewing >10k images of all different sizes from 300x300 to 1024x1024
To increase the accuracy of the model, I am attempting to pull the coin out of the image prior to training, and only train the model on the coin and not its surroundings.
The below code works OK in detecting the coin as a circle but I have to try several minRadius values to get the HoughCircles function to work well.
In some cases, minRadius=270 works on a 600x600 and a 785x1024 and in other cases only r=200 works for a 600x600 but fails on the 785x1024. In other cases only r=318 works but not 317 or 319. I've not found a consistent approach.
Question: Is there a recommended methodology to determine minRadius for finding the 1 circle in an image? assuming the image is of different sizes and the coin is taking up from 50% to 90% of the image
here are examples of typical images: https://i.ebayimg.com/images/g/r5oAAOSwH8VeHNBf/s-l1600.jpg
https://i.ebayimg.com/images/g/~JsAAOSwGtdeyFfU/s-l1600.jpg
image = cv2.imread(r"C:\testimages\70a.jpg")
output = image.copy()
height, width = image.shape[:2]
minRadius = 200
maxRadius =0
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
circles = cv2.HoughCircles(image=gray,
method=cv2.HOUGH_GRADIENT,
dp=1.2,
minDist=200*minRadius, #something large since we are looking for 1
param1=200,
param2=100,
minRadius=minRadius,
maxRadius=maxRadius
)
#Draw the circles detected
if circles is not None:
# convert the (x, y) coordinates and radius of the circles to integers
circlesRound = np.round(circles[0, :]).astype("int")
# loop over the (x, y) coordinates and radius of the circles
for (x, y, r) in circlesRound:
cv2.circle(output, (x, y), r, (0, 255, 0), 4)
plt.imshow(output)
else:
print ('No circles found')
Here is a different way to do that by fitting an ellipse to the largest contour extracted from the thresholded image. You can use the ellipse major and minor radii as approximations for your Hough Circles if you want.
Input:
import cv2
import numpy as np
# read input
img = cv2.imread('s-l1600.jpg')
# convert to gray
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# threshold
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# apply morphology open and close
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
morph = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (21,21))
morph = cv2.morphologyEx(morph, cv2.MORPH_CLOSE, kernel)
# find largest contour
contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
big_contour = max(contours, key=cv2.contourArea)
# fit ellipse to contour and get ellipse center, minor and major diameters and angle in degree
ellipse = cv2.fitEllipse(big_contour)
(xc,yc),(d1,d2),angle = ellipse
print('center: ',xc,',',yc)
print('diameters: ',d1,',',d2)
# draw ellipse
result = img.copy()
cv2.ellipse(result, ellipse, (0, 0, 255), 2)
# draw circle at center
xc, yc = ellipse[0]
cv2.circle(result, (int(xc),int(yc)), 5, (0, 255, 0), -1)
cv2.imwrite("s-l1600_thresh.jpg", thresh)
cv2.imwrite("s-l1600_morph.jpg", morph)
cv2.imwrite("s-l1600_ellipse.jpg", result)
cv2.imshow("s-l1600_thresh", thresh)
cv2.imshow("s-l1600_morph", morph)
cv2.imshow("s-l1600_ellipse", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Otsu thresholded image:
Cleaned threshold image:
Ellipse draw from contour fitting on input showing ellipse outline and center:
Ellipse parameters:
center: 504.1853332519531 , 524.3350219726562
diameters: 953.078125 , 990.545654296875
Here is your other image. But here I use color thresholding using inRange().
Input:
import cv2
import numpy as np
# read input
img = cv2.imread('s-l1600b.jpg')
# convert to hsv
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# get color bounds of red circle
lower =(0,0,0) # lower bound for each channel
upper = (150,150,150) # upper bound for each channel
# create the mask and use it to change the colors
thresh = cv2.inRange(hsv, lower, upper)
# apply morphology open and close
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
morph = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (31,31))
morph = cv2.morphologyEx(morph, cv2.MORPH_CLOSE, kernel)
# find largest contour
contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
big_contour = max(contours, key=cv2.contourArea)
# fit ellipse to contour and get ellipse center, minor and major diameters and angle in degree
ellipse = cv2.fitEllipse(big_contour)
(xc,yc),(d1,d2),angle = ellipse
print('center: ',xc,',',yc)
print('diameters: ',d1,',',d2)
# draw ellipse
result = img.copy()
cv2.ellipse(result, ellipse, (0, 0, 255), 2)
# draw circle at center
xc, yc = ellipse[0]
cv2.circle(result, (int(xc),int(yc)), 5, (0, 255, 0), -1)
cv2.imwrite("s-l1600_thresh.jpg", thresh)
cv2.imwrite("s-l1600_morph.jpg", morph)
cv2.imwrite("s-l1600_ellipse.jpg", result)
cv2.imshow("s-l1600b_thresh", thresh)
cv2.imshow("s-l1600b_morph", morph)
cv2.imshow("s-l1600b_ellipse", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Thresholded image:
Morphology cleaned image:
Largest external contour fitted to ellipse and drawn on input:
Ellipse parameters:
center: 497.53564453125 , 639.7144165039062
diameters: 454.8548583984375 , 458.95843505859375
I'm using OpenCV 4 - python 3 - to find an specific area in a black & white image.
This area is not a 100% filled shape. It may hame some gaps between the white lines.
This is the base image from where I start processing:
This is the rectangle I expect - made with photoshop -:
Results I got with hough transform lines - not accurate -
So basically, I start from the first image and I expect to find what you see in the second one.
Any idea of how to get the rectangle of the second image?
I'd like to present an approach which might be computationally less expensive than the solution in fmw42's answer only using NumPy's nonzero function. Basically, all non-zero indices for both axes are found, and then the minima and maxima are obtained. Since we have binary images here, this approach works pretty well.
Let's have a look at the following code:
import cv2
import numpy as np
# Read image as grayscale; threshold to get rid of artifacts
_, img = cv2.threshold(cv2.imread('images/LXSsV.png', cv2.IMREAD_GRAYSCALE), 0, 255, cv2.THRESH_BINARY)
# Get indices of all non-zero elements
nz = np.nonzero(img)
# Find minimum and maximum x and y indices
y_min = np.min(nz[0])
y_max = np.max(nz[0])
x_min = np.min(nz[1])
x_max = np.max(nz[1])
# Create some output
output = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
cv2.rectangle(output, (x_min, y_min), (x_max, y_max), (0, 0, 255), 2)
# Show results
cv2.imshow('img', img)
cv2.imshow('output', output)
cv2.waitKey(0)
cv2.destroyAllWindows()
I borrowed the cropped image from fmw42's answer as input, and my output should be the same (or most similar):
Hope that (also) helps!
In Python/OpenCV, you can use morphology to connect all the white parts of your image and then get the outer contour. Note I have modified your image to remove the parts at the top and bottom from your screen snap.
import cv2
import numpy as np
# read image as grayscale
img = cv2.imread('blackbox.png')
# convert to grayscale
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# threshold
_,thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY)
# apply close to connect the white areas
kernel = np.ones((75,75), np.uint8)
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
# get contours (presumably just one around the outside)
result = img.copy()
contours = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
for cntr in contours:
x,y,w,h = cv2.boundingRect(cntr)
cv2.rectangle(result, (x, y), (x+w, y+h), (0, 0, 255), 2)
# show thresh and result
cv2.imshow("thresh", thresh)
cv2.imshow("Bounding Box", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
# save resulting images
cv2.imwrite('blackbox_thresh.png',thresh)
cv2.imwrite('blackbox_result.png',result)
Input:
Image after morphology:
Result:
Here's a slight modification to #fmw42's answer. The idea is connect the desired regions into a single contour is very similar however you can find the bounding rectangle directly since there's only one object. Using the same cropped input image, here's the result.
We can optionally extract the ROI too
import cv2
# Grayscale, threshold, and dilate
image = cv2.imread('3.png')
original = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
# Connect into a single contour and find rect
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
dilate = cv2.dilate(thresh, kernel, iterations=1)
x,y,w,h = cv2.boundingRect(dilate)
ROI = original[y:y+h,x:x+w]
cv2.rectangle(image, (x, y), (x+w, y+h), (36, 255, 12), 2)
cv2.imshow('image', image)
cv2.imshow('ROI', ROI)
cv2.waitKey()
I have and portrait image. To which I:
Convert to Grayscale
Then convert to binary
Then I used morphological erosion operation.
Then I used morphological dilation operation.
Now, as last process, I am trying to find out the contours and drawing them. However, contours are getting found and drawn too, but, it is only getting drawn in white color, which made it looked like, contours is not drawn at all. What am I doing wrong??
Here is my code:
import numpy as np
import cv2
img = cv2.imread("Test1.jpg")
image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imshow("GrayScaled", image)
cv2.waitKey(0)
ret, thresh = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY_INV)
cv2.imshow("Black&White", thresh)
cv2.waitKey(0)
kernel1 = np.ones((2,2),np.uint8)
erosion = cv2.erode(thresh,kernel1,iterations = 4)
cv2.imshow("AfterErosion", erosion)
cv2.waitKey(0)
kernel2 = np.ones((1,1),np.uint8)
dilation = cv2.dilate(erosion,kernel2,iterations = 5)
cv2.imshow("AfterDilation", dilation)
cv2.waitKey(0)
contours, hierarchy = cv2.findContours(dilation, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cv2.drawContours(dilation, contours, -1, (255, 0, 0), 2)
cv2.imshow("Contours", dilation)
cv2.waitKey(0)
cv2.destroyAllWindows()
Here are images step by step:
Original Image:
GrayScaled Image:
Binary Image:
After Erosion:
After Dilation:
Contour:
I am defining the color of the contours boundary to be red in above code. So, why is it not showing red boundaries??