简体   繁体   English

使用 OpenCV 对图像进行盲文点检测

[英]Braille Dot detection on image using OpenCV

For a project I want to detect braille dots on a plate.对于一个项目,我想检测盘子上的盲文点。 I make a picture on which I make my detection thanks to the connectedComponentsWithStats function.多亏了 connectedComponentsWithStats 函数,我制作了一张图片,我可以在上面进行检测。 Despite my attempts I can never get a threshold value where all the dots and only them are detected, I have the same problem if I try to use the circle detection.尽管我进行了尝试,但我永远无法获得检测到所有点且仅检测到它们的阈值,但如果我尝试使用圆形检测,我也会遇到同样的问题。 I'm trying to use template matching on the advice of a teacher but I'm also having problems with my detection since the only factor that influences it is the threshold.我正在尝试根据老师的建议使用模板匹配,但我的检测也有问题,因为影响它的唯一因素是阈值。

import matplotlib.pyplot as plt
 
img1 = cv.imread(r"traitement\prod.png")

plt.figure(figsize=(40,40))
plt.subplot(3,1,1)

gray_img = cv.cvtColor(img1, cv.COLOR_BGR2GRAY)

test = cv.adaptiveThreshold(gray_img, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY_INV, 11, 6)

_, _, boxes, _ = cv.connectedComponentsWithStats(test)

boxes = boxes[1:]
filtered_boxes = []
for x,y,w,h,pixels in boxes:
    if pixels < 1000 and h < 35 and w < 35 and h > 14 and w > 14 and x > 15 and y > 15:
        filtered_boxes.append((x,y,w,h))

for x,y,w,h in filtered_boxes:
    W = int(w)/2
    H = int(h)/2
    #print(w)
    cv.circle(img1,(x+int(W),y+int(H)),2,(0,255,0),20) 

cv.imwrite("gray.png",gray_img)
cv.imwrite("test.png",test)

plt.imshow(test)

plt.subplot(3,1,2)
plt.imshow(img1)


import cv2 as cv
import numpy as np
from imutils.object_detection import non_max_suppression
import matplotlib.pyplot as plt

  
img = cv.imread('traitement/prod.png')
temp_gray = cv.imread('dot.png',0)
  
W, H = temp.shape[:2]
thresh = 0.6

img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)  
match = cv.matchTemplate(image=img_gray, templ=temp_gray, method=cv.TM_CCOEFF_NORMED)

(y_points, x_points) = np.where(match >= thresh)
  
boxes = list()
  

for (x, y) in zip(x_points, y_points):
    
    # update our list of rectangles
    boxes.append((x, y, x + W, y + H))
  
boxes = non_max_suppression(np.array(boxes))
  
# loop over the final bounding boxes
for (x1, y1, x2, y2) in boxes:
    cv.circle(img,(x1+int(W/2),y1+int(H/2)),2,(255,0,0),15) 
  
plt.figure(figsize=(40,40))
plt.subplot(3,1,1)
plt.imshow(img)

Image with adaptive threshold:具有自适应阈值的图像:

具有自适应阈值的图像

Image with template detection:带有模板检测的图像:

带有模板检测的图像

I found a solution that may not be better than your solutions, because I had to overfit few parameters for the given input...我找到了一个可能并不比你的解决方案更好的解决方案,因为我不得不为给定的输入过度拟合几个参数......

The problem is challenging because the input image was taken under non-uniform illumination conditions (the center part is brighter than the top).这个问题具有挑战性,因为输入图像是在非均匀照明条件下拍摄的(中心部分比顶部亮)。 Consider taking a better snapshot...考虑拍摄更好的快照...

Point of thought:思路:
The dots are ordered in rows, and we are not using that information.这些点按行排列,我们没有使用该信息。
We may get better results if we were using the fact that the dots are ordered in rows.如果我们使用点按行排列的事实,我们可能会得到更好的结果。


  • For overcoming the brightness differences we may subtract the median of the surrounding pixels from each pixel (using large filter radius), and compute the absolute difference:为了克服亮度差异,我们可以从每个像素中减去周围像素的中值(使用大的过滤器半径),并计算绝对差:

     bg = cv2.medianBlur(gray, 151) # Background fg = cv2.absdiff(gray, bg) # Foreground (use absdiff because the dost are dark but bright at the center).
  • Apply binary threshold (use THRESH_OTSU for automatic threshold level):应用二进制阈值(使用THRESH_OTSU作为自动阈值级别):

     _, thresh = cv2.threshold(fg, 0, 255, cv2.THRESH_OTSU)
  • The result of thresh is not good enough for finding the dots. thresh的结果不足以找到点。
    We may use the fact that the dots are dark with bright center.我们可以利用这些点是暗的,中心是亮的。
    That fact makes an high edges around and inside the dots.这一事实在点周围和内部形成了高边缘。
    Apply Canny edge detection:应用 Canny 边缘检测:

     edges = cv2.Canny(gray, threshold1=50, threshold2=100)
  • Merge edges with thresh (use binary or):thresh合并edges (使用二进制或):

     thresh = cv2.bitwise_or(thresh, edges)
  • Find connected components and continue (filter the components by area).找到连接的组件并继续(按区域过滤组件)。


