一.设计步骤
二.程序
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
Mat mat_to_samples(Mat &image);
int main(int argc, char** argv)
{
Mat src = imread("E:\\图像处理图片\\tx.png");
if (src.empty())
{
printf("could not load image...\n");
return -1;
}
namedWindow("原图", WINDOW_AUTOSIZE);
imshow("原图", src);
// 组装数据
Mat points = mat_to_samples(src);
// 运行KMeans
int numCluster = 4; //定义分类的数目为 4
Mat labels; //表示计算之后各个数据点的最终的分类索引
Mat centers; //用来存储聚类后的中心点
TermCriteria criteria = TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 10, 0.1);
kmeans(points, numCluster, labels, criteria, 3, KMEANS_PP_CENTERS, centers);
// 去背景+遮罩生成
Mat mask = Mat::zeros(src.size(), CV_8UC1); //初始化遮罩,都设为0黑色
int index = src.rows * 2 + 2; //这里不懂是为什么???
int cindex = labels.at<int>(index, 0); //将背景设置为0
int height = src.rows;
int width = src.cols;
//Mat dst;
//src.copyTo(dst);
//循环每个像素
for (int row = 0; row < height; row++)
{
for (int col = 0; col < width; col++)
{
index = row * width + col;
int label = labels.at<int>(index, 0);
if (label == cindex) //如果是背景,置为0黑色,否则为前景,置为255白色
{// 背景
//dst.at<Vec3b>(row, col)[0] = 0;
//dst.at<Vec3b>(row, col)[1] = 0;
//dst.at<Vec3b>(row, col)[2] = 0;
mask.at<uchar>(row, col) = 0;
}
else
{
mask.at<uchar>(row, col) = 255;
}
}
}
//imshow("mask", mask);
// 腐蚀 + 高斯模糊
Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
//getStructuringElement函数会返回指定形状和尺寸的结构元素。
//Mat getStructuringElement(int shape, Size esize, Point anchor = Point(-1, -1));
//第一个参数表示内核的形状,有三种形状可以选择。
// 矩形:MORPH_RECT;
// 交叉形:MORPH_CROSS;
// 椭圆形:MORPH_ELLIPSE;
//第二和第三个参数分别是内核的尺寸以及锚点的位置。一般在调用erode以及dilate函数之前,
//先定义一个Mat类型的变量来获得getStructuringElement函数的返回值。对于锚点的位置,有默认值Point(-1,-1),表示锚点位于中心点。
//element形状唯一依赖锚点位置,其他情况下,锚点只是影响了形态学运算结果的偏移。
erode(mask, mask, k); //腐蚀操作
//imshow("erode-mask", mask);
GaussianBlur(mask, mask, Size(3, 3), 0, 0); //高斯模糊
//PS:腐蚀和模糊的结构元素的大小要一致!(即Size要一致)
//imshow("Blur Mask", mask);
// 通道混合
RNG rng(12345); //随机数产生器
Vec3b color;
//下面三个设置的是要换证件照的背景颜色,自己随意定义
color[0] = 100;//rng.uniform(0, 255);
color[1] = 100;// rng.uniform(0, 255);
color[2] = 100;// rng.uniform(0, 255);
Mat result(src.size(), src.type());
double w = 0.0;
int b = 0, g = 0, r = 0;
int b1 = 0, g1 = 0, r1 = 0;
int b2 = 0, g2 = 0, r2 = 0;
for (int row = 0; row < height; row++)
{
for (int col = 0; col < width; col++)
{
int m = mask.at<uchar>(row, col); //首先获取mask的值
if (m == 255)
{
result.at<Vec3b>(row, col) = src.at<Vec3b>(row, col); // 前景
}
else if (m == 0)
{
result.at<Vec3b>(row, col) = color; // 背景
}
else
{
w = m / 255.0; //计算权重
b1 = src.at<Vec3b>(row, col)[0];
g1 = src.at<Vec3b>(row, col)[1];
r1 = src.at<Vec3b>(row, col)[2];
b2 = color[0];
g2 = color[1];
r2 = color[2];
b = b1 * w + b2 * (1.0 - w);
g = g1 * w + g2 * (1.0 - w);
r = r1 * w + r2 * (1.0 - w);
result.at<Vec3b>(row, col)[0] = b; //结果赋给result
result.at<Vec3b>(row, col)[1] = g;
result.at<Vec3b>(row, col)[2] = r;
}
}
}
imshow("背景替换", result);
waitKey(0);
return 0;
}
//组装数据:将图像转换为样本数据
Mat mat_to_samples(Mat &image)
{
//如同Kmeans中的第一步,将图像转换为样本的数据
int w = image.cols;
int h = image.rows;
int samplecount = w * h; //总像素个数
int dims = image.channels();
Mat points(samplecount, dims, CV_32F, Scalar(10));//points在我的博客:Kmeans算法中有介绍
int index = 0;
//遍历每个像素,将每个像素点变成对象
for (int row = 0; row < h; row++)
{
for (int col = 0; col < w; col++)
{
index = row * w + col;
Vec3b bgr = image.at<Vec3b>(row, col);
points.at<float>(index, 0) = static_cast<int>(bgr[0]);
points.at<float>(index, 1) = static_cast<int>(bgr[1]);
points.at<float>(index, 2) = static_cast<int>(bgr[2]);
}
}
return points;
}
输出结果: