Jason S
Jason S

Reputation: 189626

Detecting near-horizontal lines using Image processing

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

Answers (3)

Mark Setchell
Mark Setchell

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:

enter image description here

# 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:

enter image description here

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

Demnofocus
Demnofocus

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

Mario Abbruscato
Mario Abbruscato

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()

enter image description here

Upvotes: 3

Related Questions