Opencv trace lines from dotted image - python

I have following image: named 'Normalised.png'. I am trying to draw solid lines from dotted lines.
I have tried approaches like hough line transform:
import cv2
import numpy as np
img = cv2.imread('Normalised.png')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray,50,150,apertureSize = 3)
minLineLength = 100
maxLineGap = 10
lines = cv2.HoughLinesP(edges,1,np.pi/180,100,minLineLength,maxLineGap)
for x1,y1,x2,y2 in lines[0]:
cv2.line(img,(x1,y1),(x2,y2),(0,255,0),2)
cv2.imwrite('houghlines5.jpg',img)
But it appears that the code fails on 'edges' as no 'edges' are detected.
Input image
Expected Output Image
How do I achieve this output?

By default, HoughLinesP works for straight lines. However, you can detect curves by using cv2.HOUGH_PROBABILISTIC as follows:
img = cv.imread("Dilate.png")
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
edges = cv.Canny(gray, 150, 200, apertureSize=3)
cv.imwrite("Canny.png", edges)
element = cv.getStructuringElement(cv.MORPH_RECT, (5, 3), (-1, -1))
dilated = cv.dilate(edges, element)
cv.imwrite("Eroded.png", dilated)
minLineLength = 200
maxLineGap = 5
lines = cv.HoughLinesP(dilated, cv.HOUGH_PROBABILISTIC, np.pi/180, 150, minLineLength,
maxLineGap)
for x in range(0, len(lines)):
for x1, y1, x2, y2 in lines[x]:
pts = np.array([[x1, y1], [x2, y2]], np.int32)
cv.polylines(img, [pts], True, (0, 255, 0))
cv.imwrite('dilate_final.png', img)
Note how the lines are being drawn.
Result is not exactly what you want but close and requires you to tune the parameters which I will leave for you. Hope it helps!

A possible scheme (though the whole task seems desperate):
choose a small number of directions (say 5) uniformly spread;
for every direction,
smooth in that direction (f.i. with a very elongated Gaussian) or
erode in that direction (with a linear structuring element), or both, to better connect the dots,
binarize with threshold such that the dots come in contact,
apply morphological thickening to get thin black lines.
combine all maps so obtained (max operation),
cleanup.

Related

Finding text between two lines using Python OpenCV

