文章目录

一、证件照背景替换

关于视频的操作: ​​​

实现流程:

  1. 指定背景色
  2. 获取背景的色块形成蒙板
  3. 蒙板边缘羽化
  4. 形成新颜色背景

头文件 ​​machine_learning_all.h​​:

#pragma once
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;


class Machine_learning{
public:
void replace_bg_demo(Mat& image);
void replace_bg_video_demo(Mat& image, const char* mp4_path);
};

主函数​​main.cpp​

#include "machine_learning_all.h"


int main(int argc, char** argv) {
const char* input_path = "D:\\Desktop\\meinv3.png";
const char* mp4_path = "D:\\Desktop\\01.mp4";
Mat src = imread(input_path);
if (src.empty()) {
cout << "Read image failed!" << endl;
return -1;
}

Machine_learning ml;
ml.replace_bg_video_demo(src, mp4_path);
ml.replace_bg_demo(src);

imshow("src", src);
waitKey(0);
destroyAllWindows();
return 0;
}

效果展示

void Machine_learning::replace_bg_demo(Mat& image) {
int width = image.cols;
int height = image.rows;
int samplecount = width * height;
int dims = image.channels();
Mat points = Mat::zeros(samplecount, dims, CV_32F);

// image to matrix
int index_t = 0;
for (int row = 0; row < height; row++) {
uchar* src_ptr = image.ptr<uchar>(row);
for (int col = 0; col < width; col++) {
index_t = row * width + col;
points.at<float>(index_t, 0) = *src_ptr++;
points.at<float>(index_t, 1) = *src_ptr++;
points.at<float>(index_t, 2) = *src_ptr++;
}
}

// 运行KMeans
int numCluster = 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 = Mat::zeros(image.size(), CV_8UC1);
int index = image.rows * 5 + 12; //取此处像素为背景像素
int cindex = labels.at<int>(index, 0);

for (int row = 0; row < height; row++) {
uchar* mask_ptr = mask_mat.ptr<uchar>(row);
for (int col = 0; col < width; col++) {
index = row * width + col;
int label = labels.at<int>(index, 0);
if (label == cindex) { // 背景
*mask_ptr++ = 0;
}else {
*mask_ptr++ = 255;
}
}
}


// 腐蚀 + 高斯模糊 (处理边界区域,使图像混合更自然)
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
erode(mask_mat, mask_mat, kernel);
GaussianBlur(mask_mat, mask_mat, Size(3, 3), 0, 0);
imshow("mask_mat", mask_mat);


RNG rng(1232);
Vec3b color(217, 60, 160);
Mat result = Mat::zeros(image.size(), image.type());

double weight = 0.0;
for (int row = 0; row < height; row++) {
uchar* mask_ptr = mask_mat.ptr<uchar>(row);
uchar* src_ptr = image.ptr<uchar>(row);
uchar* result_ptr = result.ptr<uchar>(row);
for (int col = 0; col < width; col++) {
if (*mask_ptr == 255) { // 前景
*result_ptr++ = *src_ptr++;
*result_ptr++ = *src_ptr++;
*result_ptr++ = *src_ptr++;
}
else if (*mask_ptr == 0) { // 背景
*result_ptr++ = color[0];
*result_ptr++ = color[1];
*result_ptr++ = color[2];
src_ptr += 3;
}
else { // 加权混合
weight = *mask_ptr / 255.0;
*result_ptr++ = *src_ptr++ * weight + color[0] * (1.0 - weight);
*result_ptr++ = *src_ptr++ * weight + color[1] * (1.0 - weight);
*result_ptr++ = *src_ptr++ * weight + color[2] * (1.0 - weight);
}
mask_ptr++;
}
}
imshow("result", result);
}

二、视频绿幕背景替换

实现流程与图像相似,只是部分区域略有不同

Mat replace_and_blend(Mat& frame, Mat& mask_img, Mat& bg_img) {
Mat result = Mat::zeros(frame.size(), frame.type());
int h = frame.rows;
int w = frame.cols;
int dims = frame.channels();
double wt = 0;

for (int row = 0; row < h; row++) {
uchar* current = frame.ptr<uchar>(row);
uchar* bg_ptr = bg_img.ptr<uchar>(row);
uchar* mask_ptr = mask_img.ptr<uchar>(row);
uchar* result_ptr = result.ptr<uchar>(row);
for (int col = 0; col < w; col++) {
if (*mask_ptr == 255) { // 背景
*result_ptr++ = *bg_ptr++;
*result_ptr++ = *bg_ptr++;
*result_ptr++ = *bg_ptr++;
current += 3;

}
else if (*mask_ptr == 0) { // 前景
*result_ptr++ = *current++;
*result_ptr++ = *current++;
*result_ptr++ = *current++;
bg_ptr += 3;
}
else {
wt = *mask_ptr / 255.0; // 权重
*result_ptr++ = *bg_ptr++ * wt + *current++ * (1.0 - wt);
*result_ptr++ = *bg_ptr++ * wt + *current++ * (1.0 - wt);
*result_ptr++ = *bg_ptr++ * wt + *current++ * (1.0 - wt);
}
mask_ptr++;
}
}
return result;
}

void Machine_learning::replace_bg_video_demo(Mat& image, const char* mp4_path) {
Mat frame, hsv_img, mask_img;
VideoCapture capture(mp4_path);
if (!capture.isOpened()) {
cout << "could not find the video file..." << endl;;
return;
}
int frame_width = capture.get(CAP_PROP_FRAME_WIDTH);
int frame_height = capture.get(CAP_PROP_FRAME_HEIGHT);
int height = image.rows;
int width = image.cols;
if (height < frame_height || width < frame_width) {
cout << "背景图像太小了!" << endl;
return;
}
Mat bg_img = image(Rect(0, 0, frame_width, frame_height));
while (capture.read(frame)) {
cvtColor(frame, hsv_img, COLOR_BGR2HSV);
inRange(hsv_img, Scalar(35, 43, 46), Scalar(155, 255, 255), mask_img);

// 形态学操作
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
morphologyEx(mask_img, mask_img, MORPH_CLOSE, kernel);
erode(mask_img, mask_img, kernel);
GaussianBlur(mask_img, mask_img, Size(3, 3), 0, 0);

Mat result = replace_and_blend(frame, mask_img, bg_img);
if (waitKey(30) == 27) {
break;
}
imshow("result", result);
imshow("raw_frame", frame);
}
capture.release();
}