How to separate monochromatic objects of different sizes in opencv - python

I want to separate a noiseless 1-bit (black and white) image with white circles based on the concave part of the outline.
Please refer to the picture below.
This is the white object to separate:
The target result is:
Here is my implementation with the watershed algorithm:
The above result is not what I want.
If the size of the separated objects is similar, my algorithm is fine, but if the size difference is large, a problem occurs as shown in the picture above.
I would like to implement an opencv algorithm that can segment a region like the second picture.
However, the input photo is not necessarily a perfect circle.
It can be oval like the picture below:
Or it can be squished:
However, I would like to separate it based on the concave part of the outline anyway.
I think it can be implemented by using the distanceTransform function well, but I'm not sure how to approach it.
Please let me know which way to refer.
Thank you.

Here is an algorithm which should give you a good start.
Compute all contours.
For each contour compute the convexity defects. If there is no defect the contour is an isolated circle and you can segment it out.
After you handled all the isolated circles, you can work out the remaining contours by counting the convexity defects: the number of circles N for each contour is the number of convexity defects divided by 2.
Use a clustering algorithm (https://scikit-learn.org/stable/modules/generated/sklearn.mixture.GaussianMixture.html should do well given the shapes you have) and cluster the "white" points using N as the number of clusters to be found.

If you want to find the minimal openings, you can use a medial axis based approach.
Pseudo code:
compute contours of bitmap
compute medial-axis of bitmap
for each point on medial-axis:
get minimal distance d from medial axis algorithm
for each local minimum of distance d:
get two points on bitmap contours with minimal distance that are at least d apart from each other
use these points for deviding line
If you need a working implementation in python, please let me know. I would use skimage lib. For other languages you might have to implement medial-axis on your own. But that shouldn't be a big deal.

Related

How to create steerable Edge Detection filters using Python or discard edges that don't conform to desired angle

I know how to do basic Canny edge detection using OpenCV. However I need to discard all edges that do not fall within 15 degrees of a predetermined angle.
Any help would be greatly appreciated.
Its an old question but here is the process you should use.
1]Start by filter your source image (back-ground subtract/color/etc)
2]Apply a generic Edge detector or a steerable filter or (if you want to get some really good result & are doing it for research purposes look for Phase Strectch Transform Algorithm
3]Save those line in a vector/whatever
4]Create a circle drawing algorithm (here is the main idea)
Your circle drawing algorithm (CDA further) will take every point returned by your edge filter.
For each point it will build circles of a variable diameter [Dmin;Dmax] based on the max/min distance you can accept for two points be considered on the same line.
If no next-pixel are present in the circle octant corresponding to your angle, simply dismiss it.
Once you have your lines that match your angle you can sort them by length to dismiss line probably due to noise.
You should also note that there is other methods, this method as some good aspect:
1- Its robust against noise & low quality images/video
2- Its CUDA compliant (i.e. easy to push in parallel processing).
3-Its fast and roughly more accurate than most basic line detectors.

How to crop biggest rectangle out of an image

