证件照换背景色

证件照替换背景色是一个很常用的功能,原理其实很简单,其实是一个图像分割问题,找到图像背景区域,然后替换成新的背景,注意最后要在人物边缘做羽化处理,使其看起来过度自然,这里用多种图像分割方法实现。

  • K-Means
int main(int argc, char ** argv)
{
    Mat src = imread("./demo.jpg", IMREAD_COLOR);
    // 图片太大,先缩放,减少运算量
    cv::resize(src, src, src.size() / 2, 0, 0);
    namedWindow("src image");
    imshow("src image", src);
    
    // 图像分割
    const int sampleCount = src.rows * src.cols;
    Mat data(sampleCount, src.channels(), CV_32F);
    for (int r = 0; r < src.rows; r++)
    {
        for (int c = 0; c < src.cols; c++)
        {
            float blue = src.at<Vec3b>(r, c)[0];
            float green = src.at<Vec3b>(r, c)[1];
            float red = src.at<Vec3b>(r, c)[2];
            int index = r * src.cols + c;
            data.at<Vec3f>(index,0) = Vec3f(blue,green,red);         
        }
    }
    
    const int nCluster = 5; // 分为5类
    Mat bestLabels(sampleCount,1,CV_32S);
    // K-means图像分割
    
    TermCriteria citeria = TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 10, 0.1);
    cv::kmeans(data, nCluster, bestLabels,citeria, 3, KMEANS_PP_CENTERS);
    
    int bgIndex = 5 * src.cols + 5; // 背景色序号
    int bgLabel = bestLabels.at<int>(bgIndex, 0);
    Vec3b newBgColor(217, 60, 160); // 新的背景色
                                                              
    Mat mask = Mat::zeros(src.size(), CV_8UC1);  // 背景为0,前景为255
    for (int r = 0; r < src.rows; r++)
    {
        for (int c = 0; c < src.cols; c++)
        {
            int index = r * src.cols + c;
            int label = bestLabels.at<int>(index, 0);
            if (label != bgLabel)
            {
                mask.at<uchar>(r, c) = 255;
            }
        }
    }
    // 腐蚀 + 高斯模糊,羽化,图像与背景交汇处高斯模糊化
    Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
    erode(mask, mask, k);
    GaussianBlur(mask, mask, Size(3, 3), 0, 0);

    // 更换背景色以及交汇处融合处理
    Mat result(src.size(), src.type());
    
    for (int row = 0; row < src.rows; row++) {
        for (int col = 0; col < src.cols; 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) = newBgColor; // 背景
            }
            else {/* 融合处理部分 背景前景交界处 */               
                double w = m / 255.0;
                int b1 = src.at<Vec3b>(row, col)[0];
                int g1 = src.at<Vec3b>(row, col)[1];
                int r1 = src.at<Vec3b>(row, col)[2];
                int b2 = newBgColor[0];
                int g2 = newBgColor[1];
                int r2 = newBgColor[2];
                int b = b1 * w + b2 * (1.0 - w);
                int g = g1 * w + g2 * (1.0 - w);
                int r = r1 * w + r2 * (1.0 - w);   
                result.at<Vec3b>(row, col) = Vec3b(b, g, r);
            }
        }
    }

    namedWindow("result");
    imshow("result", result);

    waitKey(0);
    return 0;
}

  • GMM
    int main(int argc, char ** argv)
    {
        Mat src = imread("./demo.jpg", IMREAD_COLOR);
        // 图片太大,先缩放,减少运算量
        cv::resize(src, src, src.size() / 2, 0, 0);
        namedWindow("src image");
        imshow("src image", src);
        
        // 图像分割
        const int sampleCount = src.rows * src.cols;
        Mat data(sampleCount, src.channels(), CV_32F);
        for (int r = 0; r < src.rows; r++)
        {
            for (int c = 0; c < src.cols; c++)
            {
                float blue = src.at<Vec3b>(r, c)[0];
                float green = src.at<Vec3b>(r, c)[1];
                float red = src.at<Vec3b>(r, c)[2];
                int index = r * src.cols + c;
                data.at<Vec3f>(index,0) = Vec3f(blue,green,red);         
            }
        }
        
        const int nCluster = 5; // 分为5类
        Mat bestLabels(sampleCount,1,CV_32S);
        //GMM分割(基于高斯混合模型的期望最大值)
        Ptr<EM> em = EM::create();
        em->setClustersNumber(nCluster);
        em->setCovarianceMatrixType(EM::COV_MAT_SPHERICAL);
        em->setTermCriteria(TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 100, 0.1));
        em->trainEM(data, noArray(), bestLabels, noArray());
    
        int bgIndex = 5 * src.cols + 5; // 背景色序号
        int bgLabel = bestLabels.at<int>(bgIndex, 0);
        Vec3b newBgColor(217, 60, 160); // 新的背景色
                                                                  
        Mat mask = Mat::zeros(src.size(), CV_8UC1);  // 背景为0,前景为255
        for (int r = 0; r < src.rows; r++)
        {
            for (int c = 0; c < src.cols; c++)
            {
                int index = r * src.cols + c;
                int label = bestLabels.at<int>(index, 0);
                if (label != bgLabel)
                {
                    mask.at<uchar>(r, c) = 255;
                }
            }
        }
        // 腐蚀 + 高斯模糊,羽化,图像与背景交汇处高斯模糊化
        Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
        erode(mask, mask, k);
        GaussianBlur(mask, mask, Size(3, 3), 0, 0);
    
        // 更换背景色以及交汇处融合处理
        Mat result(src.size(), src.type());
        
        for (int row = 0; row < src.rows; row++) {
            for (int col = 0; col < src.cols; 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) = newBgColor; // 背景
                }
                else {/* 融合处理部分 背景前景交界处 */               
                    double w = m / 255.0;
                    int b1 = src.at<Vec3b>(row, col)[0];
                    int g1 = src.at<Vec3b>(row, col)[1];
                    int r1 = src.at<Vec3b>(row, col)[2];
                    int b2 = newBgColor[0];
                    int g2 = newBgColor[1];
                    int r2 = newBgColor[2];
                    int b = b1 * w + b2 * (1.0 - w);
                    int g = g1 * w + g2 * (1.0 - w);
                    int r = r1 * w + r2 * (1.0 - w);   
                    result.at<Vec3b>(row, col) = Vec3b(b, g, r);
                }
            }
        }
    
        namedWindow("result");
        imshow("result", result);
    
        waitKey(0);
        return 0;
    }

  • watershed