I want to identify and highlight / crop the text between two lines using Python (cv2).
One line is a wavy line at the top, and the second line somewhere in the page. This line can appear at any height on the page, ranging from just after 1 line to just before the last line.
An example,
I believe I need to use HoughLinesP() somehow with proper parameters for this.
I've tried some examples involving a combination of erode + dilate + HoughLinesP.
e.g.
img = cv2.imread(image)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
kernel_size = 5
blur_gray = cv2.GaussianBlur(gray, (kernel_size, kernel_size), 0)
# erode / dilate
erode_kernel_param = (5, 200) # (5, 50)
dilate_kernel_param = (5, 5) # (5, 75)
img_erode = cv2.erode(blur_gray, np.ones(erode_kernel_param))
img_dilate = cv2.dilate(img_erode, np.ones(dilate_kernel_param))
# %% Second, process edge detection use Canny.
low_threshold = 50
high_threshold = 150
edges = cv2.Canny(img_dilate, low_threshold, high_threshold)
# %% Then, use HoughLinesP to get the lines.
# Adjust the parameters for better performance.
rho = 1 # distance resolution in pixels of the Hough grid
theta = np.pi / 180 # angular resolution in radians of the Hough grid
threshold = 15 # min number of votes (intersections in Hough grid cell)
min_line_length = 600 # min number of pixels making up a line
max_line_gap = 20 # max gap in pixels between connectable line segments
line_image = np.copy(img) * 0 # creating a blank to draw lines on
# %% Run Hough on edge detected image
# Output "lines" is an array containing endpoints of detected line segments
lines = cv2.HoughLinesP(edges, rho, theta, threshold, np.array([]),
min_line_length, max_line_gap)
if lines is not None:
for line in lines:
for x1, y1, x2, y2 in line:
cv2.line(line_image, (x1, y1), (x2, y2), (255, 0, 0), 5)
# %% Draw the lines on the image
lines_edges = cv2.addWeighted(img, 0.8, line_image, 1, 0)
However, in many cases the lines dont get identified propery.
Some examples of errors being,
Too many lines being identified (ones in the text as well)
Lines not being identified completely
Lines not being identified at all
Am I on the right track? Do I just need to hit the correct combination of parameters for this purpose? or is there a simpler way / trick which will let me reliably crop the text between these two lines?
In case it's relevant, I need to do this for ~450 pages.
Here's the link to the book, in case someone wants to examine more examples of pages.
https://archive.org/details/in.ernet.dli.2015.553713/page/n13/mode/2up
Thank you.
Solution
I've made minor modifications to the answer by Ari (Thank you), and made the code a bit more comprehensible for my own sake, here's my code.
The core idea is,
Find contours and their bounding rectangles.
Two "widest" contours would represent the two lines.
Thereafter, take the lower side of the top rectangle and upper side of the bottom rectangle to bound the area (text) we are interested in.
for image in images:
base_img = cv2.imread(image)
height, width, channels = base_img.shape
img = cv2.cvtColor(base_img, cv2.COLOR_BGR2GRAY)
ret, img = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
img = cv2.bitwise_not(img)
contours, hierarchy = cv2.findContours(
img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE
)
# Get rectangle bounding contour
rects = [cv2.boundingRect(contour) for contour in contours]
# Rectangle is (x, y, w, h)
# Top-Left point of the image is (0, 0), rightwards X, downwards Y
# Sort the contours bigger width first
rects.sort(key=lambda r: r[2], reverse=True)
# Get the 2 "widest" rectangles
line_rects = rects[:2]
line_rects.sort(key=lambda r: r[1])
# If at least two rectangles (contours) were found
if len(line_rects) >= 2:
top_x, top_y, top_w, top_h = line_rects[0]
bot_x, bot_y, bot_w, bot_h = line_rects[1]
# Cropping the img
# Crop between bottom y of the upper rectangle (i.e. top_y + top_h)
# and the top y of lower rectangle (i.e. bot_y)
crop_img = base_img[top_y+top_h:bot_y]
# Highlight the area by drawing the rectangle
# For full width, 0 and width can be used, while
# For exact width (erroneous) top_x and bot_x + bot_w can be used
rect_img = cv2.rectangle(
base_img,
pt1=(0, top_y + top_h),
pt2=(width, bot_y),
color=(0, 255, 0),
thickness=2
)
cv2.imwrite(image.replace('.jpg', '.rect.jpg'), rect_img)
cv2.imwrite(image.replace('.jpg', '.crop.jpg'), crop_img)
else:
print(f"Insufficient contours in {image}")
You can find the Contours, and then take the two with the biggest width.
base_img = cv2.imread('a.png')
img = cv2.cvtColor(base_img, cv2.COLOR_BGR2GRAY)
ret, img = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
img = cv2.bitwise_not(img)
cnts, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# sort the cnts bigger width first
cnts.sort(key=lambda c: cv2.boundingRect(c)[2], reverse=True)
# get the 2 big lines
lines = [cv2.boundingRect(cnts[0]), cv2.boundingRect(cnts[1])]
# higher line first
lines.sort(key=lambda c: c[1])
# croping the img
crop_img = base_img[lines[0][1]:lines[1][1]]

Detecting the biggest rectangle with thicker lines on grid paper with OpenCV

