NDK 开发实战 - 实现相机美颜功能

news/2024/10/31 5:26:48/

在 《图形图像处理 - 实现图片的美容效果》 一文中提到了图片的美容,采用双边滤波算法来实现,具体的算法流程和实现思路,大家可以在上篇文章中了解,这篇文章就在不再反复啰嗦了。这里我们再次来看下处理效果:

上面的效果看似好像不错,其实存在了大量的问题。从处理速度上来说,双边模糊算法是在二维的高斯函数上新增像素差值来实现的,使得算法的时间复杂度比较大(处理时间 > 1s),其次从处理效果上来说,用户一眼就能看出来,这是一张经过加工处理过的图片,眼睛很迷茫没了深邃,效果看上去很模糊没真实感。因此本文就从这两个方面下手,第一优化美容算法,其次优化美颜效果,使其能够真正的用到我们的手机移动端,实现实时美颜的功能。

1. 实现快速模糊

之前我们在实现模糊时,采用的是做卷积操作,其算法的复杂度是 image.rows * image.cols* kernel.rows * kernel.cols 且内部采用的是 float 运算,我们的卷积核 kernel 越大其算法的复杂度就越大。写法如下:

    Mat src = imread("C:/Users/hcDarren/Desktop/android/example.png");if (!src.data){printf("imread error!");return -1;}imshow("src", src);Mat dst;int size = 13;Mat kernel = Mat::ones(Size(size,size),CV_32FC1)/(size*size);filter2D(src,dst,src.depth(),kernel);imshow("dst", dst);
复制代码

那么有没有什么办法可以优化呢?这里给大家介绍一种新的算法 积分图运算,我们先来看下算法实现思路:

上图的实现原理其实很简单,处理的流程就是我们根据原图创建一张积分图,通过积分图就可以求得原图某一块区域的像素大小总和。之前做卷积操作的复杂度是 kernel.rows * kernel.cols , 而通过积分图来求就变成了 O(1) ,且不会随着卷积核的增大而增加其算法的复杂度。我们来看下具体的代码实现:

// 积分图的模糊算法 size 模糊的直径
void meanBlur(Mat & src, Mat &dst, int size){// size % 2 == 1// 把原来进行填充,方便运算Mat mat;int radius = size / 2;copyMakeBorder(src, mat, radius, radius, radius, radius, BORDER_DEFAULT);// 求积分图 (作业去手写积分图的源码) Mat sum_mat, sqsum_mat;integral(mat, sum_mat, sqsum_mat, CV_32S, CV_32S);dst.create(src.size(), src.type());int imageH = src.rows;int imageW = src.cols;int area = size*size;// 求四个点,左上,左下,右上,右下int x0 = 0, y0 = 0, x1 = 0, y1 = 0;int lt = 0, lb = 0, rt = 0, rb = 0;int channels = src.channels();for (int row = 0; row < imageH; row++){// 思考,x0,y0 , x1 , y1  sum_mat// 思考,row, col, dsty0 = row;y1 = y0 + size;for (int col = 0; col < imageW; col++){x0 = col;x1 = x0 + size;for (int i = 0; i < channels; i++){// 获取四个点的值lt = sum_mat.at<Vec3i>(y0, x0)[i];lb = sum_mat.at<Vec3i>(y1, x0)[i];rt = sum_mat.at<Vec3i>(y0, x1)[i];rb = sum_mat.at<Vec3i>(y1, x1)[i];// 区块的合int sum = rb - rt - lb + lt;dst.at<Vec3b>(row, col)[i] = sum / area;}}}
}
复制代码

2. 快速边缘保留

实现了快速模糊算法后,我们就得思考一下如何才能实现,快速的边缘保留效果呢?我们来看几个公式:

具体的实现分析,大家可以参考上面的实现思路,方差公式的推倒大家可以参考这里 en.wikipedia.org/wiki/Varian… 。剩下的就是直接开始套公式了:

