【问题标题】:How to remove single pixels on the borders of a blob?如何删除斑点边界上的单个像素?
【发布时间】:2014-06-07 22:22:59
【问题描述】:

我有一个如下所示的二进制图像。如何去除斑点边界上的单个像素?

如果你不想给出完整的代码,你可以解释一些算法或指出正确的方向。

【问题讨论】:

  • 您可以执行conv2(your_image, ones(3)) == 4,然后在一行(或一列)中查找由 3 个 1s 组成的集群,其中心将是您的单个像素露头(除非它们位于角落)即conv2(conv2(your_image, ones(3)) == 4, ones(3)) == 3
  • 侵蚀,然后是膨胀 - 也称为形态开口,如果边界上的像素尺寸非常小,则解决方案是。
  • 关于腐蚀/膨胀,请参阅下面的@Jonas 反对意见(它也会吃掉 1 像素线)

标签: python c++ matlab opencv image-processing


【解决方案1】:

我设法通过使用 midtiby 建议的命中和未命中转换来解决这个问题。我使用以下内核来检测顶部、右侧、底部和左侧的单像素。

-1 -1 -1     1 -1 -1      1  1  1     -1 -1  1
-1  1 -1     1  1 -1     -1  1 -1     -1  1  1
 1  1  1     1 -1 -1     -1 -1 -1     -1 -1  1

其中-1 表示背景,1 表示前景,0 表示无关(在本例中未使用)。

四次命中和未命中变换的结果将用作掩码以去除单个像素。以下是 Python/OpenCV 中的完整代码:

import numpy as np
import cv2

def hitmiss(src, kernel):
    im = src / 255
    k1 = (kernel == 1).astype('uint8')
    k2 = (kernel == -1).astype('uint8')
    e1 = cv2.erode(im, k1, borderType=cv2.BORDER_CONSTANT)
    e2 = cv2.erode(1-im, k2, borderType=cv2.BORDER_CONSTANT)
    return e1 & e2

if __name__ == "__main__":
    im = cv2.imread('blobs.png', cv2.CV_LOAD_IMAGE_GRAYSCALE)
    _, im_binary = cv2.threshold(im, 50, 255, cv2.THRESH_BINARY)

    kernel = np.array([[-1,-1, 1], 
                       [-1, 1, 1], 
                       [-1,-1, 1]])

    im_mask = np.zeros(im_binary.shape, np.uint8)

    im_mask |= hitmiss(im_binary, kernel)
    im_mask |= hitmiss(im_binary, np.fliplr(kernel))
    im_mask |= hitmiss(im_binary, kernel.T)
    im_mask |= hitmiss(im_binary, np.flipud(kernel.T))

    im_dst = im_binary & ((1 - im_mask) * 255)
    cv2.imwrite('dst.png', im_dst)

给定这个输入图像:

脚本将产生以下输出:

【讨论】:

    【解决方案2】:

    我删除此类单个像素的方法是检查每个像素周围的 4 连接邻居。如果一个像素只有一个邻居,它将被移除。这也会擦除“旋转 L”边缘的一个像素,该像素预计不会被移除。

    这是我实现上述方法的代码:

        int neighborNum;
        vector<cv::Point> willRemove;
        for (int i = 1; i < img.rows - 1; i++) {
            for (int j = 1; j < img.cols - 1; j++) {
                neighborNum = 0;
                if (img.at<uchar>(i, j) == 255) {
                    // Check 4 connected neighbors
                    if (img.at<uchar>(i, j - 1) == 255) {
                        neighborNum++;
                    }
                    if (img.at<uchar>(i - 1, j) == 255) {
                        neighborNum++;
                    }
                    if (img.at<uchar>(i, j + 1) == 255) {
                        neighborNum++;
                    }
                    if (img.at<uchar>(i + 1, j) == 255) {
                        neighborNum++;
                    }
                    // Found! 
                    if (neighborNum == 1) {
                        willRemove.push_back(cv::Point(j, i));
                    }
                }
            }
        }
        // Try to remove pixels now    
        for (int i = 0; i < willRemove.size(); i++) {
            cv::circle(img, willRemove[i], 0, cv::Scalar(0, 0, 0), 1, 8, 0);
        }
    

    【讨论】:

    • 您只检查 neighbours==1 。恕我直言,它需要 3 个
    • @berak,在他的代码中,他检查了 N4 邻域,因此 neighborNum 确实应该是 1(而不是 3)。
    • @WangYudong 可能会删除一些其他不需要的像素(如 Y 形的中心像素等)
    【解决方案3】:

    这看起来像是hit and miss transform 的任务。

    【讨论】:

    • 请避免使用“仅链接”的答案,因为该链接将来可能会停止工作。见this meta discussion
    • 是的,这是命中和未命中转换的任务。我在自己的答案上发布了完整的代码。
    【解决方案4】:

    您应该在给定图像上执行erosion 操作,然后执行dialation 操作。不知何故,这些链接上的示例图像是相反的,所以不要混淆。
    侵蚀会使用适当的过滤器缩小您的白色区域。它将删除额外的白色像素。然后您可以再次拨号扩大白色区域,但没有额外的白色噪点像素。

    【讨论】:

    • 这种方法的问题在于,您还会删除单像素水平线。
    猜你喜欢
    • 1970-01-01
    • 2012-03-20
    • 2021-07-27
    • 2021-09-14
    • 2013-05-20
    • 1970-01-01
    • 2020-09-22
    • 2018-05-08
    • 1970-01-01
    相关资源
    最近更新 更多