I have 1000 images like that
I tried the cv2 library and Hough Line Transform by this tutorial, but I'm don't understand it is my case? I have 1000 images, i.e. I almost don't have the possibility to enter any data (like width or coordinates) manually.
By the logic, I must find every blue pixel in the image and check, if the neighbors' pixels are white
So for it I must know pixels format of a PNG image. How I must to read the image, like common file open (path, 'r') as file_object
or it must be some special method with a library?
You could count the line ends and divide by two...
#!/usr/bin/env python3
import numpy as np
from PIL import Image
from scipy.ndimage import generic_filter
# Line ends filter
def lineEnds(P):
global ends
# Central pixel and one other must be 255 for line end
if (P[4]==255) and np.sum(P)==510:
ends += 1
return 255
return 0
# Global count of line ends
ends = 0
# Open image and make into Numpy array
im = Image.open('lines.png').convert('L')
im = np.array(im)
# Invert and threshold for white lines on black
im = 255 - im
im[im>0] = 255
# Save result, just for debug
Image.fromarray(im).save('intermediate.png')
# Find line ends
result = generic_filter(im, lineEnds, (3, 3))
print(f'Line ends: {ends}')
# Save result, just for debug
Image.fromarray(result).save('result.png')
Output
Line ends: 16
Note this is not production quality code. You should add extra checks, such as the total number of line-ends being even, and adding a 1 pixel wide black border around the edge in case a line touches the edge and so on.
At first glance the problem looks simple - convert to binary image, use Hough Line Transform, and count the lines, but it's not working...
Note:
The solution I found is based on finding and merging contours, but using Hough Transform may be more robust.
Instead of merging contours, you may find many short lines, and merge them into long lines based on close angle and edges proximity.
The solution below uses the following stages:
Here is a working code sample:
import cv2
import numpy as np
def box2line(box):
"""Convert rotated rectangle box into two array of two points that defines a line"""
b = box.copy()
for i in range(2):
p0 = b[0]
dif0 = (b[1:, 0] - p0[0])**2 + (b[1:, 1] - p0[1])**2
min_idx = np.argmin(dif0, 0)
b = np.delete(b, min_idx+1, 0)
return b
def minlinesdist(line, line2):
"""Finds minimum distance between any two edges of two lines"""
a0 = line[0, :]
a1 = line[1, :]
b0 = line2[0, :]
b1 = line2[1, :]
d00 = np.linalg.norm(a0 - b0)
d01 = np.linalg.norm(a0 - b1)
d10 = np.linalg.norm(a1 - b0)
d11 = np.linalg.norm(a1 - b1)
min_dist = np.min((d00, d01, d10, d11))
return min_dist
def get_rect_box_line_and_angle(c):
"""Return minAreaRect, boxPoints, line and angle of contour"""
rect = cv2.minAreaRect(c)
box = cv2.boxPoints(rect)
line = box2line(box)
angle = rect[2]
return rect, box, line, angle
(cv_major_ver, cv_minor_ver, cv_subminor_ver) = (cv2.__version__).split('.') # Get version of OpenCV
im = cv2.imread('BlueLines.png') # Read input image
# Convert image to binary image with white lines on black background
################################################################################
gray = im[:, :, 1] # Get only the green color channel (the blue lines should be black).
# Apply threshold
ret, thresh_gray = cv2.threshold(gray, 10, 255, cv2.THRESH_BINARY)
# Invert polarity
thresh_gray = 255 - thresh_gray
################################################################################
# Split intersection points between lines (fill crossing points with black).
################################################################################
thresh_float = thresh_gray.astype(float) / 255 # Convert to float with range [0, 1]
thresh_float = cv2.filter2D(thresh_float, -1, np.ones((3, 3))) # Filter with ones 5x5
# Find pixels with "many" neighbors
thresh_intersect = np.zeros_like(thresh_gray)
thresh_intersect[(thresh_float > 3)] = 255; # Image of intersection points only.
thresh_gray[(thresh_float > 3)] = 0;
################################################################################
# Find contours in thresh_gray, and remove small contours.
################################################################################
if int(cv_major_ver) < 4:
_, contours, _ = cv2.findContours(thresh_gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
else:
contours, _ = cv2.findContours(thresh_gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# Remove small contours, because their angle is not well defined
fcontours = []
for i in range(len(contours)):
c = contours[i]
if c.shape[0] > 6: # Why 6?
fcontours.append(c)
contours = fcontours
# Starting value.
n_lines = len(contours)
################################################################################
# Merge contours with close angles, and close edges
# Loop decreases n_lines when two lines are merged.
# Note: The solution is kind of "brute force" solution, and can be better.
################################################################################
# https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_contours/py_contour_features/py_contour_features.html
# Fitting a Line
rows,cols = im.shape[:2]
for i in range(len(contours)):
c = contours[i]
rect, box, line, angle = get_rect_box_line_and_angle(c)
for j in range(i+1, len(contours)):
c2 = contours[j]
rect2 = cv2.minAreaRect(c2)
box2 = cv2.boxPoints(rect2)
line2 = box2line(box2)
angle2 = rect2[2]
angle_diff = (angle - angle2 + 720) % 180 # Angle difference in degrees (force it to be positive number in range [0, 180].
angle_diff = np.minimum(angle_diff, 180 - angle_diff)
min_dist = minlinesdist(line, line2) # Minimum distance between any two edges of line and line2
if (angle_diff < 3) and (min_dist < 20):
color = (int((i+3)*100 % 255),int((i+3)*50 % 255), int((i+3)*70 % 255))
# https://stackoverflow.com/questions/22801545/opencv-merge-contours-together
# Merge contours together
tmp = np.vstack((c, c2))
c = cv2.convexHull(tmp)
# Draw merged contour (for testing)
im = cv2.drawContours(im, [c], 0, color, 2)
# Replace contour with merged one.
contours[j] = c
n_lines -= 1 # Subtract lines counter
break
################################################################################
print('Number of lines = {}'.format(n_lines))
# Display result (for testing):
cv2.imshow('thresh_gray', thresh_gray)
cv2.imshow('im', im)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:
Number of lines = 8
thresh_gray
(before splitting):
thresh_gray
(after splitting):
im
:
Note:
I know the solution is not perfect, and not going to find perfect results on all of your 1000 images.
I think there is a better change that using Hough Transform and merging lines is going to give perfect results.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With