Reputation: 189626
Is there any way to use opencv to detect lines that are nearly horizontal? I've muddled my way through some of the concepts mentioned in How to detect lines in OpenCV? -- I can get edge detection with canny, but I'm kind of lost on how to use Hough transforms and constrain them to horizontal lines.
I have a bunch of example images here: https://gist.github.com/jason-s/df90e41e29f3ba46e6ccabad4516e916
including:
In particular each image has a pair of horizontal edges that are approximately 1200 pixels long and within 3 degrees of horizontal. (These are formed by corners of photographs I scanned in.)
Any suggestions on what algorithm to use?
Upvotes: 2
Views: 2077
Reputation: 207345
I had an attempt at this using a variation on Fred's (@fmw42) suggestion. I first tried to locate all the white pixels along the image border, by converting to HSV colourspace then finding pixels that are both unsaturated and bright.
I then rotated the resulting image through -5 to +5 degrees in 0.1 degree increments. At each angle of rotation, I ran a SobelY filter looking for horizontal edges. Then I counted the white pixels in each row. Any time I find an orientation that results in a longer horizontal line, I update my best estimate and remember the rotation.
There are many variations possible but this should get you started:
#!/usr/bin/env python3
import cv2
import numpy as np
# Load image
im = cv2.imread('a4e.jpg')
# Find white pixels, i.e. unsaturated and bright
HSV = cv2.cvtColor(im, cv2.COLOR_BGR2HSV)
unsat = HSV[:,:,1] < 50
bright= HSV[:,:,2] > 240
white = ((unsat & bright)*255).astype(np.uint8)
cv2.imwrite('DEBUG-white.png', white)
That looks like this:
# Pad with border so it isn't cropped when rotated, get new dimensions
bw = 100
white = cv2.copyMakeBorder(white, bw, bw, bw, bw, borderType= cv2.BORDER_CONSTANT)
w, h = white.shape[:2]
# Find rotation that results in horizontal row with largest number of white pixels
maxOverall = 0
# SobelY horizontal edge kernel
kernel = np.array((
[-1, -2, -1],
[0, 0, 0],
[1, 2, 1]), dtype="int")
# Rotate image -5 to +5 degrees in 0.1 degree increments
for angle in [x * 0.1 for x in range(-50, 50)]:
M = cv2.getRotationMatrix2D((h/2,w/2),angle,1)
rotated = cv2.warpAffine(white,M,(h,w))
# Output image for debug purposes
cv2.imwrite(f'DEBUG rotated {angle}.jpg',rotated)
# Filter for horizontal edges
filtered = cv2.filter2D(rotated, -1, kernel)
cv2.imwrite(f'DEBUG rotated {angle} filtered.jpg',filtered)
# Check for maximum white pixels in any row
maxThis = np.amax(np.sum(rotated, axis=1))
if maxThis > maxOverall:
print(f'Angle:{angle}: New longest horizontal row has {maxThis} white pixels')
maxOverall = maxThis
The overall process looks like this:
The output looks like this, which means the detected angle is 0.6 degrees:
Angle:-5.0: New longest horizontal row has 34287 white pixels
Angle:-4.9: New longest horizontal row has 34517 white pixels
Angle:-4.800000000000001: New longest horizontal row has 34809 white pixels
Angle:-4.7: New longest horizontal row has 35191 white pixels
Angle:-4.6000000000000005: New longest horizontal row has 35625 white pixels
Angle:-4.5: New longest horizontal row has 36108 white pixels
Angle:-4.4: New longest horizontal row has 36755 white pixels
Angle:-4.3: New longest horizontal row has 37436 white pixels
Angle:-4.2: New longest horizontal row has 38151 white pixels
Angle:-4.1000000000000005: New longest horizontal row has 38876 white pixels
Angle:-4.0: New longest horizontal row has 39634 white pixels
Angle:-3.9000000000000004: New longest horizontal row has 40414 white pixels
Angle:-3.8000000000000003: New longest horizontal row has 41240 white pixels
Angle:-3.7: New longest horizontal row has 42074 white pixels
Angle:-3.6: New longest horizontal row has 42889 white pixels
Angle:-3.5: New longest horizontal row has 43570 white pixels
Angle:-3.4000000000000004: New longest horizontal row has 44252 white pixels
Angle:-3.3000000000000003: New longest horizontal row has 44902 white pixels
Angle:-3.2: New longest horizontal row has 45776 white pixels
Angle:-3.1: New longest horizontal row has 46620 white pixels
Angle:-3.0: New longest horizontal row has 47414 white pixels
Angle:-2.9000000000000004: New longest horizontal row has 48178 white pixels
Angle:-2.8000000000000003: New longest horizontal row has 48705 white pixels
Angle:-2.7: New longest horizontal row has 49225 white pixels
Angle:-2.6: New longest horizontal row has 49962 white pixels
Angle:-2.5: New longest horizontal row has 51501 white pixels
Angle:-2.4000000000000004: New longest horizontal row has 53217 white pixels
Angle:-2.3000000000000003: New longest horizontal row has 54997 white pixels
Angle:-2.2: New longest horizontal row has 56926 white pixels
Angle:-2.1: New longest horizontal row has 59033 white pixels
Angle:-2.0: New longest horizontal row has 61017 white pixels
Angle:-1.9000000000000001: New longest horizontal row has 62538 white pixels
Angle:-1.8: New longest horizontal row has 63370 white pixels
Angle:-1.7000000000000002: New longest horizontal row has 64144 white pixels
Angle:-1.6: New longest horizontal row has 65685 white pixels
Angle:-1.5: New longest horizontal row has 68510 white pixels
Angle:-1.4000000000000001: New longest horizontal row has 72377 white pixels
Angle:-1.3: New longest horizontal row has 76693 white pixels
Angle:-1.2000000000000002: New longest horizontal row has 80932 white pixels
Angle:-1.1: New longest horizontal row has 84101 white pixels
Angle:-1.0: New longest horizontal row has 86557 white pixels
Angle:-0.9: New longest horizontal row has 90499 white pixels
Angle:-0.8: New longest horizontal row has 97179 white pixels
Angle:-0.7000000000000001: New longest horizontal row has 101430 white pixels
Angle:-0.6000000000000001: New longest horizontal row has 105001 white pixels
Angle:-0.5: New longest horizontal row has 112976 white pixels
Angle:-0.4: New longest horizontal row has 117256 white pixels
Angle:-0.30000000000000004: New longest horizontal row has 131478 white pixels
Angle:-0.2: New longest horizontal row has 141468 white pixels
Angle:-0.1: New longest horizontal row has 164588 white pixels
Angle:0.0: New longest horizontal row has 186150 white pixels
Angle:0.1: New longest horizontal row has 206695 white pixels
Angle:0.2: New longest horizontal row has 230821 white pixels
Angle:0.30000000000000004: New longest horizontal row has 249003 white pixels
Angle:0.4: New longest horizontal row has 258888 white pixels
Angle:0.6000000000000001: New longest horizontal row has 264409 white pixels
Upvotes: 2
Reputation: 31
You can find the angle by which the line is parallel to ground by using tan inverse.
for x1,y1,x2,y2 in lines[0]:
angle = math.degrees(math.atan((abs(y2-y1))/abs(x2-x1)))
cv2.line(img2,(x1,y1),(x2,y2),(255,0,0),1)
print(angle)
Then you can filter the lines as told by @Mario. Since above uses abs() to find difference, you will have to filter the angles only using a positive range.
Upvotes: 0
Reputation: 819
Lines detection an filter by line degree of orientation
import cv2
import numpy as np
import math
path='images/lines.png'
image = cv2.imread(path)
dst = cv2.Canny(image, 50, 200, None, 3)
linesP = cv2.HoughLinesP(dst, 1, np.pi / 180, 50, None, 50, 10)
if linesP is not None:
for i in range(0, len(linesP)):
l = linesP[i][0]
#here l contains x1,y1,x2,y2 of your line
#so you can compute the orientation of the line
p1 = np.array([l[0],l[1]])
p2 = np.array([l[2],l[3]])
p0 = np.subtract( p1,p1 ) #not used
p3 = np.subtract( p2,p1 ) #translate p2 by p1
angle_radiants = math.atan2(p3[1],p3[0])
angle_degree = angle_radiants * 180 / math.pi
print("line degree", angle_degree)
if 0 < angle_degree < 15 or 0 > angle_degree > -15 :
cv2.line(image, (l[0], l[1]), (l[2], l[3]), (0,0,255), 1, cv2.LINE_AA)
cv2.imshow("Source", image)
print("Press any key to close")
cv2.waitKey(0)
cv2.destroyAllWindows()
Upvotes: 3