最近项目需要用到美颜的一些效果,因此开始接触opencv 计算机视觉库,在腾讯课堂上找到一个简单且免费的入门视频《Opencv4 快速入门视频30讲》,看完视频后,初步才对opencv 有一个比较清晰的概念和基本用法。 接下来就是开始对美颜的一些初步接触,下面写的一个简单的测试 效果,具体功能包括亮度、对比度、瘦脸、大眼、美白磨皮等,但实际上用于项目使用还是问题很多,需要更多的优化。
1.图像创建各个功能滑动条
void BeautyCam::initMainImgUI()
{namedWindow("BeautyCam", WINDOW_AUTOSIZE);string path = "6.jpg";m_MainImg =imread(path);imshow("src", m_MainImg);//检测人脸数据68点m_vecFaceData = dectectFace68(path);int max_value = 100;int con_value = 100;int lignhtnesss = 50;int contrast = 2;int bigeyeval = 0;int faceval = 0;int beautyval = 0;createTrackbar("亮度", "BeautyCam", &lignhtnesss, max_value, on_lightness, (void*)(&m_MainImg));createTrackbar("对比度", "BeautyCam", &contrast, max_value, on_contrast, (void*)(&m_MainImg));createTrackbar("大眼", "BeautyCam", &bigeyeval, 60, on_BigEye, (void*)(&m_MainImg));createTrackbar("瘦脸", "BeautyCam", &faceval, 70, on_thinFace, (void*)(&m_MainImg));createTrackbar("美颜", "BeautyCam", &beautyval, 200, on_beautyFace, (void*)(&m_MainImg));on_lightness(50, (void*)(&m_MainImg));imshow("BeautyCam", m_MainImg);
}
此代码就是创建滚动条的初始化程序,主要就是createTrackbar函数的使用:
原型:
CV_EXPORTS int createTrackbar(const String& trackbarname, //滚动条名称
const String& winname, //滚动条作用在哪一个窗口上 窗口名称
int* value, int count, //滑块初始值 和 滚动条最大值
TrackbarCallback onChange = 0, // 回调函数 滑条值变化
void* userdata = 0); //用户创给回调函数的用户数据
这里就是需要定义相关的回调函数,原型为:void (*TrackbarCallback)(int pos, void* userdata);
因此只需要创建相同的函数就行,滑条的变化就会导致回调函数触发,传递形参pos值的变化。
例如:
//对比度调节static void on_contrast(int b, void*userdata);//亮度调节static void on_lightness(int b, void*userdata);//眼睛调节static void on_BigEye(int b, void*userdata);//瘦脸效果static void on_thinFace(int b, void*userdata);//美颜效果static void on_beautyFace(int b, void*userdata);
2.回调函数 对比度调节的实现
void BeautyCam::on_contrast(int b, void*userdata)
{Mat img = *((Mat *)userdata);Mat m = Mat::zeros(img.size(), img.type());Mat dst = Mat::zeros(img.size(), img.type());m = Scalar(b, b, b);double con = b / 100.0;addWeighted(img, con, m, 0, 0, dst);imshow("BeautyCam", dst);
}
addWeighted()函数是将两张相同大小,相同类型的图片融合的函数;
原型:CV_EXPORTS_W void addWeighted(InputArray src1, //第一个输入图像
double alpha, //第一个元素权重
InputArray src2, //第二个输入图像
double beta, // 第二个元素权重
double gamma, //图1和图2 作和后添加的数值
OutputArray dst,//输出图像
int dtype = -1);
3. 回调函数 亮度调节的实现
void BeautyCam::on_lightness(int b, void*userdata)
{Mat img = *((Mat *)userdata);Mat m = Mat::zeros(img.size(), img.type());Mat dst = Mat::zeros(img.size(), img.type());m = Scalar(b, b, b);addWeighted(img, 1.0, m, 0, b, dst); imshow("BeautyCam", dst);
}
同上,对比度和亮度其实可以直接用一个addWeighted 实现,同时设置亮度和对比度(差值在扩大),主要是就是对addWeighed 的beta 和alpha参数去调节。
4.人脸数据检测
需要实现大眼或者瘦脸的效果,首先是需要检测人脸并提取特征点,一般常用的就是68个点,如下:
在此处是通过调用三方库dlib来获取68点特性点数据的:
std::vector<std::vector<Point2f>> BeautyCam::dectectFace68(const string &path)
{std::vector<std::vector<Point2f>> rets;//加载图片路径array2d<rgb_pixel> img;load_image(img, path.c_str());//定义人脸检测器frontal_face_detector detector = get_frontal_face_detector();std::vector<dlib::rectangle> dets = detector(img);for (auto var : dets){//关键点检测器shape_predictor sp;deserialize("shape_predictor_68_face_landmarks.dat") >> sp;//定义shape对象保存检测的68个关键点full_object_detection shape = sp(img, var);//存储文件ofstream out("face_detector.txt");//读取关键点到容器中std::vector<Point2f> points_vec;for (int i = 0; i < shape.num_parts(); ++i){auto a = shape.part(i);out << a.x() << " " << a.y() << " ";Point2f ff(a.x(), a.y());points_vec.push_back(ff);}rets.push_back(points_vec);}cout << "人脸检测结束:" <<dets.size()<<"张人脸数据"<< endl;return rets;
}
5.图像平移变形算法
接下来的难题就是图像局部变形算法,具体原理就是瘦脸是使用图像局部平移变形,放大眼睛是图像局部缩放变形。
图像局部平移变形公式:
说实话我看着这个公式看不懂,好像是结合《Interactive Image Warping》交互式图像变形算法而来的,去查看此文章过程中,发现全英文的,只能说一句,干瞪眼,完全看不懂,更别说公式是怎么推导出来的了,放弃了,直接根据大佬的代码去看懂这个公式。
参考:https://blog.csdn.net/grafx/article/details/70232797 图像处理算法之瘦脸及放大眼睛
图像局部缩放公式(大眼):
参考:https://www.freesion.com/article/40151105562/ 瘦脸大眼算法
百度过程中并没有发现c++编写的一些瘦脸大眼测试例子,有些贴的代码比较简单参数有点不好理解,但是python却有相关的例子(原理一样 ),因此通过python代码去封装成c++ 能使用的接口。
瘦脸和大眼主要是参考如下两篇博客来封装的函数接口:
参考:https://www.cnblogs.com/ckAng/p/10978078.html python+opencv+dlib瘦脸效果
参考:https://www.freesion.com/article/40151105562/ 瘦脸大眼算法
6.回调函数 大眼效果调节
void BeautyCam::on_BigEye(int b, void*userdata)
{Mat src = *((Mat *)userdata);Mat dst = src.clone();for (auto points_vec : m_pIntance->m_vecFaceData){Point2f left_landmark = points_vec[38];Point2f left_landmark_down = points_vec[27];Point2f right_landmark = points_vec[44];Point2f right_landmark_down = points_vec[27];Point2f endPt = points_vec[30];//# 计算第4个点到第6个点的距离作为距离/*float r_left = sqrt((left_landmark.x - left_landmark_down.x) * (left_landmark.x - left_landmark_down.x) +(left_landmark.y - left_landmark_down.y) * (left_landmark.y - left_landmark_down.y));cout << "左眼距离:" << r_left;*/float r_left = b;// # 计算第14个点到第16个点的距离作为距离//float r_right = sqrt(// (right_landmark.x - right_landmark_down.x) * (right_landmark.x - right_landmark_down.x) +// (right_landmark.y - right_landmark_down.y) * (right_landmark.y - right_landmark_down.y));//cout << "右眼距离:" << r_right;float r_right = b;// # 瘦左 m_pIntance->LocalTranslationWarp_Eye(src, dst, left_landmark.x, left_landmark.y, endPt.x, endPt.y, r_left);// # 瘦右m_pIntance->LocalTranslationWarp_Eye(src, dst, right_landmark.x, right_landmark.y, endPt.x, endPt.y, r_right);}imshow("BeautyCam", dst);
}
对于眼睛的放大,主要是对特征点38 27 44 30 四个点来进行图像局部缩放,也可以自己找适当的点尝试,点并不是唯一的。
此处用作滑条调节,因此把左眼距离和右眼距离都设置成滑条值,但这样是有问题的,因为不是每对眼睛拍照的眼珠都是一样大的。因此可以直接根据计算的距离(代码注释的地方)来进行加减滑条值。
例如:r_right = (系数)*r_right + b r_left= (系数)*r_left+ b 需要自己去调节适当的系数
图像局部缩放算法代码实现:
void BeautyCam::LocalTranslationWarp_Eye(Mat &img, Mat &dst, int warpX, int warpY, int endX, int endY, float radius)
{//平移距离 float ddradius = radius * radius;//计算|m-c|^2size_t mc = (endX - warpX)*(endX - warpX) + (endY - warpY)*(endY - warpY);//计算 图像的高 宽 通道数量int height = img.rows;int width = img.cols;int chan = img.channels();auto Abs = [&](float f) {return f > 0 ? f : -f;};for (int i = 0; i < width; i++){for (int j = 0; j < height; j++){// # 计算该点是否在形变圆的范围之内//# 优化,第一步,直接判断是会在(startX, startY)的矩阵框中if ((Abs(i - warpX) > radius) && (Abs(j - warpY) > radius))continue;float distance = (i - warpX)*(i - warpX) + (j - warpY)*(j - warpY);if (distance < ddradius){float rnorm = sqrt(distance) / radius;float ratio = 1 - (rnorm - 1)*(rnorm - 1)*0.5;//映射原位置float UX = warpX + ratio * (i - warpX);float UY = warpY + ratio * (j - warpY);//根据双线性插值得到UX UY的值BilinearInsert(img, dst, UX, UY, i, j);}}}
}
这其中使用到了双线性插值算法,因此也去查看了双线性插值算法原理,这里我只是对每一个像素点单独进行双线性插值如下:
void BeautyCam::BilinearInsert(Mat &src, Mat &dst, float ux, float uy, int i, int j)
{auto Abs = [&](float f) {return f > 0 ? f : -f;};int c = src.channels();if (c == 3){//存储图像得浮点坐标CvPoint2D32f uv;CvPoint3D32f f1;CvPoint3D32f f2;//取整数int iu = (int)ux;int iv = (int)uy;uv.x = iu + 1;uv.y = iv + 1;//step图象像素行的实际宽度 三个通道进行计算(0 , 1 2 三通道)f1.x = ((uchar*)(src.data + src.step*iv))[iu * 3] * (1 - Abs(uv.x - iu)) + \((uchar*)(src.data + src.step*iv))[(iu + 1) * 3] * (uv.x - iu);f1.y = ((uchar*)(src.data + src.step*iv))[iu * 3 + 1] * (1 - Abs(uv.x - iu)) + \((uchar*)(src.data + src.step*iv))[(iu + 1) * 3 + 1] * (uv.x - iu);f1.z = ((uchar*)(src.data + src.step*iv))[iu * 3 + 2] * (1 - Abs(uv.x - iu)) + \((uchar*)(src.data + src.step*iv))[(iu + 1) * 3 + 2] * (uv.x - iu);f2.x = ((uchar*)(src.data + src.step*(iv + 1)))[iu * 3] * (1 - Abs(uv.x - iu)) + \((uchar*)(src.data + src.step*(iv + 1)))[(iu + 1) * 3] * (uv.x - iu);f2.y = ((uchar*)(src.data + src.step*(iv + 1)))[iu * 3 + 1] * (1 - Abs(uv.x - iu)) + \((uchar*)(src.data + src.step*(iv + 1)))[(iu + 1) * 3 + 1] * (uv.x - iu);f2.z = ((uchar*)(src.data + src.step*(iv + 1)))[iu * 3 + 2] * (1 - Abs(uv.x - iu)) + \((uchar*)(src.data + src.step*(iv + 1)))[(iu + 1) * 3 + 2] * (uv.x - iu);((uchar*)(dst.data + dst.step*j))[i * 3] = f1.x*(1 - Abs(uv.y - iv)) + f2.x*(Abs(uv.y - iv)); //三个通道进行赋值((uchar*)(dst.data + dst.step*j))[i * 3 + 1] = f1.y*(1 - Abs(uv.y - iv)) + f2.y*(Abs(uv.y - iv));((uchar*)(dst.data + dst.step*j))[i * 3 + 2] = f1.z*(1 - Abs(uv.y - iv)) + f2.z*(Abs(uv.y - iv));}
}
整体的一个大眼效果就完成了。
7.回调函数 瘦脸效果调节
void BeautyCam::on_thinFace(int b, void*userdata)
{Mat src = *((Mat *)userdata);Mat dst = src.clone();for (auto points_vec : m_pIntance->m_vecFaceData){Point2f endPt = points_vec[34];for (int i = 3; i < 15; i = i + 2){Point2f start_landmark = points_vec[i];Point2f end_landmark = points_vec[i + 2];//计算瘦脸距离(相邻两个点算距离)/*float dis = sqrt((start_landmark.x - end_landmark.x) * (start_landmark.x - end_landmark.x) +(start_landmark.y - end_landmark.y) * (start_landmark.y - end_landmark.y));*/float dis = b;dst = m_pIntance->LocalTranslationWarp_Face(dst, start_landmark.x, start_landmark.y, endPt.x, endPt.y, dis);}}imshow("BeautyCam", dst);
}
在这里就没有选择进行选定指定特征点进行计算距离,我直接使用滑条值来作为瘦脸距离进行操作的。具体可以根据上面68特征点采取几个点来计算瘦脸距离。
图像局部平移算法代码实现:
Mat dst = img.clone();//平移距离 float ddradius = radius * radius;//计算|m-c|^2size_t mc = (endX - warpX)*(endX - warpX) + (endY - warpY)*(endY - warpY);//计算 图像的高 宽 通道数量int height = img.rows;int width = img.cols;int chan = img.channels();auto Abs = [&](float f) {return f > 0 ? f : -f;};for (int i = 0; i < width; i++){for (int j = 0; j < height; j++){// # 计算该点是否在形变圆的范围之内//# 优化,第一步,直接判断是会在(startX, startY)的矩阵框中if ((Abs(i - warpX) > radius) && (Abs(j - warpY) > radius))continue;float distance = (i - warpX)*(i - warpX) + (j - warpY)*(j - warpY);if (distance < ddradius){//# 计算出(i, j)坐标的原坐标//# 计算公式中右边平方号里的部分float ratio = (ddradius - distance) / (ddradius - distance + mc);ratio *= ratio;//映射原位置float UX = i - ratio * (endX - warpX);float UY = j - ratio * (endY - warpY);//根据双线性插值得到UX UY的值BilinearInsert(img, dst, UX, UY, i, j);//改变当前的值}}}return dst;}
以上两个平移和缩放算法主要就是根据python代码来进行封装的,整体效果还行,但还是需要不断的优化和更改。
8.回调函数 美颜磨皮算法
原理是使用opencv自带的人脸训练数据来获取人脸矩阵数据,进行双边滤波和高斯模糊来实现的;
美颜磨皮算法公式:
代码实现如下:
void BeautyCam::on_beautyFace(int b, void*userdata)
{Mat src = *((Mat *)userdata);Mat img = src.clone();double scale = 1.3;CascadeClassifier cascade = m_pIntance->loadCascadeClassifier("./haarcascade_frontalface_alt.xml");//人脸的训练数据CascadeClassifier netcascade = m_pIntance->loadCascadeClassifier("./haarcascade_eye_tree_eyeglasses.xml");//人眼的训练数据if (cascade.empty() || netcascade.empty())return;m_pIntance->detectAndDraw(img, cascade, scale,b);if (m_pIntance->isDetected == false){cout << "enter" << endl;Mat dst;int value1 = 3, value2 = 1;int dx = value1 * 5; //双边滤波参数之一 //double fc = value1 * 12.5; //双边滤波参数之一 double fc = b;int p = 50;//透明度 Mat temp1, temp2, temp3, temp4;//对原图层image进行双边滤波,结果存入temp1图层中bilateralFilter(img, temp1, dx, fc, fc);//将temp1图层减去原图层image,将结果存入temp2图层中temp2 = (temp1 - img + 128);//高斯模糊 GaussianBlur(temp2, temp3, Size(2 * value2 - 1, 2 * value2 - 1), 0, 0);//以原图层image为基色,以temp3图层为混合色,将两个图层进行线性光混合得到图层temp4temp4 = img + 2 * temp3 - 255;//考虑不透明度,修正上一步的结果,得到最终图像dstdst = (img*(100 - p) + temp4 * p) / 100;dst.copyTo(img);}imshow("BeautyCam", img);
}
美颜效果主要是通过双边滤波参数来调节的。
void BeautyCam::detectAndDraw(Mat& img, CascadeClassifier& cascade, double scale, int val)
{std::vector<Rect> faces;const static Scalar colors[] = { CV_RGB(0,0,255),CV_RGB(0,128,255),CV_RGB(0,255,255),CV_RGB(0,255,0),CV_RGB(255,128,0),CV_RGB(255,255,0),CV_RGB(255,0,0),CV_RGB(255,0,255) };//用不同的颜色表示不同的人脸//将图片缩小,加快检测速度Mat gray, smallImg(cvRound(img.rows / scale), cvRound(img.cols / scale), CV_8UC1);//因为用的是类haar特征,所以都是基于灰度图像的,这里要转换成灰度图像cvtColor(img, gray, CV_BGR2GRAY);resize(gray, smallImg, smallImg.size(), 0, 0, INTER_LINEAR);//将尺寸缩小到1/scale,用线性插值equalizeHist(smallImg, smallImg);//直方图均衡cascade.detectMultiScale(smallImg, //image表示的是要检测的输入图像faces,//objects表示检测到的人脸目标序列1.1, //caleFactor表示每次图像尺寸减小的比例2, //minNeighbors表示每一个目标至少要被检测到3次才算是真的目标(因为周围的像素和不同的窗口大小都可以检测到人脸),0 | CASCADE_SCALE_IMAGE ,//minSize为目标的最小尺寸Size(30, 30)); //minSize为目标的最大尺寸int i = 0;//遍历检测的矩形框for (std::vector<Rect>::const_iterator r = faces.begin(); r != faces.end(); r++, i++){isDetected = true;Mat smallImgROI;std::vector<Rect> nestedObjects;Point center, left, right;Scalar color = colors[i % 8];int radius;center.x = cvRound((r->x + r->width*0.5)*scale);//还原成原来的大小center.y = cvRound((r->y + r->height*0.5)*scale);radius = cvRound((r->width + r->height)*0.25*scale);left.x = center.x - radius;left.y = cvRound(center.y - radius * 1.3);if (left.y < 0){left.y = 0;}right.x = center.x + radius;right.y = cvRound(center.y + radius * 1.3);if (right.y > img.rows){right.y = img.rows;}/*原理算法美肤-磨皮算法Dest =(Src * (100 - Opacity) + (Src + 2 * GuassBlur(EPFFilter(Src) - Src + 128) - 256) * Opacity) /100 ;*///绘画识别的人脸框//rectangle(img, left, right, Scalar(255, 0, 0));Mat roi = img(Range(left.y, right.y), Range(left.x, right.x));Mat dst;int value1 = 3, value2 = 1;int dx = value1 * 5; //双边滤波参数之一 //double fc = value1 * 12.5; //双边滤波参数之一 double fc = val;//变化值int p = 50;//透明度 Mat temp1, temp2, temp3, temp4;//双边滤波 输入图像 输出图像 每像素领域的直径范围颜色空间过滤器的sigma 坐标空间滤波器的sigma bilateralFilter(roi, temp1, dx, fc, fc);temp2 = (temp1 - roi + 128);//高斯模糊 GaussianBlur(temp2, temp3, Size(2 * value2 - 1, 2 * value2 - 1), 0, 0);temp4 = roi + 2 * temp3 - 255;dst = (roi*(100 - p) + temp4 * p) / 100;dst.copyTo(roi);}
}
到此,美白磨皮的简单功能就实现了。
参考:https://blog.csdn.net/zhangqipu000/article/details/53260647 opencv 美白磨皮人脸检测
整体效果只是简单的实现,如果要运用到项目中,问题还是很多的,需要不断的优化和算法的更改呀。作为最近学习opencv的一个简单demo,来巩固知识点。