从彩色背景中提取黑色对象
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 中的一个像素)。没有彩色背景,我用blur
和canny edge detector
来提取边缘。但是,在添加彩色背景时,由于条带,简单地执行grayscale
和edge 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 palette
、color quantation
和getting 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 颜色之间的欧几里得距离。
【讨论】:
以上是关于从彩色背景中提取黑色对象的主要内容,如果未能解决你的问题,请参考以下文章