Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Count number of the blues lines on white background in the image

I have 1000 images like thatctf capcha sample

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?

like image 583
Давид Шико Avatar asked Feb 25 '20 09:02

Давид Шико


2 Answers

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

enter image description here

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.

like image 139
Mark Setchell Avatar answered Nov 05 '22 23:11

Mark Setchell


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:

  • Convert image to binary image with white lines on black background.
  • Split intersection points between lines (fill crossing points with black).
  • Find contours in binary image (and remove small contours).
  • Merge contours with close angles, and close edges.

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):
enter image description here

thresh_gray (after splitting):
enter image description here

im:
enter image description here

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.

like image 2
Rotem Avatar answered Nov 06 '22 01:11

Rotem