int getBlockSum(Mat &sum_mat, int x0, int y0, int x1, int y1, int ch){// 获取四个点的值int lt = sum_mat.at<Vec3i>(y0, x0)[ch];int lb = sum_mat.at<Vec3i>(y1, x0)[ch];int rt = sum_mat.at<Vec3i>(y0, x1)[ch];int rb = sum_mat.at<Vec3i>(y1, x1)[ch];// 区块的合int sum = rb - rt - lb + lt;return sum;
}float getBlockSqSum(Mat &sqsum_mat, int x0, int y0, int x1, int y1, int ch){// 获取四个点的值float lt = sqsum_mat.at<Vec3f>(y0, x0)[ch];float lb = sqsum_mat.at<Vec3f>(y1, x0)[ch];float rt = sqsum_mat.at<Vec3f>(y0, x1)[ch];float rb = sqsum_mat.at<Vec3f>(y1, x1)[ch];// 区块的合float sqsum = rb - rt - lb + lt;return sqsum;
}// 积分图的模糊算法 size 模糊的直径
void fatsBilateralBlur(Mat & src, Mat &dst, int size, int sigma){// size % 2 == 1// 把原来进行填充,方便运算Mat mat;int radius = size / 2;copyMakeBorder(src, mat, radius, radius, radius, radius, BORDER_DEFAULT);// 求积分图 (作业去手写积分图的源码) Mat sum_mat, sqsum_mat;integral(mat, sum_mat, sqsum_mat, CV_32S, CV_32F);dst.create(src.size(), src.type());int imageH = src.rows;int imageW = src.cols;int area = size*size;// 求四个点,左上,左下,右上,右下int x0 = 0, y0 = 0, x1 = 0, y1 = 0;int lt = 0, lb = 0, rt = 0, rb = 0;int channels = src.channels();for (int row = 0; row < imageH; row++){// 思考,x0,y0 , x1 , y1  sum_mat// 思考,row, col, dsty0 = row;y1 = y0 + size;for (int col = 0; col < imageW; col++){x0 = col;x1 = x0 + size;for (int i = 0; i < channels; i++){int sum = getBlockSum(sum_mat, x0, y0, x1, y1, i);float sqsum = getBlockSqSum(sqsum_mat, x0, y0, x1, y1, i);float diff_sq = (sqsum - (sum * sum) / area) / area;float k = diff_sq / (diff_sq + sigma);int pixels = src.at<Vec3b>(row, col)[i];pixels = (1 - k)*(sum / area) + k * pixels;dst.at<Vec3b>(row, col)[i] = pixels;}}}
}
复制代码

3. 检测与融合皮肤区域

实现了快速边缘保留后,我们有了两方面的提升,第一个是算法时间上面的提升,第二个是效果上面的提升,脸上的水滴效果还在,眼睛区域基本没有变化,图片看上去比较真实。但我们发现效果还不是很好,如脖子上面的头发与原图相比有些模糊,因此我们打算只对皮肤区域实现美颜,其他区域采用其他算法。那我们怎么去判断皮肤区域呢?最简单的一种方式就是根据 RGB 或者 YCrCb 的值来筛选,然后根据皮肤区域来进行融合。

// 皮肤区域检测
void skinDetect(const Mat &src, Mat &skinMask){skinMask.create(src.size(), CV_8UC1);int rows = src.rows;int cols = src.cols;Mat ycrcb;cvtColor(src, ycrcb, COLOR_BGR2YCrCb);for (int row = 0; row < rows; row++){for (int col = 0; col < cols; col++){Vec3b pixels = ycrcb.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){skinMask.at<uchar>(row, col) = 255;}else{skinMask.at<uchar>(row, col) = 0;}}}
}// 皮肤区域融合
void fuseSkin(const Mat &src, const  Mat &blur_mat, Mat &dst, const Mat &mask){// 融合?dst.create(src.size(),src.type());GaussianBlur(mask, mask, Size(3, 3), 0.0);Mat mask_f;mask.convertTo(mask_f, CV_32F);normalize(mask_f, mask_f, 1.0, 0.0, NORM_MINMAX);int rows = src.rows;int cols = src.cols;int ch = src.channels();for (int row = 0; row < rows; row++){for (int col = 0; col < cols; col++){// mask_f (1-k)/*uchar mask_pixels = mask.at<uchar>(row,col);// 人脸位置if (mask_pixels == 255){dst.at<Vec3b>(row, col) = blur_mat.at<Vec3b>(row, col);}else{dst.at<Vec3b>(row, col) = src.at<Vec3b>(row, col);}*/// src ,通过指针去获取, 指针 -> Vec3b -> 获取uchar b1 = src.at<Vec3b>(row, col)[0];uchar g1 = src.at<Vec3b>(row, col)[1];uchar r1 = src.at<Vec3b>(row, col)[2];// blur_matuchar b2 = blur_mat.at<Vec3b>(row, col)[0];uchar g2 = blur_mat.at<Vec3b>(row, col)[1];uchar r2 = blur_mat.at<Vec3b>(row, col)[2];// dst 254  1float k = mask_f.at<float>(row,col);dst.at<Vec3b>(row, col)[0] = b2*k + (1 - k)*b1;dst.at<Vec3b>(row, col)[1] = g2*k + (1 - k)*g1;dst.at<Vec3b>(row, col)[2] = r2*k + (1 - k)*r1;}}
}
复制代码