// watershed
Mat mask; // watershed 标记
void onMouse(int event, int x, int y, int flags, void *) {

    if (event == CV_EVENT_LBUTTONDOWN) // 左键选择背景
    {
        mask.at<int>(y, x) = 1;
    }
    else if (event == CV_EVENT_RBUTTONDOWN)
    {
        mask.at<int>(y, x) = 2;
    }   
}

int main(int argc, char **argv)
{
    Mat src = imread("./demo.jpg", IMREAD_COLOR);
    // 图片太大,先缩放,减少运算量
    cv::resize(src, src, src.size() / 2, 0, 0);
    namedWindow("src image");
    imshow("src image", src);
    Mat gray;
    cvtColor(src, gray,CV_BGR2GRAY);
    // 用户参与,用户选择背景颜色
    // create mask
    mask = Mat::zeros(src.size(), CV_32SC1);
    setMouseCallback("src image", onMouse, 0);
    
    while (true)
    {
        char c = waitKey(150);
        if (c == 27) // ESC
        {

            break;
        }
        else if (c == 13) // enter
        {
            break;
        }
    }
       
    watershed(src, mask);
    // 替换背景颜色
    Vec3b newBgColor(217, 60, 160); // 新的背景色
    Mat result;
    src.copyTo(result);
    for (int r = 0; r < result.rows; r++)
    {
        for (int c = 0; c < result.cols; c++)
        {
            if (mask.at<int>(r, c) == 1)
            {
                result.at<Vec3b>(r, c) = newBgColor;
            }
        }
    }
    namedWindow("watershed");
    imshow("watershed", result);
    waitKey(0);
    return 0;
}

  • Grubcut
void onMouse(int event, int x, int y, int flags, void* userdata);
Rect rect;
Mat src, roiImg, result;
void showImg();
bool fineTuned = false;
Mat mask;
int main(int arc, char** argv) 
{
    src = imread("./demo.jpg", IMREAD_COLOR);
    // 图片太大,先缩放,减少运算量
    cv::resize(src, src, src.size() / 2, 0, 0);
    namedWindow("src image");
    imshow("src image", src);
    Mat result;
    src.copyTo(result);
    namedWindow("grubCut");
    imshow("grubCut", result);
    setMouseCallback("grubCut", onMouse);
    //定义输出结果,结果为:GC_BGD =0(背景),GC_FGD =1(前景),GC_PR_BGD = 2(可能的背景), GC_PR_FGD = 3(可能的前景)		
    mask = Mat::zeros(src.size(), CV_8UC1);  
    while (true)
    {
        //两个临时矩阵变量,作为算法的中间变量使用
        Mat bgModel, fgModel;
        char c = waitKey(0);
        Mat foreMask;
        if (c == 27) // ESC
        {
            break;
        }
        else if (c == 13) // enter
        {        
            // GrabCut 抠图
            grabCut(src, mask, rect, bgModel, fgModel, 3, GC_INIT_WITH_RECT);
            //比较mask的值为可能的前景像素才输出到mask中
            compare(mask, GC_PR_FGD, foreMask, CMP_EQ);
            fineTuned = true;
        }
        else if (c == 'r') // 进一步细化
        {
            // GrabCut 抠图
            grabCut(src, mask, rect, bgModel, fgModel, 1, GC_INIT_WITH_MASK);
            //比较mask的值为可能的前景像素才输出到mask中
            compare(mask, GC_PR_FGD, foreMask, CMP_EQ);
        }
        //将原图像src中的result区域拷贝到foreground中
        // 新的背景色 217,60,160
        //Mat result(src.size(), src.type(), Scalar(217, 60, 160));
        result = Scalar(217, 60, 160);
        src.copyTo(result, foreMask);
       
        imshow("grubCut", result);
    }
    waitKey(0);
    return 0;
}


void showImg() {
    src.copyTo(roiImg);  
    rectangle(roiImg, rect, Scalar(0, 0, 255), 2);
    imshow("src image", roiImg);
}
//鼠标选择矩形框左上角点
void onMouse(int event, int x, int y, int flags, void* userdata) 
{
    if (fineTuned)
    {
        mask.at<uchar>(y, x) = GC_PR_BGD;
    }
    else
    {
        switch (event)
        {
        case CV_EVENT_LBUTTONDOWN://鼠标左键按下事件
            rect.x = x;
            rect.y = y;
            break;
        case EVENT_LBUTTONUP://鼠标弹起事件
            rect = Rect(Point(0, rect.y), Point(src.cols, src.rows));
            showImg();
            break;
        default:
            break;
        }
    }
}

 

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Neil_baby

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值