从彩色背景中提取黑色对象

Posted

技术标签:

【中文标题】从彩色背景中提取黑色对象【英文标题】:Extract black objects from color background 【发布时间】:2014-06-15 09:26:33 【问题描述】:

人眼很容易将黑色与其他颜色区分开来。但是计算机呢?

我在普通的 A4 纸上打印了一些色块。由于构成彩色图像有三种墨水,青色、品红色和黄色,我将每个块的颜色设置为 C=20%、C=30%、C=40%、C=50% 和其余两种颜色是 0。这是我的源图像的第一列。到目前为止,不应该打印黑色 (K of CMYK) 墨水。之后,我将每个点的颜色设置为 K=100%,其余颜色为 0 以打印黑点。

你可能会觉得我的形象很奇怪很糟糕。事实上,图像被放大 30 倍,墨水如何欺骗我们的眼睛可以清晰地看到。色带妨碍我识别这些黑点(该点仅打印为 800 dpi 中的一个像素)。没有彩色背景,我用blurcanny edge detector 来提取边缘。但是,在添加彩色背景时,由于条带,简单地执行grayscaleedge detector 无法获得良好的效果。为了解决这些问题,我的眼睛会怎么做?

我决定检查源图像的亮度。我提到了this article 和公式:

亮度 = sqrt(0.299 R * R + 0.587 G * G + 0.114 B * B)

亮度更接近人的感知,在黄色背景下效果很好,因为黄色的亮度与青色和洋红色相比最高。但是如何使青色和品红色条尽可能亮呢?预期的结果是所有条带都消失了。


更复杂的图片:

C=40%,M=40%

C=40%,Y=40%

Y=40%,M=40%

C=40%,Y=40%亮度图像的FFT结果

谁能给我一些提示来删除色带?


@natan 我试过 FFT method 你建议我,但我并不幸运地在 x 轴和 y 轴都达到峰值。为了像您一样绘制频率,我将图像大小调整为正方形。

【问题讨论】:

既然这些行是周期性的,为什么不进行傅里叶过滤呢? @natan 你能告诉我如何从我的 fft 结果中知道背景线的频率(参见新编辑的问题)。 您可以尝试使用霍夫变换检测线条,或者只是对轴求和并查看峰值的位置(如果幸运的话),另请参阅此答案:***.com/questions/16476367/grid-detection-in-matlab/… 如果这是还不够,我稍后会尝试将其作为答案来实现... @natan 谢谢指导,但我不知道下一步该怎么做,因为频率可能不是那么均匀。 我认为有一个更简单的方法......看我的回答 【参考方案1】:

一种简单的方法是对所有像素设置阈值。这是用伪代码表达的这个想法。

for each pixel in image
    if brightness < THRESHOLD
        pixel = BLACK
    else
        pixel = WHITE

或者,如果您总是处理青色、洋红色和黄色背景,那么使用这些标准可能会得到更好的结果

if pixel.r < THRESHOLD and pixel.g < THRESHOLD and pixel.b < THRESHOLD

这种方法只会对简单的图像产生良好的效果,除了黑点之外没有任何东西太暗。

您可以尝试使用 THRESHOLD 的值来为您的图像找到合适的值。

【讨论】:

谢谢西蒙。我尝试了阈值,但我认为这不是一个聪明的方法,因为条带总是与所需的黑点混合,并且恒定的阈值无法获得良好的二进制结果。 对于您的示例图像,我认为如果您尝试使用阈值,您应该能够获得一些不错的结果。使用您的洋红色公式亮度应该在 160 左右(青色和黄色则更多),因此即使您的黑点不是亮度 0,您仍然应该能够找到一个可以很好地去除条纹的阈值。如果您仍然有条纹,请尝试降低您的门槛。【参考方案2】:

我会将图像转换为 HSV 颜色空间,然后使用值通道。这基本上分离了颜色和亮度信息。

这是 50% 的青色图像

