如何从图像中删除或清除轮廓?

3

我正在处理车牌数据,我的工作是对其应用一系列过滤器,例如:

  1. 灰度
  2. 模糊
  3. 阈值处理
  4. 二值化

问题在于,在进行操作时,有些像这张图片边缘的轮廓,如何清除它们?或者将其变成黑色(掩膜)?我使用了这段代码,但有时会出错。

# invert image and detect contours
inverted = cv2.bitwise_not(image_binary_and_dilated)
contours, hierarchy = cv2.findContours(inverted,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)

# get the biggest contour
biggest_index = -1
biggest_area = -1
i = 0
for c in contours:
    area = cv2.contourArea(c)
    if area > biggest_area:
        biggest_area = area
        biggest_index = i
    i = i+1

print("biggest area: " + str(biggest_area) + " index: " + str(biggest_index))

cv2.drawContours(image_binary_and_dilated, contours, biggest_index, [0,0,255])
center, size, angle = cv2.minAreaRect(contours[biggest_index])

rot_mat = cv2.getRotationMatrix2D(center, angle, 1.)

#cv2.warpPerspective()
print(size)
dst = cv2.warpAffine(inverted, rot_mat, (int(size[0]), int(size[1])))

mask = dst * 0
x1 = max([int(center[0] - size[0] / 2)+1, 0])
y1 = max([int(center[1] - size[1] / 2)+1, 0])
x2 = int(center[0] + size[0] / 2)-1
y2 = int(center[1] + size[1] / 2)-1

point1 = (x1, y1)
point2 = (x2, y2)
print(point1)
print(point2)

cv2.rectangle(dst, point1, point2, [0,0,0])
cv2.rectangle(mask, point1, point2, [255,255,255], cv2.FILLED)

masked = cv2.bitwise_and(dst, mask)

#cv2_imshow(imgg)
cv2_imshow(dst)
cv2_imshow(masked)
#cv2_imshow(mask)

一些结果: enter image description here enter image description here enter image description here 原始结果为:
1. 好结果 1 2. 好结果 2 3. 好结果 3 4. 好结果 4 5. 坏结果 1 6. 坏结果 2 二进制板是:
1. 图像 1 2. 图像 2 3. 图像 3 4. 图像 4 5. 图像 5 - 坏结果 1 6. 图像 6 - 坏结果 2 我该如何修复此代码?只是希望避免不良结果或改进它。

好问题! :) 你能澄清一下 image_binary_and_dilated(第二行 - 我假设是初始图像,但它已经被膨胀了吗?)和 gray(在 warpAffine 中)是什么吗?也许简单解释一下为什么要使用旋转矩阵和 warpAffine?这将使代码更易于理解,谢谢。 - freerafiki
谢谢!你是对的,image_binary_dilated就是已经膨胀的二进制图像。灰度是我的错误,应该是image_binary_dilated,已经修复了。WarpAffine和RotationMatrix我只是用来保持图像正确,但我认为这些并不是很必要。 - Sebastián
抱歉,我的错,'gray' 是反转变量,已更新。 - Sebastián
1个回答

2

介绍

你所询问的问题开始变得复杂,我相信现在已经没有一个“正确”或“错误”的答案,只有不同的方法来解决。几乎所有方法都会产生正面和负面的结果,但比例可能不同。达到100%的正面结果是非常具有挑战性的任务,我认为我的答案并没有达到这个目标。然而,它可以成为实现该目标更复杂工作的基础。

我的建议

因此,我想在这里提出一个不同的建议。 我不确定您为什么要执行所有步骤,我认为其中一些可能是不必要的。 让我们从问题开始:您想删除边框上的白色部分(这些部分不是数字)。 因此,我们需要一个关于如何将它们与字母区分开的想法,以便正确处理它们。 如果我们只是尝试轮廓和扭曲,那么它可能适用于某些图像,而对其他图像则不适用,因为并不是所有图像看起来都一样。这是最难有一个通用解决方案适用于许多图像的问题。

数字和边框(以及其他小点)之间的特征有什么区别: 经过思考后,我会说:形状!也就是说,如果您想象一下字母/数字周围的边界框,它看起来像一个矩形,其大小与图像大小有关。而在边框的情况下,它们通常非常大而窄,或者太小而不能被视为字母/数字(随机点)。

因此,我的猜测是进行分割,通过形状将特征分开。因此,我们取二进制图像,使用它们在轴上的投影删除一些部分(正如您在之前的问题中正确提出并且我认为我们应该使用),然后我们得到一个图像,其中每个字母都与白色边框分开。 然后,我们可以对每个分割对象进行分割和检查其形状,如果我们认为这些是字母,则保留它们,否则我们将放弃它们。