Code sample:代码示例:

import numpy as np
import cv2

img1 = cv2.imread('prod.jpg')

gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)  # Convert to grayscale

bg = cv2.medianBlur(gray, 151)  # Compute the background (use a large filter radius for excluding the dots)
fg = cv2.absdiff(gray, bg)  # Compute absolute difference 

_, thresh = cv2.threshold(fg, 0, 255, cv2.THRESH_OTSU)  # Apply binary threshold (THRESH_OTSU applies automatic threshold level)

edges = cv2.Canny(gray, threshold1=50, threshold2=100)  # Apply Canny edge detection.

thresh = cv2.bitwise_or(thresh, edges)  # Merge edges with thresh

_, _, boxes, _ = cv2.connectedComponentsWithStats(thresh)

boxes = boxes[1:]
filtered_boxes = []
for x, y, w, h, pixels in boxes:
    #if pixels < 1000 and h < 35 and w < 35 and h > 14 and w > 14 and x > 15 and y > 15 and pixels > 100:
    if pixels < 1000 and x > 15 and y > 15 and pixels > 200:
        filtered_boxes.append((x, y, w, h))

for x, y, w, h in filtered_boxes:
    W = int(w)/2
    H = int(h)/2
    cv2.circle(img1, (x+int(W), y+int(H)), 2, (0, 255, 0), 20) 


# Show images for testing
cv2.imshow('bg', bg)
cv2.imshow('fg', fg)
cv2.imshow('gray', gray)
cv2.imshow('edges', edges)
cv2.imshow('thresh', thresh)
cv2.imshow('img1', img1)
cv2.waitKey()
cv2.destroyAllWindows()

Result:结果:
在此处输入图像描述

There are few dots that are marked twice.很少有被标记两次的点。
It is relatively simple to merge the overlapping circles into one circle.将重叠的圆圈合并为一个圆圈相对简单。


Intermediate results:中间结果:

thresh (before merging with edges): thresh (在与边缘合并之前):
在此处输入图像描述

edges : edges
在此处输入图像描述

thresh merged with edges : threshedges合并: 在此处输入图像描述


Update:更新:

As Jeru Luke commented we may use non-maximum suppression as done in question.正如 Jeru Luke 所评论的那样,我们可以使用问题中的非最大抑制

Here is a code sample:这是一个代码示例:

import numpy as np
import cv2
from imutils.object_detection import non_max_suppression

img1 = cv2.imread('prod.jpg')

gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)  # Convert to grayscale
bg = cv2.medianBlur(gray, 151)  # Compute the background (use a large filter radius for excluding the dots)
fg = cv2.absdiff(gray, bg)  # Compute absolute difference 
_, thresh = cv2.threshold(fg, 0, 255, cv2.THRESH_OTSU)  # Apply binary threshold (THRESH_OTSU applies automatic threshold level)
edges = cv2.Canny(gray, threshold1=50, threshold2=100)  # Apply Canny edge detection.
thresh = cv2.bitwise_or(thresh, edges)  # Merge edges with thresh
_, _, boxes, _ = cv2.connectedComponentsWithStats(thresh)

boxes = boxes[1:]
filtered_boxes = []
for x, y, w, h, pixels in boxes:
    if pixels < 1000 and x > 15 and y > 15 and pixels > 200:
        filtered_boxes.append((x, y, x+w, y+h))

filtered_boxes = non_max_suppression(np.array(filtered_boxes), overlapThresh=0.2)

