在上一篇笔记《OpenCV4学习笔记(51)》中,整理记录了基于K-Means聚类算法实现的图像分割,而本次笔记就是要将这种方法应用到实际例子中去,实现一个简单的证件照背景替换。关于K-Means聚类算法实现图像分割的内容,可以参阅我的上一篇笔记。
基于K-Means聚类算法的图像分割实现简单证件照片的背景分割提取与替换,要求照片的背景和前景的颜色差异比较大,实现的主要步骤如下:
(1) 读入证件图像,并建立样本数据集。
(2)使用K-Means算法进行图像分割,指定聚类数目,需要确保背景能够被完整分割出来。
(3)取图像中左上角像素点,认为该像素点属于证件照的背景部分,获取该像素点所属的类别标签label,从而将属于同一类别标签的像素点寻找出来。
(4)生成背景掩膜mask图像,按照mask图像进行背景替换。
下面来看代码演示:
//读取图像,并获取该图像的宽、高、总像素数
Mat test_image = imread("D:\\opencv_c++\\opencv_tutorial\\data\\images\\toux.jpg");
resize(test_image, test_image, Size(500, 600));
imshow("test_image", test_image);
int width = test_image.cols;
int height = test_image.rows;
int data_counts = width * height;
//将图像转化成数据集,是一个列数为1、行数为总像素数的Mat对象,每一行的元素是一个像素点样本,具有三通道
Mat data = test_image.reshape(3, data_counts);
//将数据集转换为浮点型,因为K-Means聚类算法只适用于连续性数据,否则会出现中心点漂移出样本集的错误
data.convertTo(data, CV_32F);
Mat bestLabels, centers;
int K = 3; //聚类的类别数,类别数越大则结果越接近原图像
TermCriteria criteria = TermCriteria(TermCriteria::Type::COUNT + TermCriteria::Type::EPS, 10, 0.01);
kmeans(data, K, bestLabels, criteria, 3, KMEANS_PP_CENTERS, centers);
//将输出结果图中的每一个像素点,按照其所属的类别来改变其像素值
Mat kmeans_image = Mat::zeros(test_image.size(), test_image.type());
Mat mask = Mat::zeros(test_image.size(), CV_8UC1);
int flag_label = bestLabels.at<int>(0, 0); //获取左上角像素点的标签label,作为背景像素点的标签
for (int row = 0; row < height; row++)
{
for (int col = 0; col < width; col++)
{
int data_index = row * width + col; //表示该像素点在数据集中所在的行索引,也就是第几个样本像素点
int label = bestLabels.at<int>(data_index, 0); //获取该像素点所属的类别标签
if (label == flag_label) //如果某像素点的label和背景像素点的label相同,则mask中对应位置的像素点置为255
{
mask.at<uchar>(row, col) = 255;
}
if (label == 0)
{
Vec3b bgr = { 255,0,0 };
kmeans_image.at<Vec3b>(row, col) = bgr;
}
else if (label == 1)
{
Vec3b bgr = { 0,255,0 };
kmeans_image.at<Vec3b>(row, col) = bgr;
}
else if (label == 2)
{
Vec3b bgr = { 0,0,255 };
kmeans_image.at<Vec3b>(row, col) = bgr;
}
}
}
imshow("kmeans_image", kmeans_image);
//对mask图像进行闭操作,降低背景替换后图像的撕裂感
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
morphologyEx(mask, mask, MORPH_CLOSE, kernel, Point(-1, -1), 1, 0);
imshow("mask", mask);
Mat dst;
dst = test_image.clone();
for (int row = 0; row < height; row++)
{
for (int col = 0; col < width; col++)
{
//以mask中同一位置坐标像素点的值为标志flag,如果flag==255证明该位置为背景,则将该像素值设置为我们需要的颜色
int flag = mask.at<uchar>(row, col);
if (flag == 255)
{
Vec3b bgr = { 255,255,255 };
dst.at<Vec3b>(row, col) = bgr;
}
}
}
imshow("dst", dst);
在获取图像背景的mask后,可以进行一下形态学方面的操作,用来消除一些小的干扰或者是填充一些孔洞,使得背景替换的效果更好。
下面是我们用来演示的证件照:
注意:该图像来源网络,如果涉及侵权将立马删除!!!
使用K-Means算法进行图像分割后的结果,这里用红、绿、蓝三种颜色来代表三种不同的类别:
从分割结果中提取出的背景掩膜mask图像,经过形态学闭操作处理:
最后将原来证件照的蓝色背景替换成白色背景:
这样就实现了对证件照的简单的背景提取和替换了,当然了这种方法只适用于背景单一、而且背景和前景具有较大差异的证件照,其普适性还是比较差的。但这种方法也为我们从图像中提取前景提供了一种思路,还是可以学习一下的。
好的,今天的笔记就整理到这啦,谢谢阅读~
PS:本人的注释比较杂,既有自己的心得体会也有网上查阅资料时摘抄下的知识内容,所以如有雷同,纯属我向前辈学习的致敬,如果有前辈觉得我的笔记内容侵犯了您的知识产权,请和我联系,我会将涉及到的博文内容删除,谢谢!