代码

我以您的数据为例编写了代码。某些参数已针对此图像集进行调整,因此对于较大的数据集,可能需要放宽这些参数。

import cv2
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
import scipy.ndimage as ndimage

# do this for all the images
num_images = 6
plt.figure(figsize=(16,16))
for k in range(num_images):

    # read the image
    binary_image = cv2.imread("binary_image/img{}.png".format(k), cv2.IMREAD_GRAYSCALE)
    # just for visualization purposes, I create another image with the same shape, to show what I am doing
    new_intermediate_image = np.zeros((binary_image.shape), np.uint8)
    new_intermediate_image += binary_image
    # here we will copy only the cleaned parts
    new_cleaned_image = np.zeros((binary_image.shape), np.uint8)

    ### THIS CODE COMES FROM THE PREVIOUS ANSWER: 
    # https://stackoverflow.com/questions/62127537/how-to-clean-binary-image-using-horizontal-projection?noredirect=1&lq=1
    (rows,cols)=binary_image.shape
    h_projection = np.array([ x/rows for x in binary_image.sum(axis=0)])
    threshold_h = (np.max(h_projection) - np.min(h_projection)) / 10
    print("we will use threshold {} for horizontal".format(threshold))
    # select the black areas
    black_areas_horizontal = np.where(h_projection < threshold_h)
    for j in black_areas_horizontal:
        new_intermediate_image[:, j] = 0

    v_projection = np.array([ x/cols for x in binary_image.sum(axis=1)])
    threshold_v = (np.max(v_projection) - np.min(v_projection)) / 10
    print("we will use threshold {} for vertical".format(threshold_v))
    black_areas_vertical = np.where(v_projection < threshold_v)
    for j in black_areas_vertical:
        new_intermediate_image[j, :] = 0
    ### UNTIL HERE

    # define the features we are looking for
    # this parameters can also be tuned
    min_width = binary_image.shape[1] / 14
    max_width = binary_image.shape[1] / 2
    min_height = binary_image.shape[0] / 5
    max_height = binary_image.shape[0]
    print("we look for feature with width in [{},{}] and height in [{},{}]".format(min_width, max_width, min_height, max_height))
    # segment the iamge
    labeled_array, num_features = ndimage.label(new_intermediate_image)

    # loop over all features found
    for i in range(num_features):
        # get a bounding box around them
        slice_x, slice_y = ndimage.find_objects(labeled_array==i)[0]
        roi = labeled_array[slice_x, slice_y]
        # check the shape, if the bounding box is what we expect, copy it to the new image
        if roi.shape[0] > min_height and \
            roi.shape[0] < max_height and \
            roi.shape[1] > min_width and \
            roi.shape[1] < max_width:
            new_cleaned_image += (labeled_array == i)

    # print all images on a grid
    plt.subplot(num_images,3,1+(k*3))
    plt.imshow(binary_image)
    plt.subplot(num_images,3,2+(k*3))
    plt.imshow(new_intermediate_image)
    plt.subplot(num_images,3,3+(k*3))
    plt.imshow(new_cleaned_image)

这个方法可以产生输出结果(在网格中,左边的图像是输入图像,中间的图像是基于直方图投影的掩模处理后的图像,右边的图像是清理后的图像):

enter image description here enter image description here

结论:

如上所述,这种方法并不能百分之百地得到正面结果。最后一张图片质量较低,有些部分没有连接起来,在处理过程中丢失了。我个人认为这是为了得到更干净的图像而付出的代价,如果你有很多图像,那么这不会成为问题,你可以删除那些类似的图像。总的来说,我认为这种方法返回的图像非常清晰,除了字母和数字以外的所有其他部分都被正确地移除了。

优点

  • 图像很干净,只保留了字母或数字

  • 参数可以调整,并且应该在图像之间保持一致

  • 如果出现问题,使用一些打印或一些调试循环来选择要保留的特征应该会更容易理解问题所在并进行修正

限制

  • 它可能会在某些情况下失败,其中字母和数字接触白色边框,这似乎是相当可能的。从投影创建的black_areas中处理,但我不太确信这会100%地工作。

  • 在处理过程中,一些数字的小部分可能会丢失,就像最后一张图片一样。


我完全理解了一切,感谢你抽出时间来解决这个问题。我认为这是一个很难解决的问题,你做得非常好。 - Sebastián
1
不用谢,尝试做这件事很有趣。感谢您提出新问题并清晰地解释问题,这对我理解也很有帮助。 - freerafiki
1
哇,你真是太好了,我非常感激你。我的名字是Luca。祝你的论文顺利!继续提问吧 :) - freerafiki

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接