for x1, y1, x2, y2 in filtered_boxes:
    cv2.circle(img1, ((x1+x2)//2, (y1+y2)//2), 2, (0, 255, 0), 20)

Result:结果:
在此处输入图像描述

Approach: template matching.方法:模板匹配。

Because the appearance of these dots can't be caught by just thresholding on brightness.因为这些点的出现不能仅仅通过亮度阈值来捕捉。 These dots have both brighter and darker pixels than the flat surface.这些点比平面具有更亮和更暗的像素。 At best you'd get fractured components and given how close these dots are, any morphology operations to fix up the fractured components would run the risk of joining adjacent dots.充其量你会得到破碎的组件,并且考虑到这些点有多接近,任何修复破碎组件的形态学操作都会冒着连接相邻点的风险。

So here I do template matching.所以在这里我做模板匹配。 Works well enough, even though the appearance of these dots changes across the image.效果很好,即使这些点的外观在图像中发生变化。 Brightness is uneven but that's not too much of a problem.亮度不均匀,但这不是什么大问题。 TM_CCOEFF subtracts the mean for both patches before correlating. TM_CCOEFF在关联之前减去两个补丁的平均值。

imutils requires you to come up with bounding boxes for its NMS. imutils要求您为其 NMS 提供边界框。 Instead I'm using a simple and effective NMS formulation for raster data.相反,我对栅格数据使用了简单有效的 NMS 公式。 Comparing floats for equality is okay here because dilate simply replicates the maximum value across the kernel size.在这里比较浮点数是否相等是可以的,因为dilate只是复制了内核大小的最大值。 It uses L-inf distance;它使用 L-inf 距离; for euclidean distance or an approximation, the kernel needs to be round.对于欧几里得距离或近似值,内核需要是圆形的。 One downside: if there are multiple peaks of equal value , this will not remove either of them.一个缺点:如果有多个等值的峰值,这不会删除它们中的任何一个。 To catch adjacent equal peaks, connectedComponents would work instead of findNonZero .为了捕捉相邻的相等峰, connectedComponents将代替findNonZero工作。 For non-adjacent but equal peaks... let's just assume that doesn't happen because the data would make that situation impossible.对于不相邻但相等的峰值......让我们假设这不会发生,因为数据会使这种情况变得不可能。

# Non-maximum suppression for raster data
def non_maximum_suppression(im, radius):
    dilated = cv.dilate(im, kernel=None, iterations=radius)
    return (im == dilated)

# Read image
im = cv.imread("fK2WOX.jpeg", cv.IMREAD_GRAYSCALE)

# Select template
# (x,y,w,h) = (880, 247, 44, 44)
(x,y,w,h) = cv.selectROI("ROI", 255-im, showCrosshair=False, fromCenter=True) # inverted to see the white rectangle...
cv.destroyWindow("ROI")
print((x,y,w,h))
template = im[y:y+h, x:x+w]

模板

# find instances
scores = cv.matchTemplate(im, template, method=cv.TM_CCOEFF)
scores = scores / scores.max()

分数

You see that the template creates additional peaks to the top and bottom of each dot.您会看到模板在每个点的顶部和底部创建了额外的峰。 That can't be helped because that's the appearance of these dots, they consist of a bright dash surrounded by two darker blobs.这无济于事,因为这就是这些点的外观,它们由一个明亮的破折号组成,周围是两个较暗的斑点。 These false peaks can be suppressed with NMS quite easily.这些假峰可以很容易地用 NMS 抑制。

# find peaks of sufficient strength and NMS
threshold = 0.2
nmsradius = 20 # proportional to size of template/dot
nmsmask = (scores >= threshold) & non_maximum_suppression(scores, radius=nmsradius)
coords = cv.findNonZero(nmsmask.astype(np.uint8)).reshape((-1, 2))
coords += (w//2, h//2) # shift coordinates to be center of template

# draw result
canvas = cv.cvtColor(im, cv.COLOR_GRAY2BGR)
for pt in coords:
    cv.circle(canvas, pt, radius=5, color=(0,0,255), thickness=cv.FILLED)

结果

There are no overlapping/split detections.没有重叠/拆分检测。 You'll have to excuse the false detections around the punched holes near the top.您必须原谅顶部附近打孔周围的错误检测。 Just crop those out.把那些剪掉。

As for associating those dots into symbols, that's a new problem.至于将这些点与符号相关联,这是一个新问题。 I'd recommend using nearest-neighbor queries.我建议使用最近邻查询。 When the spacing (in N/S/E/W direction) to other dots has been estimated, you can "probe" in those directions going from each dot and check if there is another dot there... and assemble symbols like that, marking dots as "associated" until there's nothing left.当估计到其他点的间距(在 N/S/E/W 方向上)时,您可以在从每个点开始的那些方向上“探测”并检查那里是否还有另一个点......并组装这样的符号,将点标记为“关联”,直到什么都没有。 You would also want to associate symbols into strings, assuming a certain spacing between symbols.假设符号之间有一定的间距,您还希望将符号关联到字符串中。 same approach there... and that gives you the added information of a baseline for the "text".那里的方法相同……这为您提供了“文本”基线的附加信息。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM