移除口罩图像中不需要的部分

3

我使用U2NET成功地计算了图像的掩膜,如下所示:

enter image description here

然而,可以看到,掩膜内部并不是完全白色,同时掩膜外部也存在一些不需要的伪影,即左侧部分。

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

我尝试了以下类似的操作,但并没有解决问题:

from PIL import ImageFilter

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

我觉得你很接近了,但需要稍微调整一下顺序。尝试进行“开运算”(即先腐蚀再等量扩张),然后进行“闭运算”(先膨胀再等量收缩)。根据你要消除的噪点形式,在开运算中玩弄每个步骤的数量,并基于你试图填充的孔的半径(一半最小切割)来调整闭运算的步骤数。 - Sneaky Polar Bear
根据您的图片,我认为打开操作只需要2-3个步骤,而关闭操作可能需要两到三倍的时间(由于图像右侧有大空白)。 - Sneaky Polar Bear
我已经开始写上面的内容了,但似乎这可以通过简单的阈值操作来大大解决。 - Sneaky Polar Bear
4个回答

3

您可以使用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);

enter image description here


嗨,@Ian Chu,非常感谢您的回答。除了opencv之外,使用PIL或其他库是否可以完成上述操作? - azal
看起来scikit也有otsu阈值和自己的findContours函数。我对scikit不是太熟悉,但基本思路应该是相似的。 - Ian Chu

1
尽管我认为这可以通过精心放置的二进制阈值来解决(如另一个答案中所述),但添加一个基本形态学级别应该使其更能适应明显更脏的图像。(抱歉,它是用C++编写的)
我使用了任意的二进制阈值来演示概念,但建议使用基于统计的阈值(如大津方法)(如果可用)。
为了解释代码:阈值将灰度图像转换为二进制。开运算去除阈值操作留下的外部噪声(任何小条、碎片或像素噪声)。闭运算填充任何内部孔。"开运算"和"闭运算"只是特定顺序的膨胀和腐蚀组合的名称,以实现所需效果,而不改变底层对象的大小/形状。
#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;
}

结果: enter image description here


0
  1. 使用最小值滤波器(例如MinFilter(8))= 结果1

  2. 然后对结果1应用相同值的最大值滤波器(例如MaxFilter(8))= 结果2

result 2

使用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];
}

Final Result


0

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

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

两者都可以使用。

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


谢谢答复。我正在进行您所建议的工作,但能否提供一下两种解决方案的代码示例,以便我接受您的答案?此外,我的界限非常明确而不是平滑的。 - azal
阈值化处理很简单,只需要扫描图像并根据一些精心选择的阈值(您可以进行实验)将像素设置为黑色或白色即可。此外,您已经拥有执行形态学操作的代码。您可以尝试多次对它们进行处理。 - Nikos M.
那么没有办法动态识别阈值吗? - azal
请参考更新的答案,其中的方法类似于进行形态学操作。 - Nikos M.

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