朋友找我帮忙更换证件照背景,因为mac上没装合适的软件,就想用OpenCV搞一下。


源文件我上传了一份: 使用OpenCV修改证件照背景。里面的代码可以直接用,头文件和库的路径要根据自己的机器进行配置。我的机器是mabbook air 13。OpenCV版本是3.1.0.


首先分析一下图片的情况:

python opencv换背景 opencv换图片的背景_灰度值


这次的目标是把肩、头上面的灰色背景除去(当然也可以换成其它颜色)。

分析: 从肉眼可以看出背景和衣服、头发之间的色差还是挺明显的。转化成图像处理的语言就是,衣服和头发的像素值(RGB或者灰度值)都要比与它们相邻的背景区域大。

思路:

1. 从肉眼看来,从上往下像素点的值应该是在变大的过程,直到遇见头发或者衣服。所以方法一就是竖着扫描像素点,依次将扫描过的点置成你想要的颜色(比如全白:255, 255, 255),终止条件是找到第一个(我用的灰度)像素点的值小于前一个点的值。然后扫描下一竖行。

结果:这种方法的结果在这幅图像上并不适用。因为有些竖行的灰度值并不是完全递增的。

2. 我设置一个灰度值作为阈值。

结果:这种方式的阈值不够灵活,会出现过界或者没到边界的情况。

 3. 取前n行(不能把目标包含进去)的最小值作阈值。

结果:这个方法就灵活很多。处理完之后洗二寸的照片上已经可以了。

用mac air自带的图片工具稍微处理一下就可以得到不错的效果了。


代码如下:

//
//  changeContext.cpp
//  changeContext
//
//  Created by cslzy on 16/5/12.
//  Copyright © 2016年 CY. All rights reserved.
//

#include "changeContext.hpp"

bool not_all_full(uchar pixel)
{
//    if (pixel[0] > uchar(0)) {
//        return true;
//    }
//    if (pixel[1] > uchar(0)) {
//        return true;
//    }
//    if (pixel[2] > uchar(0)) {
//        return true;
//    }
//    cout<<"work?"<<endl;
    if (pixel > uchar(0)) {
        return true;
    }
    return false;
}

void changeFull(string photo)
{
    Mat img = imread(photo);
    int width = img.size().width;
    int height = img.size().height;
    
    // show depth
//    cout<<"depth:"<<img.at<Vec3b>(0,0).depth()<<endl;
    
    Mat tem_mat;
    tem_mat = img.clone();
    Mat gray;
    cvtColor(img, gray, CV_BGR2GRAY);
    
    cout<<img.at<Vec3b>(0,0)[0]<<endl;
    // 取前j行的最小值作为阈值,从上往下
    for (int i = 0; i<width; i++) {
        //        for (int j = 0; j<height; j++) {
        int j = 0;
        int min = 256;
        while (j<height-1)
        {
            if (j<300) { // 从前j行中找像灰度素点值最小的点,意思
                if (gray.at<uchar>(j,i) < min) {
                    min = gray.at<uchar>(j,i);
                }
                img.at<Vec3b>(j,i)[0] = uchar(255);//R G B 三个通道,每一个都是8bits.
                img.at<Vec3b>(j,i)[1] = uchar(255);
                img.at<Vec3b>(j,i)[2] = uchar(255);
                j++;
                continue;
            }
            //            if (not_all_full(gray.at<uchar>(j,i))) {
            if (gray.at<uchar>(j,i)>min) {
                // 整数通道,应该是有多个通道。
                //                img.at<Vec3i>(j,i)[0] = 255;
                //                img.at<Vec3i>(j,i)[1] = 255;
                //                img.at<Vec3i>(j,i)[2] = 255;
                img.at<Vec3b>(j,i)[0] = uchar(255);//R G B 三个通道,每一个都是8bits.
                img.at<Vec3b>(j,i)[1] = uchar(255);
                img.at<Vec3b>(j,i)[2] = uchar(255);
                //                cout<<"("<<i<<", "<<j<<"):"<<img.at<Vec3f>(j,i)<<endl;
                //                imshow("tao", img);
                //                waitKey(1);
            }
            else
            {
                break;
            }
            
            j++;
        }
    }
    
    
//    // 从上往下,人工设置阈值。
//    for (int i = 0; i<width; i++) {
        for (int j = 0; j<height; j++) {
//        int j = 0;
//        while (j<height-1)
//        {
//            float flag = gray.at<uchar>(j,i) - gray.at<uchar>(j+1,i);
            if (not_all_full(gray.at<uchar>(j,i))) {
//            if (flag < 10) {
//            // 整数通道,应该是有多个通道。
                img.at<Vec3i>(j,i)[0] = 255;
                img.at<Vec3i>(j,i)[1] = 255;
                img.at<Vec3i>(j,i)[2] = 255;
//            img.at<Vec3b>(j,i)[0] = uchar(255);//R G B 三个通道,每一个都是8bits.
//            img.at<Vec3b>(j,i)[1] = uchar(255);
//            img.at<Vec3b>(j,i)[2] = uchar(255);
                cout<<"("<<i<<", "<<j<<"):"<<img.at<Vec3f>(j,i)<<endl;
                imshow("tao", img);
                waitKey(1);
//            }
//            else
//            {
//                break;
//            }
//            
//            j++;
//        }
//    }
    
    // 从左往右
//    for (int j = 0; j<height; j++) {
//        int i = 0;
//        while (i < width-1) {
//            float flag = gray.at<uchar>(j,i) - gray.at<uchar>(j, i+1);
//            if (flag < 10) {
//                img.at<Vec3b>(j,i)[0] = uchar(255);//R G B 三个通道,每一个都是8bits.
//                img.at<Vec3b>(j,i)[1] = uchar(255);
//                img.at<Vec3b>(j,i)[2] = uchar(255);
//            }
//            else
//            {
//                break;
//            }
//            i++;
//        }
//    }
    

    cout<<width<<" "<<height<<endl;
    imshow("fixed", img);
    imwrite("/Users/cslzy/Desktop/fuluhou_fixed.jpg", img);
    imshow("test", tem_mat);
    moveWindow("test", width, 0);
    waitKey(0);
}







思路1 和 2 都可以参考下图:

python opencv换背景 opencv换图片的背景_扣除更换证件照背景_02


当然也可以横着扫描,但是,要从两边开始往中间扫描。


思路3的效果如下图:

python opencv换背景 opencv换图片的背景_OpenCV_03




PS:感谢TT大美女让我使用她的证件照练手。。。