Related
I have this fancy image of "Brainbow image showing map of neuronal circuits of the mouse cerebral cortex"
and I want to count the number of green flashes so ran k-means on it with 15 clusters and I isolated the two colors that together do the job, but i am left with many green streaks/ tails and edges of yellow flashes.
I was hoping to find some algo that thresholds by area and select only the actual green flashes or maybe another aproach where I would not face this problem. I have used python k-means from sklearn.cluster
You could use color thresholding to isolate the green flashes. The idea is to convert the image to HSV format and define a lower/upper color threshold range. This will give you a binary mask. From here we can do additional processing by performing morphological opening with an elliptical shaped kernel to remove noise and tails. Finally we can find contours and filter using contour area with a defined threshold area to only keep the larger blobs. Here's the result:
Count: 116
Code
import numpy as np
import cv2
# Color threshold
image = cv2.imread('1.jpg')
original = image.copy()
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
lower = np.array([42, 67, 0])
upper = np.array([69, 255, 255])
mask = cv2.inRange(hsv, lower, upper)
# Perform morphological operations
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
opening = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations=1)
# Find contours and filter using contour area
cnts = cv2.findContours(opening, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
count = 0
for c in cnts:
area = cv2.contourArea(c)
if area < 5:
cv2.drawContours(opening,[c], -1, (0,0,0), -1)
else:
count += 1
result = cv2.bitwise_and(original,original,mask=opening)
print('Count: {}'.format(count))
cv2.imshow('mask', mask)
cv2.imshow('opening', opening)
cv2.imshow('result', result)
cv2.waitKey()
Input hand drawn logical gate
I want to separate the gates from the circuit so that I can run on the isolated Svm images and can detect the type of gates but my problem here is how can I detect or segment the gates in the circuit?
Here's an approach to isolate the logic gates. The idea is to use cv2.HoughLinesP() for line detection. Once we have the detected lines, we effectively remove the lines on a mask to isolate the gates. From here, we perform morphological operations to clean the image and obtain a single contour from each gate. Finally, we perform contour filtering and ROI extraction
Detected lines to remove
Results
Extracted ROIs
Depending on the input image, you may have to change the parameters in cv2.HoughLinesP() and the minimum threshold area. With maxLineGap=50 and area > 1000, here's the results with the other input image
I implemented this approach in Python OpenCV
import cv2
import numpy as np
# Grayscale + Otsu's threshold
image = cv2.imread('1.jpg')
original = image.copy()
mask = np.zeros(image.shape, dtype=np.uint8)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# Find lines
minLineLength = 10
maxLineGap = 150
lines = cv2.HoughLinesP(thresh,1,np.pi/180,100,minLineLength,maxLineGap)
for line in lines:
for x1,y1,x2,y2 in line:
cv2.line(thresh,(x1,y1),(x2,y2),(0,0,0),5)
# Morphological operations to clean image
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9,9))
close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=3)
cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
# Contour filtering and ROI extraction
ROI_number = 0
for c in cnts:
area = cv2.contourArea(c)
if area > 3000:
x,y,w,h = cv2.boundingRect(c)
ROI = original[y:y+h,x:x+w]
cv2.imwrite('ROI_{}.png'.format(ROI_number), ROI)
cv2.rectangle(image, (x, y), (x+w, y+h), (36, 255, 12), 8)
ROI_number += 1
cv2.imwrite('thresh.png', thresh)
cv2.imwrite('close.png', close)
cv2.imwrite('image.png', image)
I am trying to find a way to crop from a circle object (Image A) the largest square that can fit inside it.
Can someone please explain/show me how to find the biggest square fit parameters of the white space inside the circle (Image I) and based on them crop the square in the original image (Image A).
Script:
A = imread('E:/CirTest/Test.jpg');
%imshow(A)
level = graythresh(A);
BW = im2bw(A,level);
%imshow(BW)
I = imfill(BW, 'holes');
imshow(I)
d = imdistline;
[centers, radii, metric] = imfindcircles(A,[1 500]);
imageCrop=imcrop(A, [BoxBottomX BoxBottomY NewX NewY]);
I have a solution for you but it requires a bit of extra work. What I would do first is use imfill but directly on the grayscale image. This way, noisy pixels in uniform areas get inpainted with the same intensities so that thresholding is easier. You can still use graythresh or Otsu's thresholding and do this on the inpainted image.
Here's some code to get you started:
figure; % Open up a new figure
% Read in image and convert to grayscale
A = rgb2gray(imread('http://i.stack.imgur.com/vNECg.jpg'));
subplot(1,3,1); imshow(A);
title('Original Image');
% Find the optimum threshold via Otsu
level = graythresh(A);
% Inpaint noisy areas
I = imfill(A, 'holes');
subplot(1,3,2); imshow(I);
title('Inpainted image');
% Threshold the image
BW = im2bw(I, level);
subplot(1,3,3); imshow(BW);
title('Thresholded Image');
The above code does the three operations that I mentioned, and we see this figure:
Notice that the thresholded image has border pixels that need to be removed so we can concentrate on the circular object. You can use the imclearborder function to remove the border pixels. When we do that:
% Clear off the border pixels and leave only the circular object
BW2 = imclearborder(BW);
figure; imshow(BW2);
... we now get this image:
Unfortunately, there are some noisy pixels, but we can very easily use morphology, specifically the opening operation with a small circular disk structuring element to remove these noisy pixels. Using strel with the appropriate structuring element in addition to imopen should help do the trick:
% Clear out noisy pixels
SE = strel('disk', 3, 0);
out = imopen(BW2, SE);
figure; imshow(out);
We now get:
This mask contains the locations of the circular object we now need to use to crop our original image. The last part is to determine the row and column locations using this mask to locate the top left and bottom right corner of the original image and we thus crop it:
% Find row and column locations of circular object
[row,col] = find(out);
% Find top left and bottom right corners
top_row = min(row);
top_col = min(col);
bottom_row = max(row);
bottom_col = max(col);
% Crop the image
crop = A(top_row:bottom_row, top_col:bottom_col);
% Show the cropped image
figure; imshow(crop);
We now get:
It's not perfect, but it will of course get you started. If you want to copy and paste this in its entirety and run this on your computer, here we are:
figure; % Open up a new figure
% Read in image and convert to grayscale
A = rgb2gray(imread('http://i.stack.imgur.com/vNECg.jpg'));
subplot(2,3,1); imshow(A);
title('Original Image');
% Find the optimum threshold via Otsu
level = graythresh(A);
% Inpaint noisy areas
I = imfill(A, 'holes');
subplot(2,3,2); imshow(I);
title('Inpainted image');
% Threshold the image
BW = im2bw(I, level);
subplot(2,3,3); imshow(BW);
title('Thresholded Image');
% Clear off the border pixels and leave only the circular object
BW2 = imclearborder(BW);
subplot(2,3,4); imshow(BW2);
title('Cleared Border Pixels');
% Clear out noisy pixels
SE = strel('disk', 3, 0);
out = imopen(BW2, SE);
% Show the final mask
subplot(2,3,5); imshow(out);
title('Final Mask');
% Find row and column locations of circular object
[row,col] = find(out);
% Find top left and bottom right corners
top_row = min(row);
top_col = min(col);
bottom_row = max(row);
bottom_col = max(col);
% Crop the image
crop = A(top_row:bottom_row, top_col:bottom_col);
% Show the cropped image
subplot(2,3,6);
imshow(crop);
title('Cropped Image');
... and our final figure is:
You can use bwdist with L_inf distance (aka 'chessboard') to get the axis-aligned distance to the edges of the region, thus concluding the dimensions of the largest bounded box:
bw = imread('http://i.stack.imgur.com/7yCaD.png');
lb = bwlabel(bw);
reg = lb==2; %// pick largest area
d = bwdist(~reg,'chessboard'); %// compute the axis aligned distance from boundary inward
r = max(d(:)); %// find the largest distance to boundary
[cy cx] = find(d==r,1); %// find the location most distant
boundedBox = [cx-r, cy-r, 2*r, 2*r];
And the result is
figure;
imshow(bw,'border','tight');
hold on;
rectangle('Position', boundedBox, 'EdgeColor','r');
Once you have the bounding box, you can use imcrop to crop the original image
imageCrop = imcrop(A, boundedBox);
Alternatively, you can
imageCrop = A(cy + (-r:r-1), cx + (-r:r-1) );
Assuming I have a group of lines segments like the red lines (or green lines) in this picture
I want to know how can I replace them with just one line segment that approximates them best. Or maybe you can advise what to search for since this might be a common problem in statistics.
Problem background: This comes actually from using OpenCV's probabilistic Hough Transform. I want to detect the corners of a piece of paper. When I apply it to an image I get on the edges a group of lines that I want to convert to a single line continuous segment.
One way to do it that I was thinking of, is to take from the line a couple of points and then to use line regression to get the equation of the line. Once I have that I should just cut it to a segment.
Here's a potential solution:
Obtain binary image. Load image, convert to grayscale, and Otsu's threshold
Perform morphological operations. We morph close to combine the contours into a single contour
Find convex hull. We create a blank mask then find the convex hull on the binary image. We draw the lines onto the mask, morph close, then find contours and fill in the outline to get a solid image
Perform linear regression. We find the line of best fit on the binary image then draw this resulting line onto a new mask
Bitwise convex hull and line mask together. We bitwise-and both masks together and draw this resulting contour onto the original image.
Here's a visualization of each step:
Using this screenshotted input image
Binary image -> Morph close
# Load image, grayscale, Otsu's threshold
image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# Morph close
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=3)
Convex hull outline -> convex hull filled
# Create line mask and convex hull mask
line_mask = np.zeros(image.shape, dtype=np.uint8)
convex_mask = np.zeros(image.shape, dtype=np.uint8)
# Find convex hull on the binary image
cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cnt = cnts[0]
hull = cv2.convexHull(cnt,returnPoints = False)
defects = cv2.convexityDefects(cnt,hull)
for i in range(defects.shape[0]):
s,e,f,d = defects[i,0]
start = tuple(cnt[s][0])
end = tuple(cnt[e][0])
far = tuple(cnt[f][0])
cv2.line(convex_mask,start,end,[255,255,255],5)
# Morph close the convex hull mask, find contours, and fill in the outline
convex_mask = cv2.cvtColor(convex_mask, cv2.COLOR_BGR2GRAY)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
convex_mask = cv2.morphologyEx(convex_mask, cv2.MORPH_CLOSE, kernel, iterations=3)
cnts = cv2.findContours(convex_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cv2.fillPoly(convex_mask, cnts, (255,255,255))
Linear regression
# Perform linear regression on the binary image
[vx,vy,x,y] = cv2.fitLine(cnt,cv2.DIST_L2,0,0.01,0.01)
lefty = int((-x*vy/vx) + y)
righty = int(((image.shape[1]-x)*vy/vx)+y)
cv2.line(line_mask,(image.shape[1]-1,righty),(0,lefty),[255,255,255],2)
Bitwise-and filled convex hull and linear regression masks together
# Bitwise-and the line and convex hull masks together
result = cv2.bitwise_and(line_mask, line_mask, mask=convex_mask)
result = cv2.cvtColor(result, cv2.COLOR_BGR2GRAY)
Result
# Find resulting contour and draw onto original image
cnts = cv2.findContours(result, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cv2.drawContours(image, cnts, -1, (200,100,100), 3)
Here's the result with the other input image
Full code
import cv2
import numpy as np
# Load image, grayscale, Otsu's threshold
image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# Morph close
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=3)
# Create line mask and convex hull mask
line_mask = np.zeros(image.shape, dtype=np.uint8)
convex_mask = np.zeros(image.shape, dtype=np.uint8)
# Find convex hull on the binary image
cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cnt = cnts[0]
hull = cv2.convexHull(cnt,returnPoints = False)
defects = cv2.convexityDefects(cnt,hull)
for i in range(defects.shape[0]):
s,e,f,d = defects[i,0]
start = tuple(cnt[s][0])
end = tuple(cnt[e][0])
far = tuple(cnt[f][0])
cv2.line(convex_mask,start,end,[255,255,255],5)
# Morph close the convex hull mask, find contours, and fill in the outline
convex_mask = cv2.cvtColor(convex_mask, cv2.COLOR_BGR2GRAY)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
convex_mask = cv2.morphologyEx(convex_mask, cv2.MORPH_CLOSE, kernel, iterations=3)
cnts = cv2.findContours(convex_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cv2.fillPoly(convex_mask, cnts, (255,255,255))
# Perform linear regression on the binary image
[vx,vy,x,y] = cv2.fitLine(cnt,cv2.DIST_L2,0,0.01,0.01)
lefty = int((-x*vy/vx) + y)
righty = int(((image.shape[1]-x)*vy/vx)+y)
cv2.line(line_mask,(image.shape[1]-1,righty),(0,lefty),[255,255,255],2)
# Bitwise-and the line and convex hull masks together
result = cv2.bitwise_and(line_mask, line_mask, mask=convex_mask)
result = cv2.cvtColor(result, cv2.COLOR_BGR2GRAY)
# Find resulting contour and draw onto original image
cnts = cv2.findContours(result, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cv2.drawContours(image, cnts, -1, (200,100,100), 3)
cv2.imshow('thresh', thresh)
cv2.imshow('close', close)
cv2.imshow('image', image)
cv2.imshow('line_mask', line_mask)
cv2.imshow('convex_mask', convex_mask)
cv2.imshow('result', result)
cv2.waitKey()
This is a great question. The right way to look at this is to integrate the error along each line segment. Instead of a simple term (y[i] - y_hat)^2 (where y_hat is the predicted value from the regression line) you should have integral((y[i](t) - y_hat)^2, t, 0, 1) where y[i](t) is parametrization of the line segment, y[i](t) = t * y[i, 1] + (1 - t)*y[i, 0] (denoting the endpoints of the i-th segment as y[i, 0] and y[i, 1]). I think you'll find that you can compute the integral exactly and therefore you'll get terms for the sum of squared errors which involve just the endpoints. I've left out some details but I think that's enough for you to work out the rest. EDIT: Error terms should be squared; I've adjusted formulas accordingly.
2ND EDIT: I worked out some formulas (using Maxima). For a regression line represented by y = alpha*x + beta, I get as the least squares estimates for alpha and beta:
alpha = (4*('sum(ll[k],k,1,n))*'sum(xx[k][2]*yy[k][2]*ll[k],k,1,n)
+2*('sum(ll[k],k,1,n))*'sum(xx[k][1]*yy[k][2]*ll[k],k,1,n)
+('sum(xx[k][2]*ll[k],k,1,n))*(-3*'sum(yy[k][2]*ll[k],k,1,n)
-3*'sum(yy[k][1]*ll[k],k,1,n))
+('sum(xx[k][1]*ll[k],k,1,n))*(-3*'sum(yy[k][2]*ll[k],k,1,n)
-3*'sum(yy[k][1]*ll[k],k,1,n))
+2*('sum(ll[k],k,1,n))*'sum(yy[k][1]*xx[k][2]*ll[k],k,1,n)
+4*('sum(ll[k],k,1,n))*'sum(xx[k][1]*yy[k][1]*ll[k],k,1,n))
/(4*('sum(ll[k],k,1,n))*'sum(xx[k][2]^2*ll[k],k,1,n)
+4*('sum(ll[k],k,1,n))*'sum(xx[k][1]*xx[k][2]*ll[k],k,1,n)
-3*('sum(xx[k][2]*ll[k],k,1,n))^2
-6*('sum(xx[k][1]*ll[k],k,1,n))*'sum(xx[k][2]*ll[k],k,1,n)
+4*('sum(ll[k],k,1,n))*'sum(xx[k][1]^2*ll[k],k,1,n)
-3*('sum(xx[k][1]*ll[k],k,1,n))^2)
beta = -((2*'sum(xx[k][2]*ll[k],k,1,n)+2*'sum(xx[k][1]*ll[k],k,1,n))
*'sum(xx[k][2]*yy[k][2]*ll[k],k,1,n)
+('sum(xx[k][2]*ll[k],k,1,n)+'sum(xx[k][1]*ll[k],k,1,n))
*'sum(xx[k][1]*yy[k][2]*ll[k],k,1,n)
+('sum(xx[k][2]^2*ll[k],k,1,n))*(-2*'sum(yy[k][2]*ll[k],k,1,n)
-2*'sum(yy[k][1]*ll[k],k,1,n))
+('sum(xx[k][1]*xx[k][2]*ll[k],k,1,n))
*(-2*'sum(yy[k][2]*ll[k],k,1,n)-2*'sum(yy[k][1]*ll[k],k,1,n))
+('sum(xx[k][1]^2*ll[k],k,1,n))*(-2*'sum(yy[k][2]*ll[k],k,1,n)
-2*'sum(yy[k][1]*ll[k],k,1,n))
+('sum(xx[k][2]*ll[k],k,1,n)+'sum(xx[k][1]*ll[k],k,1,n))
*'sum(yy[k][1]*xx[k][2]*ll[k],k,1,n)
+2*('sum(xx[k][1]*yy[k][1]*ll[k],k,1,n))*'sum(xx[k][2]*ll[k],k,1,n)
+2*('sum(xx[k][1]*ll[k],k,1,n))*'sum(xx[k][1]*yy[k][1]*ll[k],k,1,n))
/(4*('sum(ll[k],k,1,n))*'sum(xx[k][2]^2*ll[k],k,1,n)
+4*('sum(ll[k],k,1,n))*'sum(xx[k][1]*xx[k][2]*ll[k],k,1,n)
-3*('sum(xx[k][2]*ll[k],k,1,n))^2
-6*('sum(xx[k][1]*ll[k],k,1,n))*'sum(xx[k][2]*ll[k],k,1,n)
+4*('sum(ll[k],k,1,n))*'sum(xx[k][1]^2*ll[k],k,1,n)
-3*('sum(xx[k][1]*ll[k],k,1,n))^2)
where xx and yy are lists of pairs of values, one pair for each line segment. That is, xx[k] are the x-coordinates for the endpoints of the k-th segment, yy[k] are the y-coordinates for the endpoints of the k-th segment, and ll[k] is the length sqrt((xx[k][2] - xx[k][1])^2 + (yy[k][2] - yy[k][1])^2) of the k-th segment.
Here is my program to derive those formulas. Probably there are other reasonable ways to set up this problem which yield similar but different formulas.
y_hat[k](l) := alpha * x[k](l) + beta;
x[k](l) := (1 - l/ll[k]) * xx[k][1] + l/ll[k] * xx[k][2];
y[k](l) := (1 - l/ll[k]) * yy[k][1] + l/ll[k] * yy[k][2];
e[k]:= y[k](l) - y_hat[k](l);
foo : sum (integrate (e[k]^2, l, 0, ll[k]), k, 1, n);
declare (nounify (sum), linear);
[da, db] : [diff (foo, alpha), diff (foo, beta)];
map (expand, %);
bar : solve (%, [alpha, beta]);
Here are some example data and the result I get. I postponed defining dx, dy, and ll because, since they are all constant terms, I didn't want them to be expanded in the solutions for alpha and beta.
dx[k] := xx[k][2] - xx[k][1];
dy[k] := yy[k][2] - yy[k][1];
ll[k] := sqrt (dx[k]^2 + dy[k]^2);
xx : [[1,2],[1.5,3.5],[5.5,10.5]]$
yy : [[1,2.2],[1.5,3.3],[5,12]]$
''bar, nouns, n=length(xx);
=> [[alpha = 1.133149837130799, beta = - 0.4809409869515073]]
I am trying to find horizontal and vertical lines from an image which came from a "document". The documents are scanned pages from contracts and so the lines look like what you would see in a table or in a contract block.
I have been trying OpenCV for the job. The Hough transform implementation in OpenCV seemed useful for the job, but I could not find any combination of parameters that would allow it to cleanly find the vertical and horizontal lines. I tried with and without edge detection. No luck. If anyone has done anything similar I'm interested in knowing how.
See here an image of my before and after experimentation with HoughP in OpenCV. It's the best I could do, http://dl.dropbox.com/u/3787481/Untitled%201.png
So now I'm wondering whether there is another kind of transform I could use which would allow me to reliably find horizontal and vertical lines (and preferably dashed lines too).
I know this problem is solvable because I have Nuance and ABBYY OCR tools which can both reliably extract horizontal and vertical lines and return me the bounding box of the lines.
Thanks!
Patrick.
Have you seen a code sample from HoughLinesP function documentation?
I think you can use it as starting point for your algorithm. To pick horizontal an vertical lines you just need to filter out other lines by line angle.
UPDATE:
As I see you need to find not the lines but horizontal an vertical edges on the page. For this task you need to combine several processing steps to get good results.
For your image I'm able to get good results by combining Canny edge detection with HoughLinesP. Here is my code (I've used python, but I think you see the idea):
img = cv2.imread("C:/temp/1.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 80, 120)
lines = cv2.HoughLinesP(edges, 1, math.pi/2, 2, None, 30, 1);
for line in lines[0]:
pt1 = (line[0],line[1])
pt2 = (line[2],line[3])
cv2.line(img, pt1, pt2, (0,0,255), 3)
cv2.imwrite("C:/temp/2.png", img)
Result looks like:
Here's a complete OpenCV solution using morphological operations.
Obtain binary image
Create horizontal kernel and detect horizontal lines
Create vertical kernel and detect vertical lines
Here's a visualization of the process. Using this input image:
Binary image
import cv2
# Load image, convert to grayscale, Otsu's threshold
image = cv2.imread('1.png')
result = image.copy()
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
Detected horizontal lines highlighted in green
# Detect horizontal lines
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (40,1))
detect_horizontal = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=2)
cnts = cv2.findContours(detect_horizontal, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
cv2.drawContours(result, [c], -1, (36,255,12), 2)
Detected vertical lines highlighted in green
# Detect vertical lines
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,10))
detect_vertical = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel, iterations=2)
cnts = cv2.findContours(detect_vertical, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
cv2.drawContours(result, [c], -1, (36,255,12), 2)
Result
Here's the output using another input image
Input -> Binary -> Detected Horizontal -> Detected Vertical -> Result
Note: Depending on the image, you may have to modify the kernel size. For instance to capture longer horizontal lines, it may be necessary to increase the horizontal kernel from (40, 1) to say (80, 1). If you wanted to detect thicker horizontal lines, then you could increase the width of the kernel to say (80, 2). In addition, you could increase the number of iterations when performing cv2.morphologyEx(). Similarly, you could modify the vertical kernels to detect more or less vertical lines. There is a trade-off when increasing or decreasing the kernel size as you may capture more or less of the lines. Again, it all varies depending on the input image
Full code for completeness
import cv2
# Load image, convert to grayscale, Otsu's threshold
image = cv2.imread('1.png')
result = image.copy()
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# Detect horizontal lines
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (40,1))
detect_horizontal = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=2)
cnts = cv2.findContours(detect_horizontal, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
cv2.drawContours(result, [c], -1, (36,255,12), 2)
# Detect vertical lines
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,10))
detect_vertical = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel, iterations=2)
cnts = cv2.findContours(detect_vertical, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
cv2.drawContours(result, [c], -1, (36,255,12), 2)
cv2.imshow('result', result)
cv2.waitKey()
If you just want the "lines" and not the "line segments", I would avoid using Canny, Hough, FindContours or any other such function in case you want more speed in your code. If your images are not rotated and what you want to find is always vertical or horizontal, I would just use cv::Sobel (one for vertical, and another for horizontal) and create accumulation arrays for columns and rows. Then you can search for maxima in such accumulations or profiles, for instance by setting a threshold, and you will know the row or column in which there is a vertical or horizontal edge lines.
You might consider leaving the Hough line detection since this method looks for "global" lines, not necessarily line segments. I recently implemented an application that identified "parallelograms" - essentially squares that might be rotated and perspective fore-shortened due to viewing angle. You might consider something similar. My pipeline was:
Convert from RGB to grayscale (cvCvtColor)
Smooth (cvSmooth)
Threshold (cvThreshold)
Detect edges (cvCanny)
Find contours (cvFindContours)
Approximate contours with linear features (cvApproxPoly)
In your application, the resulting contour list will likely be large (depending upon the "aggressiveness" of smoothing and the feature enhancement of the Canny edge detector. You can prune this list by a variety of parameters: number of points returned from the contour finder, area of the contour (cvContourArea), etc. From my experience, I would expect that "valid" lines in your application would have well-defined area and vertex count properties. Additionally, you can filter out contours based on distance between end-points, angle defined by the line connecting end-points, etc.
Depending upon how much CPU "time" you have, you can always pair the Hough algorithm with an algorithm like that above to robustly identify horizontal and vertical lines.
DonĀ“t convert the RGB to grayscale. Sometimes, different colors in RGB can be merged to the same grayscale value, so it could miss some contours. You should analyze each of the RGB channels separately.
Here is an approach that accumulates arrays for columns and rows. Then one can search for maxima in such accumulations (above a certain threshold) and deduce in which row or column there is a vertical or horizontal line.
If you want to quickly test the code, use the following Google Colab Notebook.
Google Colab Notebook
import numpy as np
import cv2
import scipy
from scipy.signal import find_peaks
from matplotlib import pyplot as plt
url = "https://i.stack.imgur.com/S00ap.png"
!wget $url -q -O input.jpg
fileName = 'input.jpg'
img = cv2.imread(fileName)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
tmp = img.copy()
gray = cv2.cvtColor(tmp, cv2.COLOR_BGR2GRAY)
blurred = cv2.bilateralFilter(gray, 11, 61, 39)
edges = cv2.Canny(blurred, 0, 255)
v_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,3))
h_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7,1))
v_morphed = cv2.morphologyEx(edges, cv2.MORPH_OPEN, v_kernel, iterations=2)
v_morphed = cv2.dilate(v_morphed, None)
h_morphed = cv2.morphologyEx(edges, cv2.MORPH_OPEN, h_kernel, iterations=2)
h_morphed = cv2.dilate(h_morphed, None)
v_acc = cv2.reduce(v_morphed, 0, cv2.REDUCE_SUM, dtype=cv2.CV_32S)
h_acc = cv2.reduce(h_morphed, 1, cv2.REDUCE_SUM, dtype=cv2.CV_32S)
def smooth(y, box_pts):
box = np.ones(box_pts)/box_pts
y_smooth = np.convolve(y, box, mode='same')
return y_smooth
s_v_acc = smooth(v_acc[0,:],9)
s_h_acc = smooth(h_acc[:,0],9)
v_peaks, v_props = find_peaks(s_v_acc, 0.70*np.max(np.max(s_v_acc)))
h_peaks, h_props = find_peaks(s_h_acc, 0.70*np.max(np.max(s_h_acc)))
for peak_index in v_peaks:
cv2.line(tmp, (peak_index, 0), (peak_index, img.shape[0]), (255, 0, 0),2)
for peak_index in h_peaks:
cv2.line(tmp, (0, peak_index), (img.shape[1], peak_index), (0, 0, 255),2)
v_height = v_props['peak_heights'] #list of the heights of the peaks
h_height = h_props['peak_heights'] #list of the heights of the peaks
def align_axis_x(ax, ax_target):
"""Make x-axis of `ax` aligned with `ax_target` in figure"""
posn_old, posn_target = ax.get_position(), ax_target.get_position()
ax.set_position([posn_target.x0, posn_old.y0, posn_target.width, posn_old.height])
def align_axis_y(ax, ax_target):
"""Make y-axis of `ax` aligned with `ax_target` in figure"""
posn_old, posn_target = ax.get_position(), ax_target.get_position()
ax.set_position([posn_old.x0, posn_target.y0, posn_old.width, posn_target.height])
fig = plt.figure(constrained_layout=False, figsize=(24,16))
spec = fig.add_gridspec(ncols=4, nrows=2, height_ratios=[1, 1])
ax1 = fig.add_subplot(spec[0,0])
ax1.imshow(tmp)
ax2 = fig.add_subplot(spec[0, 1])
ax2.imshow(v_morphed)
ax3 = fig.add_subplot(spec[0, 2])
ax3.imshow(h_morphed)
ax4 = fig.add_subplot(spec[0, 3], sharey=ax3)
ax4.plot(h_acc[:,0], np.arange(len(h_acc[:,0])), 'y', marker="o", ms=1, mfc="k", mec="k")
ax4.plot(s_h_acc, np.arange(len(s_h_acc)), 'r', lw=1)
ax4.plot(h_height, h_peaks, "x", lw="5")
ax5 = fig.add_subplot(spec[1, 1], sharex=ax2)
ax5.plot(np.arange(len(v_acc[0,:])), v_acc[0,:], 'y', marker="o", ms=1, mfc="k", mec="k")
ax5.plot(np.arange(len(s_v_acc)), s_v_acc, 'r', lw=2)
ax5.plot(v_peaks, v_height, "x", lw="5")
plt.tight_layout()
align_axis_y(ax4,ax3)
align_axis_x(ax5,ax2)