opencv 证件照背景替换-KMeans
一、步骤说明
使用了KMeans图像分割,也使用了GMM高斯混合算法,但是感觉KMeans的效果好点。 整体的步骤:
- 数据组装;
- KMeans分割;
- 背景去除;
- 遮罩生成;
- 遮罩模糊,(视情况而定,可以不做);
- 通道混合输出;
- 完成。
二、代码实例
#include <opencv2/opencv.hpp> #include <iostream> using namespace cv; using namespace std; using namespace ml; const char* INPUT = "input image"; Mat mat_to_samples(Mat& image) { int w = image.cols; int h = image.rows; int samplecount = w * h; int dims = image.channels(); Mat points(samplecount, dims, CV_32F, Scalar(10)); 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; } int main() { Mat src = imread("D:/source/images/zjz1.png"); if (src.empty()) { puts("read image error"); system("pause"); return -1; } imshow(INPUT, src); // 组装数据 Mat points = mat_to_samples(src); // 运行 KMeans 感觉要比GMM分割的效果要好 int numCluster = 4; Mat labels; Mat centers; TermCriteria citeria = TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 10, 0.1); kmeans(points, numCluster, labels, citeria, 3, KMEANS_PP_CENTERS, centers); // 使用GMM 分类算法 //Ptr<EM> em_model = EM::create(); //em_model->setClustersNumber(numCluster); //em_model->setCovarianceMatrixType(EM::COV_MAT_SPHERICAL); //em_model->setTermCriteria(TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 100, 0.1)); //em_model->trainEM(points, noArray(), labels, noArray()); // 去背景 + 遮罩生成 Mat mask(src.size(), CV_8UC1); // 遮罩层 int index = src.rows * 2 + 2; // 取左上角的(2,2)坐标为背景颜色 int cindex = labels.at<int>(index, 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) { 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); imshow("KMeans", dst); // 腐蚀 + 高斯模糊 Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1)); erode(mask, mask, k); imshow("erode mask", mask); GaussianBlur(mask, mask, Size(3, 3), 0, 0); // 这里的核心是(3,3)一定要和腐蚀的核心一样,不然效果不好 imshow("Gaussianblur mask", mask); // 通道混合 :获取权重,然后混合图像, 使得图像边缘过度自然 RNG rng(12345); Vec3b color; color[0] = rng.uniform(0, 255); color[1] = rng.uniform(0, 255); color[2] = 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); 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; 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.at<Vec3b>(row, col)[1] = g; result.at<Vec3b>(row, col)[2] = r; } } } imshow("背景替换", result); waitKey(0); return 0; }