Python OpenCV 线检测以检测图像中的“X”符号
Posted
技术标签:
【中文标题】Python OpenCV 线检测以检测图像中的“X”符号【英文标题】:Python OpenCV line detection to detect `X` symbol in image 【发布时间】:2020-03-09 06:06:40 【问题描述】:我有一张图像,我需要在其中检测行内的X
符号。
图片:
如上图所示,一行中有一个X
符号。我想知道符号的 X 和 Y 坐标。有没有办法在这张图片中找到这个符号还是很小?
import cv2
import numpy as np
def calculateCenterSpot(results):
startX, endX = results[0][0], results[0][2]
startY, endY = results[0][1], results[0][3]
centerSpotX = (endX - startX) / 2 + startX
centerSpotY = (endY - startY) / 2 + startY
return [centerSpotX, centerSpotY]
img = cv2.imread('crop_1.png')
res2 = img.copy()
cords = [[1278, 704, 1760, 1090]]
center = calculateCenterSpot(cords)
cv2.circle(img, (int(center[0]), int(center[1])), 1, (0,0,255), 30)
cv2.line(img, (int(center[0]), 0), (int(center[0]), img.shape[0]), (0,255,0), 10)
cv2.line(img, (0, int(center[1])), (img.shape[1], int(center[1])), (255,0,0), 10)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# You can either use threshold or Canny edge for HoughLines().
_, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
#edges = cv2.Canny(gray, 50, 150, apertureSize=3)
# Perform HoughLines tranform.
lines = cv2.HoughLines(thresh,0.5,np.pi/180,1000)
for line in lines:
for rho,theta in line:
a = np.cos(theta)
b = np.sin(theta)
x0 = a*rho
y0 = b*rho
x1 = int(x0 + 5000*(-b))
y1 = int(y0 + 5000*(a))
x2 = int(x0 - 5000*(-b))
y2 = int(y0 - 5000*(a))
if x2 == int(center[0]):
cv2.circle(img, (x2,y1), 100, (0,0,255), 30)
if y2 == int(center[1]):
print('hell2o')
# cv2.line(res2,(x1,y1),(x2,y2),(0,0,255),2)
#Display the result.
cv2.imwrite('h_res1.png', img)
cv2.imwrite('h_res3.png', res2)
cv2.imwrite('image.png', img)
我已经尝试过使用HoughLines
,但没有成功。
【问题讨论】:
请分享您的代码,以便我们专门研究它并寻找改进或解决方案。 @SimonFink 我添加了代码 sn-p 您是否有一个图像需要在其中找到此 x 或多个?如果有多个图像要处理,x 符号看起来是否总是相同的,并且具有相同的尺寸? @VictorDeleau 我们有多个。 X 符号始终相同。它具有相同的维度。我希望这些信息对您有所帮助。 然后你可以查看matchTemplate(你可以寻找几个版本的'X'来解释垂直线等) 【参考方案1】:不使用cv2.HoughLines()
,另一种方法是使用template matching。这个想法是在更大的图像中搜索并找到模板图像的位置。为了执行此方法,模板在输入图像上滑动(类似于 2D 卷积),其中执行比较方法以确定像素相似度。这是模板匹配背后的基本思想。不幸的是,这种基本方法存在缺陷,因为它只有在模板图像大小与要在输入图像中找到的所需项目相同时才有效。因此,如果您的模板图像小于要在输入图像中找到的所需区域,则此方法将不起作用。
为了解决这个限制,我们可以使用np.linspace()
动态重新缩放图像以获得更好的模板匹配。在每次迭代中,我们调整输入图像的大小并跟踪比率。我们继续调整大小,直到模板图像大小大于调整大小的图像,同时跟踪最高相关值。更高的相关值意味着更好的匹配。一旦我们遍历不同的尺度,我们找到最大匹配的比率,然后计算边界框的坐标来确定 ROI。
使用这个截屏的模板图像
这是结果
import cv2
import numpy as np
# Resizes a image and maintains aspect ratio
def maintain_aspect_ratio_resize(image, width=None, height=None, inter=cv2.INTER_AREA):
# Grab the image size and initialize dimensions
dim = None
(h, w) = image.shape[:2]
# Return original image if no need to resize
if width is None and height is None:
return image
# We are resizing height if width is none
if width is None:
# Calculate the ratio of the height and construct the dimensions
r = height / float(h)
dim = (int(w * r), height)
# We are resizing width if height is none
else:
# Calculate the ratio of the 0idth and construct the dimensions
r = width / float(w)
dim = (width, int(h * r))
# Return the resized image
return cv2.resize(image, dim, interpolation=inter)
# Load template, convert to grayscale, perform canny edge detection
template = cv2.imread('template.png')
template = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
template = cv2.Canny(template, 50, 200)
(tH, tW) = template.shape[:2]
cv2.imshow("template", template)
# Load original image, convert to grayscale
original_image = cv2.imread('1.png')
gray = cv2.cvtColor(original_image, cv2.COLOR_BGR2GRAY)
found = None
# Dynamically rescale image for better template matching
for scale in np.linspace(0.1, 3.0, 20)[::-1]:
# Resize image to scale and keep track of ratio
resized = maintain_aspect_ratio_resize(gray, width=int(gray.shape[1] * scale))
r = gray.shape[1] / float(resized.shape[1])
# Stop if template image size is larger than resized image
if resized.shape[0] < tH or resized.shape[1] < tW:
break
# Detect edges in resized image and apply template matching
canny = cv2.Canny(resized, 50, 200)
detected = cv2.matchTemplate(canny, template, cv2.TM_CCOEFF)
(_, max_val, _, max_loc) = cv2.minMaxLoc(detected)
# Uncomment this section for visualization
'''
clone = np.dstack([canny, canny, canny])
cv2.rectangle(clone, (max_loc[0], max_loc[1]), (max_loc[0] + tW, max_loc[1] + tH), (0,255,0), 2)
cv2.imshow('visualize', clone)
cv2.waitKey(0)
'''
# Keep track of correlation value
# Higher correlation means better match
if found is None or max_val > found[0]:
found = (max_val, max_loc, r)
# Compute coordinates of bounding box
(_, max_loc, r) = found
(start_x, start_y) = (int(max_loc[0] * r), int(max_loc[1] * r))
(end_x, end_y) = (int((max_loc[0] + tW) * r), int((max_loc[1] + tH) * r))
# Draw bounding box on ROI
cv2.rectangle(original_image, (start_x, start_y), (end_x, end_y), (0,255,0), 2)
cv2.imshow('detected', original_image)
cv2.imwrite('detected.png', original_image)
cv2.waitKey(0)
【讨论】:
当模板图像有时与原始图像略有不同时,是否还有一种方法可以做到这一点?我需要对多个图像执行此操作,但由于 JPEG 压缩,符号每次都略有不同。 我找到了另一种使用多个模板来检测符号的方法,我也在下面发布了我的答案。 如果您的模板由于 JPEG 压缩而略有不同,它应该仍然有效,因为它确定了“最佳”相关值,因此即使您的模板不完全相同,您仍然应该获得一个很好的估计。看看template matching for multiple objects,但这个方法假设模板和要查找的对象大小完全相同。 @NizarB。我不确定您的意思,边界框坐标位于 start_x、start_y、end_x 和 end_y 中。你可以从那里计算质心 不,计算是错误的,要找到其x=start_x +(end_x - start_x)/2
和y=start_y + (end_y - start_y)/2
的质心。画出点来可视化盒子【参考方案2】:
对于多个模板图像,您可以使用 for 循环来处理您拥有的不同模板图像的数量,然后使用阈值来查找多个模板匹配项。
for i in range(templateAmount):
template = cv2.imread('template.png'.format(i),0)
w, h = template.shape[::-1]
res = cv2.matchTemplate(img_gray,template,cv2.TM_CCOEFF_NORMED)
threshold = 0.8
loc = np.where( res >= threshold)
for pt in zip(*loc[::-1]):
cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0,0,255), 2)
【讨论】:
【参考方案3】:如果您有多个图像需要检测此X
符号,并且如果此X
符号始终相同且具有相同的尺寸,您可以在每个图像上运行二维convolution,您正在卷积的 kernel 是您尝试检测的孤立的 X
符号。然后,您可以检查该二维卷积的输出中是否具有最大强度的像素,其归一化坐标 (x/w,y/h)
很可能对应于输入图像中 X
符号的归一化坐标。这是二维卷积的数学表达式:
在 opencv 中,您可以定义 your own kernel(确保仅在背景中保留十字架而不是其他任何内容),然后将其应用于您的图像。
【讨论】:
以上是关于Python OpenCV 线检测以检测图像中的“X”符号的主要内容,如果未能解决你的问题,请参考以下文章