计算图像中白色背景上蓝调线的数量

Posted

技术标签:

【中文标题】计算图像中白色背景上蓝调线的数量【英文标题】:Count number of the blues lines on white background in the image 【发布时间】:2020-06-09 00:12:17 【问题描述】:

我有 1000 张这样的图片

我通过tutorial 尝试了 cv2 库和霍夫线变换,但我不明白这是我的情况吗?我有 1000 张图像,也就是说,我几乎无法手动输入任何数据(如宽度或坐标)。

按照逻辑,我必须找到图像中的每个蓝色像素并检查邻居的像素是否为白色 所以为此我必须知道PNG图像的像素格式。我必须如何读取图像,例如公共文件open (path, 'r') as file_object 或者它必须是带有库的一些特殊方法?

【问题讨论】:

【参考方案1】:

你可以数行数并除以二...

#!/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')

输出

Line ends: 16

请注意,这不是生产质量代码。您应该添加额外的检查,例如线端的总数是偶数,并在边缘周围添加 1 像素宽的黑色边框以防线接触边缘等等。

【讨论】:

除以二的好方法【参考方案2】:

乍一看,问题看起来很简单——转换为二进制图像,使用Hough Line Transform,并计算行数,但它不起作用......

注意:我找到的解决方案是基于寻找和合并轮廓,但使用霍夫变换可能更稳健。相反在合并轮廓的过程中,你可能会发现很多短线,然后根据角度和边缘的接近度将它们合并成长线。

以下解决方案使用以下阶段:

将图像转换为黑底白线的二值图像。 分割线之间的交叉点(用黑色填充交叉点)。 在二值图像中查找轮廓(并删除小轮廓)。 合并具有闭合角度和闭合边缘的轮廓。

这是一个工作代码示例:

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://***.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()

结果:

Number of lines = 8

thresh_gray(拆分前):

thresh_gray(拆分后):

im:

注意: 我知道解决方案并不完美,并且不会在所有 1000 张图像上找到完美的结果。 我认为使用霍夫变换和合并线会有一个更好的变化,这将产生完美的结果。

【讨论】:

但它是关于计算行数的吗?与相交的线如何计算? @YunusTemurlenk 是的,我相信在二进制图像上计算行数有一个已知的解决方案(使用霍夫线变换)。

以上是关于计算图像中白色背景上蓝调线的数量的主要内容,如果未能解决你的问题,请参考以下文章

当计算从睡眠模式或待机模式唤醒时,基于图像的 jframe 变为白色

在matlab中计算边缘像素的数量

如何用matlab计算二值化图中白色区域的像素点个数

使用ImageMagick计算PNG图像中的对象数

计算图像中白色像素的总数会引发错误

计算图像中的对象