opencv实战案例之照片背景替换

案例需求

替换证件照片的背景颜色


用到的知识

  • K-Means
  • 背景融合-高斯模糊
  • 遮罩层生成

处理流程

  • 数据组装,准备样本数据
  • KMeans分割
  • 背景去除
  • 遮罩生成
  • 遮罩模糊
  • 通道混合输出
    在这里插入图片描述

代码演示

#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>

using namespace  std;
using namespace cv;

#define PIC_PATH "/work/opencv_pic/"
#define PIC_NAME "zhang.jpg"

Mat mat_to_sample(Mat &img);
int main(void)
{
    Mat src;
    //获取完整的图片路径及名称
    string pic = string(PIC_PATH)+string(PIC_NAME);

    //打印图片路径
    cout << "pic path is :"<<pic<<endl;

    //读取图片
    src = imread(pic);

    //判断图片是否存在
    if(src.empty())
    {
        cout<<"pic is not exist!!!!"<<endl;
        return -1;
    }

    //显示图片
    string win_name = "pic";
    namedWindow(win_name,WINDOW_AUTOSIZE);
    imshow(win_name,src);


    //准备数据
    Mat points = mat_to_sample(src);

    //运行KMeans
    int num_cluster = 4;
    Mat lables;
    Mat centers;
    TermCriteria criteria =  TermCriteria(TermCriteria::EPS+TermCriteria::COUNT,10,0.1);
    kmeans(points,num_cluster,lables,criteria,3,KMEANS_PP_CENTERS,centers);

    //去背景 + 遮罩生成
    Mat mask = Mat::zeros(src.size(),CV_8UC1);
    int index = src.cols*2+2;
    int cindex = lables.at<int>(index,0);  //获取图片左上角(2,2)标签数据 证件照这个位置一般都是背景
    int height = src.rows;
    int width = src.cols;

    //遍历每个像素点
    for(int row=0;row <height;row++)
        for(int col=0;col<width;col++)
        {
            index = row*width+col;
            int lable = lables.at<int>(index,0);
            if(lable == cindex)   //如果当前点为背景点
            {
                mask.at<uchar>(row,col) = 0;    //背景部分为黑色
            }else
            {
                mask.at<uchar>(row,col) = 255;  //原图部分为黑色
            }
        }
    imshow("mask",mask);


    //腐蚀 + 高斯模糊   使边界点过渡更加自然 颜色过渡不会太生硬
    Mat kernel = getStructuringElement(MORPH_RECT,Size(3,3));
    erode(mask,mask,kernel);  //腐蚀 减少边界噪点
    imshow("erode mask",mask);

    GaussianBlur(mask,mask,Size(3,3),0,0);  //高斯模糊
    imshow("blur mask",mask);


    //通道混合
    Vec3b color(0,0,255);
    //color[0] = theRNG().uniform(0,255);
    //color[1] = theRNG().uniform(0,255);
    //color[2] = theRNG().uniform(0,255);

    //创建结果图片
    Mat result(src.size(),src.type());

    //背景与前景边界部分经过高斯处理 该部分的颜色需要渐变处理 看起来效果更加自然
    double w =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.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.at<Vec3b>(row,col)[1] = g;
                result.at<Vec3b>(row,col)[2] = r;
            }
        }
    imshow("result ",result);
    waitKey(0);
    destroyAllWindows();
    return 0;
}

Mat mat_to_sample(Mat &img)
{
    int width = img.cols;
    int height = img.rows;
    int sample_counts = width*height;
    int dims = img.channels();
    Mat points(sample_counts,dims,CV_32F);

    int index = 0;
    for(int row=0;row <img.rows;row++)
        for(int col=0;col<img.cols;col++)
        {
            index = row * width + col;
            Vec3b bgr = img.at<Vec3b>(row,col);
            points.at<float>(index,0) = bgr[0];
            points.at<float>(index,1) = bgr[1];
            points.at<float>(index,2) = bgr[2];

        }

    return points;
}

效果展示

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值