I have a few images of pages on a table. I would like to crop the pages out of the image. Generally, the page will be the biggest rectangle in the image, however, all four sides of the rectangle might not be visible in some cases.
I am doing the following but not getting desired results:
import cv2
import numpy as np
im = cv2.imread('images/img5.jpg')
gray=cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(gray,127,255,0)
_,contours,_ = cv2.findContours(thresh,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
areas = [cv2.contourArea(c) for c in contours]
max_index = np.argmax(areas)
cnt=contours[max_index]
x,y,w,h = cv2.boundingRect(cnt)
cv2.rectangle(im,(x,y),(x+w,y+h),(0,255,0),2)
cv2.imshow("Show",im)
cv2.imwrite("images/img5_rect.jpg", im)
cv2.waitKey(0)
Below are a few examples:
1st Example: I can find the rectangle in this image , however, would like if the remaining part of the wood can be cropped out as well.
2nd Example: Not finding the correct dimensions of the rectangle in this image.
3rd Example: Not able to find the correct dimensions in this image either.
4th Example: Same with this as well.
As I have previously done something similar, I have experienced with hough transforms, but they were much harder to get right for my case than using contours. I have the following suggestions to help you get started:
Generally paper (edges, at least) is white, so you may have better luck by going to a colorspace like YUV which better separates luminosity:
image_yuv = cv2.cvtColor(image,cv2.COLOR_BGR2YUV)
image_y = np.zeros(image_yuv.shape[0:2],np.uint8)
image_y[:,:] = image_yuv[:,:,0]
The text on the paper is a problem. Use a blurring effect, to (hopefully) remove these high frequency noises. You may also use morphological operations like dilation as well.
image_blurred = cv2.GaussianBlur(image_y,(3,3),0)
You may try to apply a canny edge-detector, rather than a simple threshold. Not necessarily, but may help you:
edges = cv2.Canny(image_blurred,100,300,apertureSize = 3)
Then find the contours. In my case I only used the extreme outer contours. You may use CHAIN_APPROX_SIMPLE flag to compress the contour
contours,hierarchy = cv2.findContours(edges,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
Now you should have a bunch of contours. Time to find the right ones. For each contour cnt, first find the convex hull, then use approaxPolyDP to simplify the contour as much as possible.
hull = cv2.convexHull(cnt)
simplified_cnt = cv2.approxPolyDP(hull,0.001*cv2.arcLength(hull,True),True)
Now we should use this simplified contour to find the enclosing quadrilateral. You may experiment with lots of rules you come up with. The simplest method is picking the four longest longest segments of the contour, and then create the enclosing quadrilateral by intersecting these four lines. Based on your case, you can find these lines based on the contrast the line makes, the angle they make and similar things.
Now you have a bunch of quadrilaterals. You can now perform a two step method to find your required quadrilateral. First you remove those ones that are probably wrong. For example one angle of the quadrilateral is more than 175 degrees. Then you can pick the one with the biggest area as the final result. You can see the orange contour as one of the results I got at this point:
The final step after finding (hopefully) the right quadrilateral, is transforming back to a rectangle. For this you can use findHomography to come up with a transformation matrix.
(H,mask) = cv2.findHomography(cnt.astype('single'),np.array([[[0., 0.]],[[2150., 0.]],[[2150., 2800.]],[[0.,2800.]]],dtype=np.single))
The numbers assume projecting to letter paper. You may come up with better and more clever numbers to use. You also need to reorder the contour points to match the order of coordinates of the letter paper. Then you call warpPerspective to create the final image:
final_image = cv2.warpPerspective(image,H,(2150, 2800))
This warping should result in something like the following (from my results before):
I hope this helps you to find an appropriate approach in your case.
That's a pretty complicated task which cannot be solved by simply searching contours. The Economist cover for example only shows 1 edge of the magazine which splits the image in half. How should your computer know which one is the magazine and which one is the table? So you have to add much more intelligence to your program.
You might look for lines in your image. Hough transform for example. Then find sets of more or less parallel or orthogonal lines, lines of a certain length...
Find prints by checking for typical print colours or colours that you usually don't find on a table. Search for high contrast frequencies as created by printed texts...
Imagine how you as a human recognize a printed paper...
All in all this is a too broad question for StackOverflow. Try to break it down into smaller sub-problems, try to solve them and if you hit a wall, come back here.

How can I detect a grid of filled circles?

Given an image of a connect-4 board I'd like to recognize and output the board's state (a 6 by 7 matrix). The first approach I tried was based on finding the circles and then looking for a grid pattern in their centroids.
This is the open-cv function I'm using:
circles = cv2.HoughCircles(bw_im,
cv2.cv.CV_HOUGH_GRADIENT,
dp=DP,
minDist=MIN_DIST,
minRadius=MIN_RADIUS,
maxRadius=MAX_RADIUS)
I add non-maximum suppression, but the results are not great.
Is there a better way than dealing with Hough circles directly, perhaps there is some sort of filled circularity morphological operation that I don't know of.
Here's an example input image:
You can assume that the input image has been cropped and has similar margins as above (I have another piece of code that takes care of this).
If Hough isn't a requirement, Id suggest implementing a ray-casting algorithm as described here: https://en.wikipedia.org/wiki/Point_in_polygon
The general steps are:
Create a mask for the red circles
Run ray-casting on x columns spaced y apart to determine # and position of reds
Repeat steps 1 & 2 for yellow
Since you're working in RGB, the color contrast should be enough to give you good results.
Assuming your grid will maintain its position the easiest way would be to setup a fixed region of interest for every slot and measure their hue values every time you change something.

Smoothing the edge in a large scale

I have images of eyes and eyebrows like the following one.
And I want it to be processed to be more smooth on the edges like the following one, which is drawn by hand.
I've tried with morphology opening, but with different size of the SE, it either fills the unexpected area or leaves with some rough edges. Here's the result with circle SE of size 9 and 7 respectively.
Another idea is to calculate the Convex Hull of the eyebrow and fill the color. But since the eyebrow is usually bending and the Convex Hull will become something like the following image, which is also not very ideal.
Or should I make every pixel on the edge to be a vertex of a polygon and then smooth the polygon? Any specific idea here?
Any idea how can I get the result in the second image?
I'm using Python OpenCV. Code or general idea are both welcomed. Thanks!

Determining "bottleneck" image regions using scipy

I'm doing image processing and mathematical morphology using scipy.ndimage and really enjoy it. Our work involves simulating charges moving through various films, and we're trying to use image analysis tools to estimate why different morphologies work better than others.
I quickly was able to use ndimage.label and distance_transform_edt to find the connected components and get sizing on them. I also implemented a breadth-first search to find minimal paths between the components and the edges, which represent electrodes.
Now, I'd like to determine "bottleneck" or "narrow channel" regions. I'm not even sure if I'm searching for the right keywords, since my expertise isn't really in image processing. I've given two examples below.. I want to find features like the red circles and count them and determine their size distributions. (Consider that charges will move more easily through wider bottlenecks.)
The problem is that I can't label these, since they're not independent components. The distance transforms give me small numbers at the edges.. I want something like the smallest distance through these bottlenecks.
Any advice where to look or general strategies?
One could use the medial axis transform to calculate the radius of a ball fit at each point in the bacl set to obtain the nooks in the image. In the following example we use the watershed of the distance function weighted by the distance function itself to obtain contours which separate minimas(the white components in the image). This thus gives a path weighted by the maximum value of the distance function separating 2 white components. I have done this in matlab but i think its easy to replicate the same in Scikit image tool box.
Image1:
Filling the holes since they aren't paths:
Distance function: (heat map)
Watershed of distance function (paths):
Watershed weighted by Distance function (final paths):
Image 2:
Distance function:
Watershed of distance function (paths):
Watershed weighted by Distance function (final paths):
Thus as demonstrated we have calculated technical a skeleton by zone of influence(SKIZ) using the watershed of the distance function(cityblock used here). One has to also note that the holes on the borders are not filled since the imfill ignores holes on borders. If its to be filled one can add a frame around so that one can use imfill to fill these later.

Categories

Resources