小案例:使用OpenCV实现证件照背景替换

一、概述

  案例:使用kmeans算法实现证件照背景替换

  算法实现步骤:

    1.加载原图像

    2.制作kmeans输入参数所需要的数据(kmeans的输入数据类型是CV_32F,所以不能直接使用原始图像的数据,因为原始图像的数据类型为CV_8UC1)

    3.使用kmeans算法实现图像分类,并得到分类标签

    4.创建遮罩:通过分类标签,将背景部分的颜色标记位0,将前景(人物)像素值标记位255

    5.先对mask执行形态学操作去除干扰的白点,在使用高斯模糊平滑前景和背景之前的过度

    6.创建一个3通道的目标输出结果Mat,然后将目标背景填充到背景区域,将前景部分填充到前景区域。

    7.输出图像

 

    ps:算法的核心步骤其实就是找到mask,当mask找到之后就可以使用分类标签将背景和前景替换成为自己想要的像素。

二、代码示例

Id_Photo_Background_Replacement::Id_Photo_Background_Replacement(QWidget *parent)
    : MyGraphicsView{parent}
{
    this->setWindowTitle("证件照背景替换");
}

void Id_Photo_Background_Replacement::dropEvent(QDropEvent *event){
    QString filePath = event->mimeData()->urls().at(0).toLocalFile();
    showIdPhotoBackgroundReplacement(filePath.toStdString().c_str());
}

void Id_Photo_Background_Replacement::showIdPhotoBackgroundReplacement(const char* filePath){
    Mat src = imread(filePath);
    if(src.empty()){
        qDebug()<<"图像为空";
        return;
    }
    imshow("src",src);

    //制作kmeans需要的数据
    int width = src.cols;
    int height = src.rows;
    int dims = src.channels();
    int sampleCount = width*height;//总共的像素点
    Mat points(sampleCount,dims,CV_32F,Scalar(10));
    int index = 0;
    for(int row = 0;row<height;row++){
        for(int col = 0;col<width;col++){
            index = row*width +col;
            Vec3b bgr = src.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]);
        }
    }


    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::zeros(src.size(),CV_8UC1);
    index = src.rows*2+2;//找到背景像素的像素点位置
    int cIndex = labels.at<int>(index,0);//找到像素点位置在labels中所对应的标签,找到这个标签以后就可以根据这个标签来判断前景和背景
    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){//背景
                mask.at<uchar>(row,col) = 0;
            }else{//前景
                mask.at<uchar>(row,col) = 255;
            }
        }
    }
    imshow("mask",mask);

    //使用形态学腐蚀操作取出遮罩中的可能干扰正常结果的白点
    Mat kernel = getStructuringElement(MORPH_RECT,Size(3,3),Point(-1,-1));
    erode(mask,mask,kernel);
    //使用高斯模糊平滑边缘像素
    GaussianBlur(mask,mask,Size(3,3),0,0);

    //执行图像像素融合,执行最终的背景替换
    //定义背景颜色
    Vec3b bgColor;
    bgColor[0] = 217;//rng.uniform(0, 255);
    bgColor[1] = 60;// rng.uniform(0, 255);
    bgColor[2] = 160;

    Mat result = Mat::zeros(src.size(),CV_8UC3);//定义一个空的彩色图片

    //下面是背景融合的代码
    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 pix = mask.at<uchar>(row,col);//获取像素值
            if(pix==255){//前景
                result.at<Vec3b>(row,col) = src.at<Vec3b>(row,col);
            }else if(pix==0){//背景
                result.at<Vec3b>(row,col) = bgColor;
            }else{//需要像素融合的部分
                w = pix/255.0;//权重
                b1 = src.at<Vec3b>(row,col)[0];
                g1 = src.at<Vec3b>(row,col)[1];
                r1 = src.at<Vec3b>(row,col)[2];

                b2 = bgColor[0];
                g2 = bgColor[1];
                r2 = bgColor[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);

}

 

三、图片示例演示

 

 

 

 

posted on 2022-04-16 16:54  飘杨......  阅读(56)  评论(0编辑  收藏  举报