美文网首页
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