美文网首页
OpenCV实现图片的美化

OpenCV实现图片的美化

作者: 小小混世魔王 | 来源:发表于2019-04-08 10:29 被阅读0次
这篇文章了解快速模糊和快速有效的保留边缘来达到图片的美化,其效率比 opencv bilateralFilter()函数处理图片要快。我们知道bilateralFilter()是利用图像做卷积操作来实现的。我们来看一下opencv bilateralFilter()函数对图片美容的效果。 原图.jpg
bilateralFilter.jpg

从效果上来看bilateralFilter(双边滤波)既模糊了图像又很好的保留了边缘,通常的模糊都是通过定义一个卷积核,通过图像的卷积达到图像模糊的效果,代码如下

int main(){
    Mat src = imread("E:/ccode/gcyh_cv/u_test.jpg");

    if (!src.data){
        printf("imread error!");
        return -1;
    }

    imshow("src", src);

    Mat dst;

    int size = 15;
    
    Mat kernel = Mat::ones(Size(size, size), CV_32F) / (size*size);

    filter2D(src, dst, src.depth(), kernel);

    imshow("dst", dst);

    imwrite("E:\\ccode\\gcyh_cv\\模糊.jpg",dst);

    waitKey(0);
    return 0;
}

上面代码卷积操作,其算法的时间复杂度(图像的宽)x(图像的高)x(kernel 的宽)x(kernel 的高),复杂度随着我们定义的卷积核变化而变化,当核越大算法复杂度就会越大。接下来我们来了解一下积分图的运算,它能帮减少算法的复杂度实现图像的快速模糊。

快速模糊图像(积分图运算)

积分图.png

下面是我们利用积分图的运算实现快速模糊的代码

int integral_blur(const Mat &src, Mat &dst,int size) {
    if (size%2 == 0) {
        cout << "size 必须是奇数" << endl;
        return 0;
    }

    int src_w = src.cols;
    int src_h = src.rows;
    int channels = src.channels();
    cout <<"通道数"<< channels << endl;
    dst.create(src.size(), src.type());

    //把原图像进行边缘填充
    Mat makeBorder;
    int borderW = size / 2;
    copyMakeBorder(src, makeBorder, borderW, borderW, borderW, borderW,BORDER_DEFAULT);

    //积分图运算
    Mat sum_mat, sqsum_mat;
    integral(makeBorder, sum_mat, sqsum_mat);
    int left_t = 0, left_b = 0, right_t = 0, right_b = 0;
    int y0 = 0, x0 = 0, y1 = 0, x1 = 0;
    for (int row = 0; row < src_h; row++)
    {
        y0 = row;
        y1 = y0 + size;
        for (int col = 0; col < src_w; col++)
        {
            x0 = col;
            x1 = x0 + size;
            for (int channel = 0; channel < channels; channel++)
            {   
                //求去makeBorder图像size区块大小的和

                left_t = sum_mat.at<Vec3i>(y0, x0)[channel];
                left_b = sum_mat.at<Vec3i>(y1, x0)[channel];
                right_t = sum_mat.at<Vec3i>(y0, x1)[channel];
                right_b = sum_mat.at<Vec3i>(y1, x1)[channel];

                int sum = right_b - right_t - left_b + left_t;
                //我们最终要得到的是
                dst.at<Vec3b>(row, col)[channel] = sum / (size*size);
            }

        }
    }
    return 1;
}

上面代码 integral函数就是帮我们去转化积分图,当我们有了积分图sum_mat那么就可以去算原图像区块的和,最重要的是找出积分图像上的四个点的位置left_t left_b right_t right_b 利用积分图的规律sum = right_b - right_t - left_b + left_t就可以算出原图像上某区块的和,还要注意的一点就是积分图上at的时候用的类型Vec3i 因为积分图像上的值是慢慢变大的最后要超过Vec3b所盛放的类型。从上面可以看到代码少了size*size 的内循环,大大的减少了时间算法的复杂度,那么看看模糊出来的效果怎样吧 integral_blur.jpg

可以看到模糊的效果还是不错的,这样图片美容我们还不够照片都看不清晰了,接下来我们还要对图片的轮廓做保留,来了解了解快速保留轮廓吧。

快速保留轮廓

轮廓的保留我们引入几个公式:来看看具体怎么保留轮廓的 快速边缘保留.png
最终我们要通过公式 图片.png
来计算输出图像的值,输出图像的值跟k值系数有关,k值越小 输出图像的值趋近于局部平局值,k值越大就趋近当前像素值,这样就既可以模糊图像有可以很好的保留了轮廓,那么我们来看看k值是怎么计算的,上面公式k值是与我们的局部平方差有关系,局部平方差越大k值趋近1,局部平方差越小k值趋近0 那么我们看看具体套用公式的代码
float getSqsum(Mat &sqsum, int y0, int x0, int y1, int x1, int channel) {
    float  lt = sqsum.at<Vec3f>(y0, x0)[channel];

    float  lb = sqsum.at<Vec3f>(y1, x0)[channel];

    float  rt = sqsum.at<Vec3f>(y0, x1)[channel];

    float  rb = sqsum.at<Vec3f>(y1, x1)[channel];


    float sqsum_value = rb - rt - lb + lt;
    return sqsum_value;
}