4. 最后总结

如果我们对处理效果依旧不是很满意的话,我们可以自己再做一些折腾,像边缘加强或者模糊叠加等等。

// 边缘的提升 (可有可无)
Mat cannyMask;
Canny(src, cannyMask, 150, 300, 3, false);
imshow("Canny", cannyMask);
// & 运算  0 ,255 
bitwise_and(src, src, fuseDst, cannyMask);
imshow("bitwise_and", fuseDst);
// 稍微提升一下对比度(亮度)
add(fuseDst, Scalar(10, 10, 10), fuseDst);
复制代码

最后总结一下:无论我们怎么处理要保证两个方面,第一个是速度方面,因为如果集成到移动端手机上必须得考虑实时性,第二个是效果方面,要让用户看上去自然,尽量不要让用户感知这是处理过的特效。至于怎么集成到 android 移动端,大家感兴趣可以自己去试试,我将在后面的直播美颜部分来为大家进行讲解。

视频地址:pan.baidu.com/s/1Ax6qunmE…

视频密码:xzts


http://www.ppmy.cn/news/437084.html

相关文章

iOS-美颜相机 by GPUImage

之前网上有个段子说&#xff1a;日本女人靠化妆&#xff0c;韩国女人靠整容&#xff0c;中国女人靠美颜 足以证明相机美颜功能在中国地位是多么高&#xff0c;关于美颜相机的 App 在中国是那么的火 其实美颜相机的实现原理很简单&#xff0c;就是给采集到的图像通过添加滤镜进…

android 美颜相机开发,Android OpenGL ES从入门到进阶(一)—— 五分钟开发一款美颜相机...

源码链接:https://github.com/smzhldr/AGLFramework 一、前言 商店里有数十款的美颜相机类产品,以及像抖音,唱吧之类带有视频的软件,功能很强大,其实现原理基本上都是以OpenGL ES为核心的特效处理,笔者码了一个很轻量级的Android OpenGL ES及Camera开发框架,意在使用的时…

android自定义美颜相机,效果最自然 美颜相机for Android版更新

【IT168 资讯】备受爱自拍女生追捧的手机自拍神器“美颜相机”安卓版在八月初迎来了又一次重大改版&#xff0c;跟iPhone最新版一样&#xff0c;美颜相机安卓版1.3也在“自拍”功能中新增加了“美颜特效”模式&#xff0c;此外自拍瞬间的祛黑眼圈功能及祛斑功能再度升级&#x…

鸿蒙系统视频美颜,BeautyCam美颜相机

BeautyCam美颜相机app是一款超美的自拍相机软件。BeautyCam美颜相机app提供细腻质感滤镜和高级美颜功能&#xff0c;让你时刻保持好气色&#xff01; 软件介绍 BeautyCam美颜相机app是一款火爆全球的自拍神器。BeautyCam美颜相机app具有强大的功能&#xff0c;一键拍照美颜全搞…

java美颜相机

通过java来实现美颜相机需要以下步骤&#xff1a; 1.设置窗体&#xff08;画图版面、功能版面&#xff09; 2.添加动作监听器 3.对监听器的功能设置 一、设置窗体 首先我们应该设置美颜相机的窗体界面&#xff08;用边框布局BorderLayout&#xff09; 我们可以直接通过创建…

美颜相机基本算法总结

一、主要内容概述 本文主要是对美颜相关的一些内容的学习做一个总结&#xff0c;一个基本的美颜相机大概包含了美颜和美型两个方面&#xff0c;美颜主要是磨皮美白等效果&#xff0c;美型则是对眼睛、鼻子、脸型等做一些微调&#xff1b;大多美型相机还会提供各式各样的滤镜、…

【项目:实现美颜相机——java】

我们可以用java代码实现美颜相机的功能。 类似于之前的图片处理效果&#xff1a; 【java用监听器实现选择处理图片的效果】_ZERO_HOPE的博客-CSDN博客 摄像头抓取的图片一帧一帧地绘制在界面上&#xff0c;得到视频效果。 我们对图片进行处理&#xff0c;就能得到滤镜效果的…

为什么说网络安全是IT行业最后的红利?

前言 “没有网络安全就没有国家安全”。当前&#xff0c;网络安全已被提升到国家战略的高度&#xff0c;成为影响国家安全、社会稳定至关重要的因素之一。 网络安全行业特点 1、就业薪资非常高&#xff0c;涨薪快 2021年猎聘网发布网络安全行业就业薪资行业最高人均33.77万…