最近项目需要用到美颜的一些效果,因此开始接触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|^2
	size_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|^2
	size_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图层为混合色,将两个图层进行线性光混合得到图层temp4
		temp4 = img + 2 * temp3 - 255;

		//考虑不透明度,修正上一步的结果,得到最终图像dst
		dst = (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,来巩固知识点。

 

 

GitHub 加速计划 / opencv31 / opencv
77.38 K
55.71 K
下载
OpenCV: 开源计算机视觉库
最近提交(Master分支:2 个月前 )
c3747a68 Added Universal Windows Package build to CI. 11 天前
9b635da5 - 11 天前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