We are making a game where you can use grid paper to make your own level.
The first step was detecting the boundaries of the level, so I made a thicker black border around the level for the edges.
This is the image: https://imgur.com/a/Wx3p7Ci
I first edited the image to gray, applied GaussianBlur and adaptiveThreshold.
This is the result: https://imgur.com/a/dp0G4my
Normal Houghlines isn't really an option I think as it detects so many lines and loses the thicker lines.
findContour I haven't got a usable outcomes as there are so many shapes in a gridpaper.
So I ended up with HoughLinesP which resulted in this:https://imgur.com/a/yvIpYNy
So it detects one part of the rectangle correctly and that is it. I would have another idea which would rely on colors which isn't ideal (people would need a specific color) but I'm at a loss on what a good approach for this would be.
Here is my code I currently have for HoughLinesP
import cv2 as cv
import numpy as np
# from openCV.main import contours
image = cv.imread("grey-grid-blackborder.jpg")
cv.imshow("Image", image)
gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
cv.imshow("Gray", gray)
blur = cv.GaussianBlur(gray, (5, 5), 0)
cv.imshow("blur", blur)
thresh = cv.adaptiveThreshold(blur, 255, 1, 1, 11, 2)
cv.imshow("thresh", thresh)
grid = image
minLineLength = 1
maxLineGap = 0
# lines = cv.HoughLinesP(thresh, 1, np.pi / 180, 100, minLineLength, maxLineGap)
lines = cv.HoughLinesP(thresh, 1, np.pi / 180, 100, minLineLength=minLineLength, maxLineGap=maxLineGap)
for x1, y1, x2, y2 in lines[0]:
cv.line(grid, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv.imshow("grid", grid)
cv.waitKey(0)

Corner detection with Hughlines transformation

I need to detect corner of a paper on given image. It will always be a cropped part of whole picture containing only one of the corners. My idea was to transform image by bluring and Canny edge detection to get outlines and then aplying Houghlines to get coordinates of corner.
However i get some problem to actualy detect anything consistently and precisly by Hough lines and I'm running out of ideas what can be the cause here.
I've tried tresholding instead of Canny, but it's not gonna work due to many variations in applicable images. I've downcaled whole image to make it easier to see just edges of paper, but still no improvement. Increasing line tresholds make lines from paper content diapear, but at the same time edge lines disapear from time to time
Input
Edges
Results
Code to reproduce
import cv2
import numpy as np
img = cv2.imread('inv_0001-01.1_0_corner.jpg')
resized = cv2.resize(img, (250,250), interpolation = cv2.INTER_AREA)
gray = cv2.cvtColor(resized, cv2.COLOR_BGR2GRAY)
kernel_size = 5
blur_gray = cv2.GaussianBlur(gray,(kernel_size, kernel_size),0)
edges = cv2.Canny(blur_gray,50,150,apertureSize = 3)
cv2.imshow('edges', edges)
cv2.waitKey()
min_line_length = 50
max_line_gap = 20
lines = cv2.HoughLinesP(edges, 1, np.pi / 180, 5, np.array([]), min_line_length, max_line_gap)
for line in lines:
for x1,y1,x2,y2 in line:
cv2.line(resized,(x1,y1),(x2,y2),(255,0,0),5)
cv2.imshow('hough', resized)
cv2.waitKey()
My go-to result would be coordinate of paper corner in given image, but in this post I'm rather looking for some help in understanding how to use Houglines for such tasks
This answer explains how to find the corner. Finding the corner requires a two part solution. First, the image needs to be segmented in to two regions: paper and background. Second, you can look for corners in the segmented image.
After you find the edges, floodfill the image to segment the paper from the background (this is the floodfill image):
mask = np.zeros((h+2, w+2), np.uint8)
# Floodfill from point (0, 0)
cv2.floodFill(edges, mask, (0,0), 123);
Now that you have segmented the image, get rid of the text on the paper using a mask (this is the image titled 'Masking'):
bg = np.zeros_like(edges)
bg[edges == 123] = 255
After you get the mask, appl the canny edge filter again to get the out line of the paper (HoughLines needs an outline not a mask...this is the 'Edges after masking' image):
bg = cv2.blur(bg, (3,3))
edges = cv2.Canny(bg,50,150,apertureSize = 3)
Now you can run your HoughLines algorithm on the cleaner image. I used a different HoughLines algorithm than you did, but yours should work too. Here is the full code that I used:
import cv2
import numpy as np
import matplotlib.pyplot as plt
# Create a multi plot
f, axarr = plt.subplots(2,3, sharex=True)
img = cv2.imread('/home/stephen/Desktop/IRcCAWL.png')
resized = cv2.resize(img, (250,250), interpolation = cv2.INTER_AREA)
# Show source image
axarr[0,0].imshow(resized)
gray = cv2.cvtColor(resized, cv2.COLOR_BGR2GRAY)
kernel_size = 5
blur_gray = cv2.GaussianBlur(gray,(kernel_size, kernel_size),0)
edges = cv2.Canny(blur_gray,50,150,apertureSize = 3)
# Show first edges image
axarr[0,1].imshow(edges)
h, w = edges.shape[:2]
mask = np.zeros((h+2, w+2), np.uint8)
# Floodfill from point (0, 0)
cv2.floodFill(edges, mask, (0,0), 123);
# Show the flood fill image
axarr[0,2].imshow(edges)
floodfill = edges.copy()
bg = np.zeros_like(edges)
bg[edges == 123] = 255
# Show the masked image
axarr[1,0].imshow(bg)
bg = cv2.blur(bg, (3,3))
edges = cv2.Canny(bg,50,150,apertureSize = 3)
# Show the edges after masking
axarr[1,1].imshow(edges)
min_line_length = 50
max_line_gap = 20
def intersection(line1, line2):
"""Finds the intersection of two lines given in Hesse normal form.
Returns closest integer pixel locations.
See https://stackoverflow.com/a/383527/5087436
"""
rho1, theta1 = line1[0]
rho2, theta2 = line2[0]
A = np.array([
[np.cos(theta1), np.sin(theta1)],
[np.cos(theta2), np.sin(theta2)]
])
b = np.array([[rho1], [rho2]])
x0, y0 = np.linalg.solve(A, b)
x0, y0 = int(np.round(x0)), int(np.round(y0))
return [[x0, y0]]
import math
lines = cv2.HoughLines(edges, 1, np.pi / 180, 100, None, 0, 0)
# Draw the lines
if lines is not None:
for i in range(0, len(lines)):
rho = lines[i][0][0]
theta = lines[i][0][1]
a = math.cos(theta)
b = math.sin(theta)
x0 = a * rho
y0 = b * rho
pt1 = (int(x0 + 1000*(-b)), int(y0 + 1000*(a)))
pt2 = (int(x0 - 1000*(-b)), int(y0 - 1000*(a)))
cv2.line(resized, pt1, pt2, (123,234,123), 2, cv2.LINE_AA)
xy = tuple(intersection(lines[0], lines[1])[0])
resized = cv2.circle(resized, xy, 5, 255, 2)
# Show the image with the corner
axarr[1,2].imshow(resized)
# Add titles
axarr[0,0].set_title('Source Image')
axarr[0,1].set_title('Edges')
axarr[0,2].set_title('Floodfill')
axarr[1,0].set_title('Masking')
axarr[1,1].set_title('Edges after masking')
axarr[1,2].set_title('Hough Lines')
# Clean up
axarr[0,0].axis('off')
axarr[0,1].axis('off')
axarr[1,0].axis('off')
axarr[1,1].axis('off')
axarr[1,2].axis('off')
axarr[0,2].axis('off')
plt.show()

Calculating center of an object in an image

I was reading this post to calculate the center of an image using OpenCV which uses Moments. But I am trying to calculate the center of an object I detected using HoughLinesP. Is there a way with OpenCV I could do this?
Here is the image for which I am trying to calculate the centers.
The line segments were found and the output image looks like:
import cv2
import numpy as np
import math
img = cv2.imread("./images/octa.jpg")
b,g,r = cv2.split(img)
smoothed = cv2.GaussianBlur(g, (3,3), 0)
edges = cv2.Canny(smoothed, 15, 60, apertureSize = 3)
lines = cv2.HoughLinesP(edges,1,np.pi/180,35, 30, 20)
print("length of lines detected ", lines.shape)
for line in lines:
for x1,y1,x2,y2 in line:
cv2.line(img,(x1,y1),(x2,y2),(255,0,0),2)
print("x1,y1", x1,",",y1, " --- ", "x2,y2", x2,",",y2)
cv2.imshow('detected',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Using the coordinates how could I calculate the center of this image? How could I use Moments here?
One constraint I have is that I cannot use Contour methods included with OpenCV.
The following code was used with cv2 version of 3.3.1.
I closely followed the opencv docs and it worked fine.
import cv2
img = cv2.imread("octa.jpg", 0)
ret,thresh = cv2.threshold(img,100,255,0)
im2, contours, hierachy = cv2.findContours(thresh, 1, 2)
cnt = contours[0]
M = cv2.moments(cnt)
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
im2 = cv2.cvtColor(im2, cv2.COLOR_GRAY2RGB)
cv2.polylines(im2, cnt, True, (0, 0, 255), 2)
cv2.circle(im2, (cx, cy), 5, (0, 0, 255), 1)
cv2.imshow("res", im2)
Two notes:
you need to add the argument 0 to imread otherwise the contour finding would not work
I set the threshold just a little bit lower, so only the contours of the octagon were found
Result:
If you use a different version of cv2, you can just change the docs to your version; the documentation is really good.
You also may want to blur your image a bit or do some other preprocessing, but in this case, there was no need for it.
EDIT Without contour:
I took the helpful comments from this post and tinkered around a bit. This does not use contours. It finds lines and uses them to find the center
import cv2
import numpy as np
mg = cv2.imread('octa.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
kernel_size = 5
blur_gray = cv2.GaussianBlur(gray,(kernel_size, kernel_size),0)
ret,thresh = cv2.threshold(blur_gray,100,255,0)
low_threshold = 50
high_threshold = 150
edges = cv2.Canny(thresh, low_threshold, high_threshold)
rho = 1 # distance resolution in pixels of the Hough grid
theta = np.pi / 180 # angular resolution in radians of the Hough grid
threshold = 15 # minimum number of votes (intersections in Hough grid cell)
min_line_length = 50 # minimum number of pixels making up a line
max_line_gap = 50 # maximum gap in pixels between connectable line segments
line_image = np.copy(img) * 0 # creating a blank to draw lines on
# Run Hough on edge detected image
# Output "lines" is an array containing endpoints of detected line segments
lines = cv2.HoughLinesP(edges, rho, theta, threshold, np.array([]),
min_line_length, max_line_gap)
for line in lines:
for x1,y1,x2,y2 in line:
cv2.line(line_image,(x1,y1),(x2,y2),(255,0,0),2)
lines_edges = cv2.addWeighted(img, 0.5, line_image, 1, 0)
line_image_gray = cv2.cvtColor(line_image, cv2.COLOR_RGB2GRAY)
M = cv2.moments(line_image_gray)
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
cv2.circle(lines_edges, (cx, cy), 5, (0, 0, 255), 1)
cv2.imshow("res", lines_edges)
Result:
Found lines are drawn in blue; the center in red

Strange lines detected by openCV python

I followed a tutorial of python openCV, and was trying to use HoughLinesP() to detect lines, here is the code:
imgLoc = '...\lines.jpg'
img = cv2.imread(imgLoc)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 50, 100)
minLineLength = 20; maxLineGap = 5
lines = cv2.HoughLinesP(edges, 1, np.pi/180, 100, \
minLineLength, maxLineGap)
for [[x1, y1, x2, y2]] in lines:
cv2.line(img, (x1, y1), (x2, y2), (0, 0, 255), 2)
cv2.imshow('line', img)
cv2.waitKey(); cv2.destroyAllWindows()
And here's what I get:
apparently very strange. there's so many lines in the sky which I never expected, and I thought there would be more vertical lines on the building, but there's not.
The tutorial doesn't give pictures demonstrating how the result should be, so I have no idea if that's normal or not.
So, Is there any problem in my code that lead to that wired image? If it does, can I make some change to let it detect more vertical line?
==========Update_1============
I followed the comment's suggestion, now more line can be detected:
#minLineLength = 20; maxLineGap = 5
lines = cv2.HoughLinesP(edges, 1, np.pi/180, 100, \
minLineLength = 10, maxLineGap = 5)
But still vertical lines are lacked.
And the Canny() result:
in the result of Canny() there ARE vertical edges, why would they disappeared after HoughLinesP()? (Or that's just visual error?)
==========Update_2============
I added a blur and the value of minLineLength:
gray = cv2.GaussianBlur(gray, (5, 5), 0)
...
lines = cv2.HoughLinesP(edges, 1, np.pi/180, 100, \
minLineLength = 50, maxLineGap = 5)
The result is clearer, but still not much vertical lines...
And I started to wonder where does these slashes comes from
It might be worth starting with some smoothing before performing Canny edge detection.
I suggest higher values for minLineLength (and maxLineGap, though this should not be very large). That should get rid of smaller lines and connect vertical line segments where detected. If that still does not bring out the vertical lines, we might have to look in to the threshold parameter.

Categories

Resources