int getSum(Mat &sum_mat, int y0, int x0, int y1, int x1, int channel) {
    int left_t = sum_mat.at<Vec3i>(y0, x0)[channel];
    int left_b = sum_mat.at<Vec3i>(y1, x0)[channel];
    int right_t = sum_mat.at<Vec3i>(y0, x1)[channel];
    int right_b = sum_mat.at<Vec3i>(y1, x1)[channel];

    return  right_b - right_t - left_b + left_t;
}


/*
利用积分图运算 实现快速模糊
*/


int integral_blur(const Mat &src, Mat &dst, int size, double sigma) {
    if (size%2 == 0) {
        cout << "size 必须是奇数" << endl;
        return 0;
    }

    int src_w = src.cols;
    int src_h = src.rows;
    int channels = src.channels();
    cout <<"通道数"<< channels << endl;
    dst.create(src.size(), src.type());

    //把原图像进行边缘填充
    Mat makeBorder;
    int borderW = size / 2;
    copyMakeBorder(src, makeBorder, borderW, borderW, borderW, borderW,BORDER_DEFAULT);

    //积分图运算
    Mat sum_mat, sqsum_mat;
    integral(makeBorder, sum_mat, sqsum_mat, CV_32S, CV_32F);

    int left_t = 0, left_b = 0, right_t = 0, right_b = 0;
    int y0 = 0, x0 = 0, y1 = 0, x1 = 0;
    for (int row = 0; row < src_h; row++)
    {
        y0 = row;
        y1 = y0 + size;
        for (int col = 0; col < src_w; col++)
        {
            x0 = col;
            x1 = x0 + size;
            for (int channel = 0; channel < channels; channel++)
            {   
                //求去makeBorder图像size区块大小的和

                int sum_value = getSum(sum_mat, y0, x0, y1, x1, channel);

                float sqsum_value = getSqsum(sqsum_mat, y0, x0, y1, x1, channel);
                

                float diff = (sqsum_value - (sum_value*sum_value / (size*size))) / (size*size);

                float k = diff / (diff + sigma);

                dst.at<Vec3b>(row, col)[channel] = (1 - k)*(sum_value / (size*size)) + k*src.at<Vec3b>(row, col)[channel];
            }

        }
    }
    return 1;
}
再看看运行的效果: 快速模糊与快速保留边缘.jpg

,从效果上似乎可以看出这种方法比bilateralFilter之后的效果更加好,从代码的复杂度上来说是大大优于bilateralFilter的。之后我们还可以对图片的亮度,对比度进行调整,如果只做人的美容,那还的对皮肤进行检测和融合

皮肤检测融合

基于YCrCb颜色空间Cr,Cb范围筛选法

void findSkin(Mat &src, Mat &dst) {
    dst= Mat::zeros(src.size(), CV_8UC1);
    Mat ycrcbMat;
    cvtColor(src, ycrcbMat,CV_BGR2YCrCb);

    for (int row = 0; row <src.rows; row++)
    {
        for (int col = 0; col < src.cols; col++)
        {
            Vec3b pixels = ycrcbMat.at<Vec3b>(row, col);
            uchar y = pixels[0];
            uchar cr = pixels[1];
            uchar cb = pixels[2];

            if (y>80 && 85<cb<135 && 135<cr<180){
                dst.at<uchar>(row, col) = 255;
            }
        }
    }

}

void fuseSkin(Mat &src, Mat &blur, Mat &dst, Mat &skin){
    dst.create(src.size(), src.type());

    for (int row = 0; row < src.rows; row++)
    {
        for (int col = 0; col < src.cols; col++)
        {

            uchar   skin_pix = skin.at<uchar>(row, col);

            if (skin_pix == 0) {
               //非皮肤区域
                dst.at<Vec3b>(row, col) = src.at<Vec3b>(row, col);
            }
            else{
                //皮肤区域
                dst.at<Vec3b>(row, col) = blur.at<Vec3b>(row, col);
            }

        }
    }
}

加入人脸检测人脸融合代码
    Mat skin_mat;
    findSkin(blur, skin_mat);

    Mat dst;
    fuseSkin(src, blur, dst, skin_mat);
//亮度提升
    add(dst, Scalar(10, 10, 10), dst);
//提高对比度
    vector<Mat> mats;
    Mat b_mat, g_mat, r_mat;
    split(dst, mats);
    equalizeHist(mats[0], b_mat);
    equalizeHist(mats[1], g_mat);
    equalizeHist(mats[2], r_mat);
    merge(mats, dst);
我们来看看一张类似证件照片的处理 处理前.jpg 处理后.jpg

相关文章

网友评论

      本文标题:OpenCV实现图片的美化

      本文链接:https://www.haomeiwen.com/subject/jytovqtx.html