Detecting Grid blocks using Computer vision in realtime (Open CV, Python) - python

I want to detect the centroid of individual blocks in the following grids for path planning. The idea is that a central navigation system like overhead camera will detect the blocks of grids along with the bot and help in navigation. Till now I have tried Hough lines Probabilistic and Harris corner detection but both of them either detect extra points or fail in real world scenario. I want to detect the blocks in real time and number them. Those numbering should not change or the whole path planning will be messed up.
Is there any solution to this problem that I missed.
thanks in advance

You need to learn how to eliminate noise. This is not a complete answer. The more time you spend and learn, the better your results will be.
import cv2
import numpy as np
import sys
# Load source as grayscale
im = cv2.imread(sys.path[0]+'/im.jpg', cv2.IMREAD_GRAYSCALE)
H, W = im.shape[:2]
# Convert im to black and white
im = cv2.adaptiveThreshold(
im, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 21, 2)
# Remove noise
im = cv2.medianBlur(im, 11)
im = cv2.erode(im, np.ones((15, 15)))
# Fill the area around the shape
im = ~im
mask = np.zeros((H+2, W+2), np.uint8)
cv2.floodFill(im, mask, (0, 0), 255)
cv2.floodFill(im, mask, (W-1, 0), 255)
cv2.floodFill(im, mask, (0, H-1), 255)
cv2.floodFill(im, mask, (W-1, H-1), 255)
# Remove noise again
im = cv2.dilate(im, np.ones((15, 15)))
# Find the final blocks
cnts, _ = cv2.findContours(~im, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for c in cnts:
x, y, w, h = cv2.boundingRect(c)
cv2.circle(im, (x+w//2, y+h//2), max(w, h)//2, 127, 5)
print("Found any: ", len(cnts) > 0)
# Save the output
cv2.imwrite(sys.path[0]+'/im_.jpg', im)

Related

opencv bounding box issue

I have an image I need to draw a bounding box around and I'm trying to use the code at bottom of this post.
The issue I have is that I have tried to blur the blue box shape to remove its details e.g.
cv2.blur(img,(20,20))
but the blurred image doesnt seem to have a good enough edge to produce a bounding box.
I have found that if I use my code below with a plain blue square with sharp edges of the same size as the image below, it works just fine. It seems the detail within the blue area stops a boundary box being drawn.
I was hoping someone could tell me how to get this to work please.
import cv2
# Load the image - container completely blurred out so no hinges,locking bars , writing etc are visible, just a blank blue square
img = cv2.imread('blue_object.jpg')
# convert to grayscale
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
edged = cv2.Canny(img, 120,890)
# Apply adaptive threshold
thresh = cv2.adaptiveThreshold(edged, 255, 1, 1, 11, 2)
thresh_color = cv2.cvtColor(thresh, cv2.COLOR_GRAY2BGR)
# apply some dilation and erosion to join the gaps - change iteration to detect more or less area's
thresh = cv2.dilate(thresh,None,iterations = 50)
thresh = cv2.erode(thresh,None,iterations = 50)
# Find the contours
contours,hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
# For each contour, find the bounding rectangle and draw it
for cnt in contours:
area = cv2.contourArea(cnt)
if area > 50000:
x,y,w,h = cv2.boundingRect(cnt)
cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
cv2.imshow('img',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
The image below is the yellow container after lower HSV mask range of 8,0,0 and upper range of 78,255,255 . Trees are above and to top right of the container, so hard to separate the tress from the container to put a proper bounding box around it.Happy to move to chat if that helps.
You're converting to gray, throwing all that valuable color information away. You're also Canny-ing, which is generally a bad idea. Beginners don't have the judgment to apply Canny sensibly. Best stay away from it.
This can be solved with the usual approach:
transform colorspace into something useful, say HSV
inRange on well-saturated blue
some morphology operations to clean up debris
bounding box
That is assuming you are looking for a blue container, or any well-saturated color really.
im = cv.imread("i4nPk.jpg")
hsv = cv.cvtColor(im, cv.COLOR_BGR2HSV)
lower = (90, 84, 0)
upper = (180, 255, 255)
mask1 = cv.inRange(hsv, lower, upper)
mask2 = cv.erode(mask1, kernel=None, iterations=2)
(x, y, w, h) = cv.boundingRect(mask2) # yes that works on masks too
canvas = cv.cvtColor(mask2, cv.COLOR_GRAY2BGR)
cv.rectangle(canvas, (x,y), (x+w, y+h), color=(0,0,255), thickness=3)

How to accurately detect the positions of hollow circles (rings) with OpenCV Python?

I would like to determine the center positions of the tips of the syringes in this (video still) image. The tips are nominally round and of known size and quantity.
I am currently putting red ink on the tips to make them easier to detect. It would be nice to not to have to do this but I think without it, detection would be very difficult. Anyone like a challenge?
I started off trying SimpleBlobDetector as it has some nice filtering. One thing I couldn't figure out was how to get SimpleBlobDetector to detect the hollow circles (rings)?
I then tried canny + hough but the circle detection was too unstable, the positions jumped around.
I am currently using findContours + minEnclosingCircle which works OK but still quite unstable.
The mask looks like this. The result. You can see the accuracy is not great:
I briefly looked at RANSAC but I couldn't find a Python example that would detect multiple circles plus the edge detection is tricky.
My current code:
# https://stackoverflow.com/questions/32522989/opencv-better-detection-of-red-color
frame_inv = ~frame0
# Convert BGR to HSV
hsv = cv2.cvtColor(frame_inv, cv2.COLOR_BGR2HSV)
blur = cv2.GaussianBlur(hsv, (5, 5), 0)
# define range of color in HSV
lower_red = np.array([90 - 10, 70, 50])
upper_red = np.array([90 + 10, 255, 255])
# Threshold the HSV image to get only red colors
mask = cv2.inRange(hsv, lower_red, upper_red)
# cv2.imshow('Mask', mask)
kernel = np.ones((5, 5), np.uint8)
dilate = cv2.dilate(mask, kernel)
# cv2.imshow('Dilate', dilate)
contours = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]
tipXY = []
for c in contours:
area = cv2.contourArea(c)
if area > 200:
(x, y), r = cv2.minEnclosingCircle(c)
center = (int(x), int(y))
r = int(r)
shift = 2
factor = 2 ** shift
cv2.circle(frame0, (int(round((x) * factor)), int(round((y) * factor))),
int(round(10 * factor)), (0, 255, 0), 2, shift=shift)
tipXY.append(center)
Any suggestions to improve the position detection accuracy/stability?
Here is a better way to segment red color using the second image as input.
Idea:
Since the red color is prominent, I tried converting to other known color spaces (LAB and YCrCb) and viewed their individual channels. The Cr from YCrCb expressed the red color more prominently. According to this link, Cr channel represents the difference between red and luminance, enabling the color to stand out.
Code:
img = cv2.imread('stacked_rings.jpg')
ycc = cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb)
cr_channel = ycc[:,:,1]
Though the hollow rings can be seen, the pixel intensity range is limited to the range [109 - 194]. Let's stretch the range:
dst = cv2.normalize(cr_channel, dst=None, alpha=0, beta=255,norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)
The circles a more prominent. Hope this pre-processing step helps you.

how can I count the number of grains in this image

I need to return the number of grains present in the image. There are a lot of grains present and all of them are overlapping. can someone help me out with this problem? thanks in advance.
The objective here is to create some segmentation algorithms using OpenCV and Python to segment each grain and count the number of grains in the image.
This is not a complete algorithm; and definitely has an error and probably not for all test cases. But it can be a good place to start. You have to take time; Explore different ideas; Check out different test cases; Learn and explore image processing techniques to achieve better outputs.
import sys
import cv2
import numpy as np
# Load image
im = cv2.imread(sys.path[0]+'/im.png', cv2.IMREAD_GRAYSCALE)
H, W = im.shape[:2]
# Remove noise
im = cv2.medianBlur(im, 9)
im = cv2.GaussianBlur(im, (11, 11), 21)
# Find outlines
im = cv2.adaptiveThreshold(
im, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 61, 2)
top1 = im.copy()
# Fill area with black to find seeds
cv2.floodFill(im, np.zeros((H+2, W+2), np.uint8), (0, 0), 0)
im = cv2.erode(im, np.ones((5, 5)))
top2 = im.copy()
# Find seeds
cnts, _ = cv2.findContours(im, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# Convert GRAY instances to BGR
top1 = cv2.cvtColor(top1, cv2.COLOR_GRAY2BGR)
top2 = cv2.cvtColor(top2, cv2.COLOR_GRAY2BGR)
im = cv2.cvtColor(im, cv2.COLOR_GRAY2BGR)
# Draw circle around detected seeds
c = 0
for cnt in cnts:
x, y, w, h = cv2.boundingRect(cnt)
cv2.circle(im, (x+w//2, y+h//2), max(w, h)//2, (c, 150, 255-c), 3)
c += 5
im=~im
# Print number of seeds
print(len(cnts))
# Save output
cv2.imwrite(sys.path[0]+'/out.png', np.hstack((top1,top2,im)))

Recognizing handwritten digits off a scanned image

I'm trying to read all of the handwritten digits in the scanned image here
I tried looking through pixel-by-pixel using PIL, cropping the sub-images, then feeding them through a neural network, but the regions that were being cropped never quite lined up and led to a lot of inaccuracy.
I've also tried using OpenCV to find all the grey squares, then crop the images and feed them through a neural network, but I couldn't seem to get it to find all or even only miss a few; it would miss ~30% of squares. (I'm not very experienced with OpenCV, so I could be messing something up)
So I'm just looking for a potential idea/solution for this problem, so any suggestions would be appreciated, thanks in advance!
I assume the input image name is "sqaures.jpg"
First of all, import required libraries and load image in both RGB and Gray format:
import cv2
import numpy as np
image = cv2.imread("squares.jpg", 1)
image_gray = cv2.imread("squares.jpg", 0)
Then, we perform a simple operation to clean some noise from the input image using np.where() function:
image_gray = np.where(image_gray > 240, 255, image_gray)
image_gray = np.where(image_gray <= 240, 0, image_gray)
Because we want to grab the whole square regions from the image. We need to blur the image a little bit before performing the adaptive thresholding method:
image_gray = cv2.blur(image_gray, (5, 5))
im_th = cv2.adaptiveThreshold(image_gray, 255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 115, 1)
kernal = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
im_th = cv2.morphologyEx(im_th, cv2.MORPH_OPEN, kernal, iterations=3)
Use contour detection in OpenCV to find all possible regions:
_, contours, _ = cv2.findContours(im_th.copy(), cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
contours = sorted(contours, key=cv2.contourArea, reverse=True)
contours.remove(contours[0]) #remove the biggest contour
Finally, try to find the potential square regions based on the ratio of height and width:
square_rects = []
square_areas = []
for i, cnt in enumerate(contours):
(x, y, w, h) = cv2.boundingRect(cnt)
ar = w / float(h)
if 0.9 < ar < 1.1:
square_rects.append(((x,y), (x+w, y+h)))
square_areas.append(w*h) #store area information
We need to remove anything that is too small from the list by doing the follows:
import statistics
median_size_limit= statistics.median(square_areas) * 0.8
square_rects = [rect for i, rect in enumerate(square_rects)
if square_areas[i] > median_size_limit]
You can visually check the output by drawing all the rectangles on the original image:
for rect in square_rects:
cv2.rectangle(image, rect[0], rect[1], (0,255,0), 2)
cv2.imwrite("_output_image.png", image)
cv2.imshow("image", image)
cv2.waitKey()
You can use "square_rects" to locate all the squares and crop them from the original image.
The following is the preview of the final result.
Cheers.

Improve text area detection (OpenCV, Python)

I am working on a project which ask me to detect text area in an image. This is the result I achieved until now using the code below.
Original Image
Result
The code is the following:
import cv2
import numpy as np
# read and scale down image
img = cv2.pyrDown(cv2.imread('C:\\Users\\Work\\Desktop\\test.png', cv2.IMREAD_UNCHANGED))
# threshold image
ret, threshed_img = cv2.threshold(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY),
127, 255, cv2.THRESH_BINARY)
# find contours and get the external one
image, contours, hier = cv2.findContours(threshed_img, cv2.RETR_TREE,
cv2.CHAIN_APPROX_SIMPLE)
# with each contour, draw boundingRect in green
# a minAreaRect in red and
# a minEnclosingCircle in blue
for c in contours:
# get the bounding rect
x, y, w, h = cv2.boundingRect(c)
# draw a green rectangle to visualize the bounding rect
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), thickness=1, lineType=8, shift=0)
# get the min area rect
#rect = cv2.minAreaRect(c)
#box = cv2.boxPoints(rect)
# convert all coordinates floating point values to int
#box = np.int0(box)
# draw a red 'nghien' rectangle
#cv2.drawContours(img, [box], 0, (0, 0, 255))
# finally, get the min enclosing circle
#(x, y), radius = cv2.minEnclosingCircle(c)
# convert all values to int
#center = (int(x), int(y))
#radius = int(radius)
# and draw the circle in blue
#img = cv2.circle(img, center, radius, (255, 0, 0), 2)
print(len(contours))
cv2.drawContours(img, contours, -1, (255, 255, 0), 1)
cv2.namedWindow('contours', 0)
cv2.imshow('contours', img)
while(cv2.waitKey()!=ord('q')):
continue
cv2.destroyAllWindows()
As you can see, this can do more than I need. Look for commented parts if you need more.
By the way, what I need is to bound every text area in a single rectangle not (near) every char which the script is finding. Filter the single number or letter and to round everything in a single box.
For example, the first sequence in a box, the second in another one and so on.
I searched a bit and I found something about "filter rectangle area". I don't know if it is useful for my purpose.
Tooked a look also at some of the first result on Google but most of them don't work very well. I guess the code need to be tweaked a bit but I am a newbie in OpenCV world.
Solved using the following code.
import cv2
# Load the image
img = cv2.imread('image.png')
# convert to grayscale
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# smooth the image to avoid noises
gray = cv2.medianBlur(gray,5)
# Apply adaptive threshold
thresh = cv2.adaptiveThreshold(gray,255,1,1,11,2)
thresh_color = cv2.cvtColor(thresh,cv2.COLOR_GRAY2BGR)
# apply some dilation and erosion to join the gaps - change iteration to detect more or less area's
thresh = cv2.dilate(thresh,None,iterations = 15)
thresh = cv2.erode(thresh,None,iterations = 15)
# Find the contours
contours,hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
# For each contour, find the bounding rectangle and draw it
for cnt in contours:
x,y,w,h = cv2.boundingRect(cnt)
cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
cv2.rectangle(thresh_color,(x,y),(x+w,y+h),(0,255,0),2)
# Finally show the image
cv2.imshow('img',img)
cv2.imshow('res',thresh_color)
cv2.waitKey(0)
cv2.destroyAllWindows()
Parameters that need to be modified to obtain the result below is numbers of iterations in erode and dilate functions.
Lower values will create more bounding rectangles around (nearly) every digit/character.
Result

Categories

Resources