1

So I'm working on this piece of code to extract data from some graphs in images. These images are all scanned from a book. Since we're talking about 100+ images here, I would like to automate the process of course. My first step was to make sure that all images are aligned. Because the pages of the book were scanned by hand, the scans are all slightly shifted or rotated in regards to each other. Luckily there are some dotted lines on the images, which can be used as a reference point to align them. Afterwards I can then divide the image into smaller subimages, by slicing the image on these dotted lines. In that way, all subimages will be equal for all scanned images.

So, first step of course is to detect these dotted lines. My strategy can be described in 4 steps:

  • turn the dotted lines into solid lines, using Morphological Transformation
  • detect all edges, using Canny Edge Detection
  • identify the lines, using HoughLines
  • draw these lines on a mask for further usage

Now there are several problems which may occur. Sometimes HoughLines will detect a wrong line (such as the fold of the next page in the book), but this could potentially be fixed by cropping the image a little on the right side (better solutions are always welcome). The second (and biggest) problem is that HoughLines sometimes tends to identify a single line as multiple lines. I think this has something to do with Canny Edge Detection being too rough or vague about the edges, so that HoughLines actually sees multiple lines. Is there a way I could "smooth" the output from Canny so that HoughLines detects each line exactly once?

In the case of this specific image, the vertical dotted lines in the middle didn't get identified, whereas the fold of the next page in the book did. Furthermore the vertical dotted lines got identified as multiple lines. (left source image, middle edges detected, right lines detected)

enter image description here

# load image
img_large = cv2.imread("image.png")
# resize for ease of use
img_ori = cv2.resize(img_large, None, fx=0.2, fy=0.2, interpolation=cv2.INTER_CUBIC)
# create grayscale
img = cv2.cvtColor(img_ori, cv2.COLOR_BGR2GRAY)
# create mask for image size
mask = np.zeros((img.shape[:2]), dtype=np.uint8)
# do a morphologic close to merge dotted line
kernel = np.ones((8, 8))
res = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
# detect edges for houghlines
edges = cv2.Canny(res, 50, 50)
# detect lines
lines = cv2.HoughLines(edges, 1, np.pi/180, 200)
# draw detected lines
for line in lines:
    rho, theta = line[0]
    a = np.cos(theta)
    b = np.sin(theta)
    x0 = a*rho
    y0 = b*rho
    x1 = int(x0 + 1000*(-b))
    y1 = int(y0 + 1000*a)
    x2 = int(x0 - 1000*(-b))
    y2 = int(y0 - 1000*a)
    cv2.line(mask, (x1, y1), (x2, y2), 255, 2)
    cv2.line(img, (x1, y1), (x2, y2), 127, 2)
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
  • Those are typically due to stair stepping along diagonal lines. The only suggestion I have would be to try a coarser angular resolution and/or do you own filtering and merging of lines that are close and nearly parallel. Or try one of the other two Hough line transform methods that have more options. – fmw42 Aug 02 '19 at 22:22
  • Building on fireant's answer: you can easily experiment with the values using trackbars. You can deal with the small contours by checking the `contourArea` and discard the ones below a threshold. In [this answer](https://stackoverflow.com/a/57261989/10699171) both techniques are used. – J.D. Aug 03 '19 at 07:30

1 Answers1

3

In your script, the pixel-bins and the rotation bins are too fine for the threshold you've set:

lines = cv2.HoughLines(edges, 1, np.pi/180, 200)

So you can tune the threshold parameter (200) to get only one line, or tune the rho (1) and theta (np.pi/180) parameters, or tune all these. You can select a set of image that contain only one horizontal or vertical line from your images. Then do grid search to find the parameters that detect only one line in your set of test images.

fireant
  • 14,080
  • 4
  • 39
  • 48
  • What are some common values to use as these parameters? I have been looking online. But 1 and pi/180 seem very standard, so I can't find much about them. – Frederik Vanclooster Aug 02 '19 at 22:48
  • @FrederikVanclooster most likely the tutorials use those values to make sure most or all lines are detected, even if that means detecting the same line multiple times. There is no typical values for these parameters, that's why they are exposed as parameters without default values by opencv in the first place. That means the user should learn how the algorithm works and decide how to find good values for their application. – fireant Aug 02 '19 at 22:58
  • So there are no boundaries as to how high or low I could go? – Frederik Vanclooster Aug 02 '19 at 23:00
  • If you understand how the method works, you would know the reasonable range for these values, it just takes time for me to explain here. – fireant Aug 02 '19 at 23:22