Hough_line() has poor accuracy with a short line

I’m attempting to measure the angle of a line from a camera, and I’m getting some pretty poor results when the line is near 45 degrees. When the line is between 39 and 52 degrees hough_line() thinks the line is exactly 45. Once the line gets a few degrees below or above that range hough_line() starts measuring the angle to within a couple degrees of true angle again.

Any idea why this is happening or how to better measure the angle of relatively short lines?

Here is some code I wrote to demonstrate the problem. Run it with LINE_LENGTH set to 75 and you’ll see significant error near 45 degrees; run it again with LINE_LENGTH set to 275 and the error near 45 is greatly reduced. I’ve tried rotating the image 20 degrees when the line is near the 45 degree mark and remeasuring the angle but the results are less than great.

import cv2
import numpy as np
from skimage.transform import (hough_line, hough_line_peaks)

blackFrame = np.zeros(shape=[600, 600, 3], dtype=np.uint8)
blackFrame = cv2.inRange(blackFrame, (125,125,125), (255,255,255))
ORIGIN = (300, 300)
LINE_LENGTH = 75 #275
LINE_COLOR = (255,255,255)
WINDOW_TITLE = "Line Angle Measurement Test"


def MeasureAngle(frame):
    hspace, angles, distances = hough_line(frame)
    angle=[]
    for _, a , distances in zip(*hough_line_peaks(hspace, angles, distances)):
        angle.append([_,a])
    mainAngle = (0,0)
    for segment in angle:
        if segment[0] > mainAngle[0]:
            mainAngle = segment
    return mainAngle[1]


if __name__ == '__main__':
    cv2.namedWindow(WINDOW_TITLE, cv2.WINDOW_NORMAL)
    cv2.resizeWindow(WINDOW_TITLE, 600, 600)
    cv2.moveWindow(WINDOW_TITLE, 0, 0)

    for targetDegrees in range(0,90):
        angle = targetDegrees*np.pi/180
        lineEndpoint = (int(ORIGIN[0]+np.sin(angle)*LINE_LENGTH),
                        int(ORIGIN[1]-np.cos(angle)*LINE_LENGTH))
        blackFrame = np.zeros(shape=[600, 600, 3], dtype=np.uint8)
        blackFrame = cv2.inRange(blackFrame, (125,125,125), (255,255,255))
        frameWithLine = cv2.line(blackFrame, ORIGIN, lineEndpoint, LINE_COLOR, 5)
        angleRadians = MeasureAngle(frameWithLine)
        angleDegrees = round(angleRadians*180/np.pi)
        print(targetDegrees, angleDegrees, sep=":")
        cv2.imshow(WINDOW_TITLE, frameWithLine)
        cv2.waitKey(1)

    cv2.destroyAllWindows()

Hough is the wrong approach.

show your data. then we can discuss approaches.

Here’s the data:


I’m only allowed to post one image, but the rest are essentially the same thing but rotated.

just use minAreaRect()

flip width and height, and change the angle by quarters, so that one dimension is longer. then you know the major axis and everything else.

for an accurate centroid, you might wanna calculate “moments” and calculate the centroid from those moments.

1 Like

Good recommendation, minAreaRect() gives acceptable accuracy, thanks!

import cv2
import numpy as np
from skimage.transform import (hough_line, hough_line_peaks)

blackFrame = np.zeros(shape=[600, 600, 3], dtype=np.uint8)
blackFrame = cv2.inRange(blackFrame, (125,125,125), (255,255,255))
ORIGIN = (300, 300)
LINE_LENGTH = 75 #275
LINE_COLOR = (255,255,255)
WINDOW_TITLE = "Line Angle Measurement Test"


def MeasureAngle(image):
    # taken from https://stackoverflow.com/questions/18207181/opencv-python-draw-minarearect-rotatedrect-not-implemented
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

    # Find contours, find rotated rectangle, obtain four verticies, and draw
    cnts = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    cnts = cnts[0] if len(cnts) == 2 else cnts[1]
    rect = cv2.minAreaRect(cnts[1])
    box = np.intp(cv2.boxPoints(rect))
    cv2.drawContours(image, [box], 0, (36,255,12), 2)

    return round(rect[2])


if __name__ == '__main__':
    cv2.namedWindow(WINDOW_TITLE, cv2.WINDOW_NORMAL)
    cv2.resizeWindow(WINDOW_TITLE, 600, 600)
    cv2.moveWindow(WINDOW_TITLE, 0, 0)

    for targetDegrees in range(0,90):
        angle = targetDegrees*np.pi/180
        lineEndpoint = (int(ORIGIN[0]+np.sin(angle)*LINE_LENGTH),
                        int(ORIGIN[1]-np.cos(angle)*LINE_LENGTH))
        blackFrame = np.zeros(shape=[600, 600, 3], dtype=np.uint8)
        frameWithLine = cv2.line(blackFrame, ORIGIN, lineEndpoint, LINE_COLOR, 5)

        angle = MeasureAngle(frameWithLine)
        print(targetDegrees, angle, sep=":")

        cv2.imshow(WINDOW_TITLE, frameWithLine)
        cv2.waitKey(10)

    cv2.destroyAllWindows()