让我们将图像加载为灰度,将其阈值设置为黑白并反转颜色,稍微腐蚀一下,使用 Canny 边缘检测,然后使用霍夫线检测(主要遵循 this tutorial):
import cv2
import numpy as np
import math
import random
src = cv2.imread("s34I0.png", cv2.IMREAD_GRAYSCALE)
thr, bw = cv2.threshold(src, 128, 255, cv2.THRESH_BINARY_INV)
eroded = cv2.erode(bw, np.ones((5, 5), np.uint8))
canny = cv2.Canny(src, 50, 200, None, 3)
lines = cv2.HoughLines(canny, 1, np.pi / 180, 150, None, 0, 0)
lines = [list(x[0]) for x in lines]
def draw_line(img, line, color, thickness):
rho, the = line
a = math.cos(the)
b = math.sin(the)
x0 = a * rho
y0 = b * rho
pt1 = (int(x0 + 1000 * (-b)), int(y0 + 1000 * (a)))
pt2 = (int(x0 - 1000 * (-b)), int(y0 - 1000 * (a)))
cv2.line(img, pt1, pt2, color, thickness, cv2.LINE_AA)
很遗憾,我们为每个直线段检测到两条平行线。让我们用它们的中线替换每一对这样接近的平行线:
lines_ = []
def midline(line1, line2):
return [(x + y) / 2 for x, y in zip(line1, line2)]
used = []
for l1 in lines:
if l1 in used: continue
for l2 in lines:
if l2 in used: continue
if l1 is l2: continue
if (abs(l1[0] - l2[0]) < 20) and (abs(l1[1] - l2[1]) < 1):
lines_.append(midline(l1, l2))
used.append(l1)
used.append(l2)
continue
lines = lines_
现在,让我们为直线创建二进制掩码。对于每条直线,我们创建一个临时的二进制黑色图像(所有像素值都为零),然后在其上绘制一条粗白线(与原始图像上的线条相同或略粗)。然后我们对原始阈值图像和临时线条图像进行逻辑与运算,得到两者的共同像素——即线条的二进制掩码。
line_masks = []
for i, line in enumerate(lines):
line_img = np.zeros(bw.shape)
draw_line(line_img, line, 255, 10) # 10 pixel thick white line
common = np.logical_and((bw != 0), (line_img != 0))
line_masks.append(common)
从原始黑白图像中删除被遮罩的像素,因此只应保留弧线。不幸的是,一些垃圾仍然存在,因为原始图像中的线条并不完美。为了摆脱这种情况,我们可以将霍夫线画得更粗(比如 15 或 20 像素而不是 10 像素),但是它们占用了太多的弧像素。相反,我们可以对生成的图像进行一点腐蚀扩张,以去除垃圾:
for lm in line_masks:
bw[lm] = 0
bw = cv2.erode(bw, np.ones((5, 5), np.uint8))
bw = cv2.dilate(bw, np.ones((5, 5), np.uint8))
让我们为弧创建二进制掩码。 OpenCV 中没有检测弧的功能,但对于这种情况,我们可以使用连接组件的检测:
arc_masks = []
num, labels = cv2.connectedComponents(bw)
for i in range(1, num):
arc_masks.append(labels == i)
现在我们有了蒙版,让我们通过在原始图像上绘制来可视化它们。线条将具有随机的绿色阴影,弧线 - 蓝色:
line_colors = [(0, random.randint(127, 256), 0) for _ in line_masks]
arc_colors = [(random.randint(127, 256), 0, 0) for _ in arc_masks]
dst = cv2.imread("s34I0.png")
for color, mask in zip(line_colors, line_masks):
dst[mask] = color
for color, mask in zip(arc_colors, arc_masks):
dst[mask] = color