然后你可以做一个简单的阈值来隔离点。

我只是很快就做到了,我相信你会得到更好的结果。也许在图像中找到轮廓,然后去除任何小区域的轮廓,以过滤任何剩余的噪声。

【讨论】:

谢谢詹姆斯。您的 HSV 建议确实使我的工作更好。但是,我有一个想法要与您分享,并想听听您的建议。 HSV 模型中的Value 是 r、g、b 分量的最大值,但我们没有考虑其他两个通道,只选择了最大的一个。我的意思是,在处理 C=60% 或更多的图像时,也许我们应该有效地利用每个通道。 是的,您当然可以改进仅使用价值渠道的方法。例如,粗略地说,饱和度是色调的多少(例如,低饱和度主要是灰色,而高饱和度主要是您的色调颜色)。因此,您可以使用饱和度通道找到彩色线条(高饱和度),然后尝试将它们移除。虽然可能有更好的(虽然可能更复杂)的方法,但我不是专家!【参考方案3】:

我建议转换为一些基于色度的色彩空间,例如LCH,并同时调整亮度和色度的阈值。这是输入图像的 L

似乎您需要自适应阈值,因为不同的值最适合图像的不同区域。

您也可以使用 HSV 或 HSL 作为色彩空间,但它们在感知上不如 LCH(源自 Lab)。

【讨论】:

【参考方案4】:

检查图像后,我认为稳健的阈值比任何事情都简单。例如,查看 C=40%,M=40% 的照片,我首先反转强度,因此仅使用黑色(信号)将变为白色

im=(abs(255-im));

我们可以检查它的RGB histograms using this:

hist(reshape(single(im),[],3),min(single(im(:))):max(single(im(:)))); 
colormap([1 0 0; 0 1 0; 0 0 1]);

所以我们看到对一些中等强度有很大贡献,而现在为白色的“信号”大部分被分离到更高的值。然后我应用了一个简单的阈值如下:

thr = @(d) (max([min(max(d,[],1))  min(max(d,[],2))])) ;
for n=1:size(im,3)
    imt(:,:,n)=im(:,:,n).*uint8(im(:,:,n)>1.1*thr(im(:,:,n)));
end

imt=rgb2gray(imt);

并摆脱小于某些典型区域大小的对象

min_dot_area=20;
bw=bwareaopen(imt>0,min_dot_area);
imagesc(bw); 
colormap(flipud(bone));

以下是原始图像的结果:

