

从效果上来看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 的高),复杂度随着我们定义的卷积核变化而变化,当核越大算法复杂度就会越大。接下来我们来了解一下积分图的运算,它能帮减少算法的复杂度实现图像的快速模糊。
快速模糊图像(积分图运算)

下面是我们利用积分图的运算实现快速模糊的代码
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 的内循环,大大的减少了时间算法的复杂度,那么看看模糊出来的效果怎样吧

可以看到模糊的效果还是不错的,这样图片美容我们还不够照片都看不清晰了,接下来我们还要对图片的轮廓做保留,来了解了解快速保留轮廓吧。
快速保留轮廓
轮廓的保留我们引入几个公式:来看看具体怎么保留轮廓的
最终我们要通过公式

来计算输出图像的值,输出图像的值跟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;
}
再看看运行的效果:

,从效果上似乎可以看出这种方法比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);
我们来看看一张类似证件照片的处理


网友评论