【问题标题】:Remove unwanted parts of mask image删除遮罩图像的不需要的部分
【发布时间】:2021-11-13 02:20:27
【问题描述】:

我正在使用 U2NET 成功计算图像的掩码,如下所示:

但是,可以看出,面具不是内部完全是白色的,并且在面具之外还可以观察到一些不需要的伪影,即左侧部分。

我正在尝试使用膨胀和腐蚀操作来解决上述两个问题,但我认为这是错误的方法,因为它不会产生预期的结果。

我正在做类似以下的事情,但没有解决问题:

from PIL import ImageFilter

dilation_img = image.filter(ImageFilter.MaxFilter(15))
erotion_img = dilation_img.filter(ImageFilter.MinFilter(15))

【问题讨论】:

  • 我认为你很接近,但需要稍微不同的顺序。尝试做一个“打开操作”(即腐蚀后跟着相等的膨胀步长),然后是一个“闭合操作”(膨胀跟着相等的腐蚀步长)。根据您尝试以岛的形式为打开操作移除的噪音以及您尝试在关闭操作中填充的孔的半径(半分钟切割)来玩每个步骤的数量。跨度>
  • 根据你的图片,我认为打开操作只需要2-3个步骤,关闭操作可能需要x2或x3那么多(由于右边的空白很大图片)
  • hmm 开始写上面的,但似乎这在很大程度上可以通过简单的阈值操作进行排序

标签: python image image-processing computer-vision


【解决方案1】:

您可以使用 Otsu 算法动态阈值图像。之后,您可以通过使用 OpenCV 轮廓绘制它们来填充任何孔。我不确定 U2Net 在屏蔽时可以返回什么值范围,但您也可以手动设置一个小的阈值 ~50。

import cv2
import numpy as np

# load image
img = cv2.imread("mask.jpg", cv2.IMREAD_GRAYSCALE);

# otsu thresholding
_, mask = cv2.threshold(img,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU);

# show
cv2.imshow("Mask", mask);
cv2.waitKey(0);

# close everything inside
contour, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE);

# get the biggest contour # returns _, contours, _ if using OpenCV 3
biggest_area = -1;
biggest = None;
for con in contour:
    area = cv2.contourArea(con);
    if biggest_area < area:
        biggest_area = area;
        biggest = con;

# fill in the contour
cv2.drawContours(mask, [biggest], -1, 255, -1);

# show
cv2.imshow("Filled Mask", mask);
cv2.waitKey(0);

【讨论】:

  • 嗨@Ian Chu,非常感谢您的回答。上述操作是否可以使用 PIL 或除 opencv 之外的其他库来完成?
  • 看起来 scikit 也有 otsu 阈值和它自己的 findContours 函数。我对scikit不太熟悉,但基本思路应该差不多。
【解决方案2】:

一种解决方案是使用二值阈值,使不是白色但接近白色的部分变为白色。

另一种解决方案是使用morphological operators 来填充或消除任何间隙(请注意,腐蚀/扩张等形态运算符可能需要对图像进行多次传递才能获得所需的结果)。

两者都可以使用。

为了自动/自适应地计算所需的阈值,我能想到的过程类似于使用合适的大型结构元素执行形态学运算。也就是说,扫描图像的区域并使用该区域像素的多数投票来为该区域的所有像素着色。

【讨论】:

  • 感谢您的回答。我正在按照您的建议做很多事情,但是您能否提供有关您提出的两个解决方案的代码示例以便接受您的答案?我的边界也很尖锐而不是平滑
  • 阈值设置就像扫描图像并根据一些精心选择的阈值(您可以尝试)将像素设置为黑色或白色一样简单。此外,您已经拥有执行形态学操作的代码。你可以多试几次
  • 那么就没有办法动态识别阈值了吗?
  • 查看更新的答案以了解一种方法,类似于进行形态学操作
【解决方案3】:
  1. 使用最小过滤器(例如 MinFilter(8)) = result 1

  2. 然后在 result 1 上应用与前一个最小值相同的值的最大过滤器(例如 MaxFilter(8))=result 2

  1. 使用result 2作为原始掩码的掩码,方法如下:

...

//assumptions: white=255, black=0
threshold = 128;// you can change it to give more weight for white or black
for(... loop all the the pixels)
for(...)
{
if((OrginalMask[x,y] > threshold) && (result2[x,y] < threshold))
    finalResult[x,y] = result2[x,y];//to remove unwanted artifacts outside the mask
else if ((OrginalMask[x,y] < threshold) && (result2[x,y] > threshold))
    finalResult[x,y] = result2[x,y];//to make the mask completely white inside
else
    finalResult[x,y] = OrginalMask[x,y];
}

【讨论】:

    【解决方案4】:

    虽然我认为这可以通过放置良好的二进制阈值来解决(如另一个答案中所述),但添加基本形态的附加级别应该使其对明显更脏的图像更加稳健。 (对不起,它是在 C++ 中)

    我使用任意二进制阈值来演示这个概念,但如果可用,我建议使用基于统计的阈值(例如 otsu 方法)。

    稍微解释一下代码:threshold将灰度图像转换为二进制。打开去除阈值 op 留下的外部噪声(任何小条、块或像素噪声)。闭合填充任何内部孔。 “打开”和“关闭”只是扩张和侵蚀组合的名称,以特定顺序实现预期效果,而不改变底层对象的大小/形状。

    #include <stdio.h>
    #include <opencv2/opencv.hpp>
    #include <Windows.h>
    #include <string>
    
    using namespace cv;
    
    int main(int argc, char** argv)
    {
        //C:/Local Software/voyDICOM/resources/images/oXsnC.jpg
        std::string fileName = "C:/Local Software/voyDICOM/resources/images/oXsnC.jpg";
        Mat tempImage = imread(fileName, cv::IMREAD_GRAYSCALE);
    
        Mat bwImg;
        //binary thresh (both of these work, otsu just gets a "smarter" threshold value rather than a hardcoded one)
        cv::threshold(tempImage, bwImg, 150, 255, cv::THRESH_BINARY);
        //cv::threshold(tempImage, bwImg, 0, 255, cv::THRESH_OTSU);
        
        Mat openedImage;
        //opening
        cv::erode(bwImg, openedImage, cv::getStructuringElement(cv::MORPH_CROSS, cv::Size(3, 3)), cv::Point(-1, -1), 2);
        cv::dilate(openedImage, openedImage, cv::getStructuringElement(cv::MORPH_CROSS, cv::Size(3, 3)), cv::Point(-1, -1), 2);
    
        Mat closedImg;
        //closing
        cv::dilate(openedImage, closedImg, cv::getStructuringElement(cv::MORPH_CROSS, cv::Size(3, 3)),cv::Point(-1,-1),5);
        cv::erode(closedImg, closedImg, cv::getStructuringElement(cv::MORPH_CROSS, cv::Size(3, 3)), cv::Point(-1, -1), 5);
    
    
        namedWindow("Original", WINDOW_AUTOSIZE);
        imshow("Original", tempImage);
    
        namedWindow("Thresh", WINDOW_AUTOSIZE);
        imshow("Thresh", bwImg);
    
        namedWindow("Opened", WINDOW_AUTOSIZE);
        imshow("Opened", openedImage);
    
        namedWindow("Closed", WINDOW_AUTOSIZE);
        imshow("Closed", closedImg);
    
        waitKey(0);
        system("pause");
        return 0;
    }
    

    结果:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-07-20
      • 1970-01-01
      • 2022-10-23
      • 1970-01-01
      • 2020-12-19
      • 2018-05-29
      • 2016-06-09
      • 2016-01-07
      相关资源
      最近更新 更多