这个阈值的起源来自this code 我写了假设稀疏信号在嘈杂的背景中以二维峰值或斑点的形式出现。稀疏的意思是没有堆积的山峰。在这种情况下,当在 x 或 y 轴上投影 max(image) 时(通过(max(im,[],1)(max(im,[],1),您可以很好地测量背景。那是因为您采用了max(im) 向量的最小强度。

如果您想以不同的方式看待这一点,您可以查看图像强度的直方图。背景应该是某种强度周围的某种正态分布,信号应该高于该强度,但出现次数要低得多。通过找到其中一个轴(x 或 y)的 max(im),您会发现最大噪声水平是多少。

您会看到阈值选择了直方图中仍然存在一些噪声的点,但所有信号也都在它之上。这就是为什么我将它调整为1.1*thr。最后,有很多更好的方法来获得一个稳健的阈值,这是一种快速而肮脏的方法,在我看来已经足够好了......

【讨论】:

感谢您的出色回答。我正在学习你的方法。 @(d) 是什么? 它只是一个匿名函数的虚拟变量名。在这里阅读他们mathworks.com/help/matlab/matlab_prog/anonymous-functions.html 再次感谢并检查我的回答。【参考方案5】:

感谢大家发布他的答案!经过一番搜索和尝试,我也想出了一种自适应的方法来从彩色背景中提取这些黑点。看来只考虑亮度并不能完美解决问题。因此natan的计算和分析RGB直方图的方法更加稳健。不幸的是,我仍然无法获得一个可靠的阈值来提取其他颜色样本中的黑点,因为当我们添加更深的颜色(例如青色 > 60)或将两种颜色混合在一起(例如青色 = 50、洋红色)时,事情变得越来越不可预测= 50)。

有一天,我在谷歌上搜索“提取颜色”,TinEye's color extraction 和 color thief 启发了我。它们都是非常酷的应用程序,前一个网站处理的图像正是我想要的。所以我决定自己实现类似的东西。我在这里使用的算法是k-means clustering。其他一些相关的搜索关键词可能是color palettecolor quantationgetting dominant color

我首先应用高斯滤波器对图像进行平滑处理。

GaussianBlur(img, img, Size(5, 5), 0, 0);

OpenCV 有kmeans 函数,它为我节省了大量的编码时间。我修改this code。

// Input data should be float32 
Mat samples(img.rows * img.cols, 3, CV_32F);
for (int i = 0; i < img.rows; i++) 
    for (int j = 0; j < img.cols; j++) 
        for (int z = 0; z < 3; z++) 
            samples.at<float>(i + j * img.rows, z) = img.at<Vec3b>(i, j)[z];
        
    


// Select the number of clusters
int clusterCount = 4;
Mat labels;
int attempts = 1;
Mat centers;
kmeans(samples, clusterCount, labels, TermCriteria(CV_TERMCRIT_ITER|CV_TERMCRIT_EPS, 10, 0.1), attempts, KMEANS_PP_CENTERS, centers);

// Draw clustered result
Mat cluster(img.size(), img.type());
for (int i = 0; i < img.rows; i++) 
     for(int j = 0; j < img.cols; j++)  
        int cluster_idx = labels.at<int>(i + j * img.rows, 0);
        cluster.at<Vec3b>(i, j)[0] = centers.at<float>(cluster_idx, 0);
        cluster.at<Vec3b>(i, j)[1] = centers.at<float>(cluster_idx, 1);
        cluster.at<Vec3b>(i, j)[2] = centers.at<float>(cluster_idx, 2);
    

imshow("clustered image", cluster); 
// Check centers' RGB value
cout << centers;

聚类后,我将结果转换为灰度,找到最暗的颜色,它更有可能是黑点的颜色。

// Find the minimum value
cvtColor(cluster, cluster, CV_RGB2GRAY);
Mat dot = Mat::zeros(img.size(), CV_8UC1);
cluster.copyTo(dot);
int minVal = (int)dot.at<uchar>(dot.cols / 2, dot.rows / 2);
for (int i = 0; i < dot.rows; i += 3) 
    for (int j = 0; j < dot.cols; j += 3) 
        if ((int)dot.at<uchar>(i, j) < minVal) 
            minVal = (int)dot.at<uchar>(i, j);
        
    

inRange(dot, minVal - 5 , minVal + 5, dot);
imshow("dot", dot);

让我们测试两张图片。

(clusterCount = 4)

(clusterCount = 5)

k-means 聚类的一个缺点是一个固定的clusterCount 不能应用于每个图像。对于较大的图像,聚类也不是那么快。这是让我非常恼火的问题。我为了更好的实时性能(在 iPhone 上)的肮脏方法是裁剪 1/16 的图像并将较小的区域聚集在一起。然后将原始图像中的所有像素与每个聚类中心进行比较,并选择最接近“黑色”颜色的像素。我只是计算两种 RGB 颜色之间的欧几里得距离。

【讨论】:

以上是关于从彩色背景中提取黑色对象的主要内容,如果未能解决你的问题,请参考以下文章

从图像中删除黑色背景。

彩色 Bootstrap Glyphicons 打印为黑色

如何在白色背景上的Open GL ES中显示彩色纹理画笔?

使用 Grabcut 从图像中删除黑色背景 - Python

在终端中输出彩色文字

惠普 1050打印机不是自带了一个黑色墨盒和彩色墨盒吗?怎样设置这两个墨盒间的转换使用?