基于VS2017+OpenCV3.4.1的PC端摄像头卡通化和皮肤变换

教材:《深入理解OpenCV 实用计算机视觉项目解析》
https://pan.baidu.com/s/16YPsbWmcys31CBXPCR4b3Q
提取码:o8dk
案例源码:https://github.com/MasteringOpenCV/code

关于opencv_contrib3.4.1,感谢用户鹏程朋诚
https://blog.csdn.net/qq_36163358/article/details/86593634
直接下了,可用,并体验了一下文章里的跟踪算法案例

也可以自己编译,具体参考我的文章。
win7x64+vs2017+opencv3.4.1+opencv_contrib3.4.1安装配置过程
https://blog.csdn.net/LTyyCFY/article/details/90035645

中间会出现一些小问题啥的。慢慢找怎么解决吧
问题1:LNK1112: 模块计算机类型“X86”与目标计算机类型“x64”冲突
vs里面有Debug和Release两种配置。网上的解决方案基本大同小异,这里说的问题是将配置管理器的平台和目标计算机(目标计算机在链接器->高级里)、视图->其他窗口->属性管理器修改后,VC++目录里的包含目录、库目录和链接器->输入->附加依赖项需要重新添加
我是将解决方案配置管理器和目标计算机修改后,在改VC++和附加依赖项
|在这里插入图片描述
在这里插入图片描述

问题2:无法查找或打开PDB文件
参考:https://www.cnblogs.com/wxl845235800/p/7206767.html

项目里的注释有些是源码注释翻译的,有些是自己照着案例pdf手动添加的,新手可能看起来友好些
先上效果图。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
1.新建C++空项目。设置工程名为CartoonSkin
在这里插入图片描述
2.添加.cpp文件,文件名main_desktop.cpp

// 为了让程序在高分辨率摄像机上运行得更快,可用vc::VideoCaptrue::set()将摄像机的分辨率设为640x480.
// 由于摄像机的模式、驱动,或操作系统不同,OpenCV可能无法改变某些摄像机属性。
const int DESIRED_CAMERA_WIDTH = 640;
const int DESIRED_CAMERA_HEIGHT = 480;

const int NUM_STICK_FIGURE_ITERATIONS = 40; // 设置检测皮肤时显示的简笔人物画面应显示多长时间.

const char *windowName = "Cartoonifier";   // 在GUI窗口中显示的名称


// 如果你想看线条图而不是绘画,设置为true。
bool m_sketchMode = false;
// 如果您想将皮肤颜色更改为外星p皮肤,请将其设置为true。
bool m_alienMode = false;
// 如果你想要一个邪恶的傻屌角色而不是一个善良的傻屌角色,设置为true。
bool m_evilMode = false;
// 如果您希望看到创建了许多窗口,显示各种调试信息,请将其设置为true。否则设置为0
bool m_debugMode = false;

#include <cstdio>
#include <cstdlib>

// Include OpenCV's C++ Interface
#include <opencv2/opencv.hpp> 

// 包括我们其余的代码!
//#include "detectObject.h"       // 容易检测人脸或眼睛(使用LBP或Haar Cascades)。
#include "cartoon.h"              // 卡通化一张图片.
//#include "ImageUtils.h"           // Shervin's handy OpenCV utility functions.

using namespace cv;
using namespace std;

int m_stickFigureIterations = 0;  // 为用户的脸绘制一个简笔画轮廓。

#ifndef VK_ESCAPE
#define VK_ESCAPE 0x1B      // Escape character (27)
#endif

// 使用网络摄像头
void initWebcam(VideoCapture &videoCapture, int cameraNumber)
{
	// 访问默认相机.
	try {   // 用try/catch块包围OpenCV调用,这样我们就可以给出有用的错误消息!
		//加载视频文件与直接从摄像机获得视频的不同之处在于创建cv::VideoCapture对象时,应将视频文件名(例如:camera.open("my_video.avi"))而不是摄像机编号(例如:camera.open(0))作为参数。
	    //这两种方法创建cv::VideoCapture对象,使用方式都一样。
		videoCapture.open(cameraNumber);
	}
	catch (cv::Exception &e) {}
	if (!videoCapture.isOpened()) {
		cerr << "ERROR: Could not access the camera!" << endl;
		exit(1);
	}
	cout << "Loaded camera " << cameraNumber << "." << endl;
}

// 键盘按键事件处理程序。注意,为了更好地支持Linux,它应该是“char”而不是“int”.
void onKeypress(char key)
{
	switch (key) {
	case 's':
		m_sketchMode = !m_sketchMode;
		cout << "Sketch / Paint mode: " << m_sketchMode << endl;
		break;
	case 'a':
		m_alienMode = !m_alienMode;
		cout << "Alien / Human mode: " << m_alienMode << endl;
		// 当外星皮肤启用时,显示一个简笔画轮廓,以便用户将脸放在正确的位置。
		if (m_alienMode) {
			m_stickFigureIterations = NUM_STICK_FIGURE_ITERATIONS;
		}
		break;
	case 'e':
		m_evilMode = !m_evilMode;
		cout << "Evil / Good mode: " << m_evilMode << endl;
		break;
	case 'd':
		m_debugMode = !m_debugMode;
		cout << "Debug mode: " << m_debugMode << endl;
		break;
	}
}
int main(int argc, char *argv[])
{
	cout << "Cartoonifier, by Shervin Emami (www.shervinemami.info), June 2012." << endl;
	cout << "Converts real-life images to cartoon-like images." << endl;
	cout << "Compiled with OpenCV version " << CV_VERSION << endl;
	cout << endl;

	cout << "Keyboard commands (press in the GUI window):" << endl;
	cout << "    Esc:  Quit the program." << endl;
	cout << "    s:    change Sketch / Paint mode." << endl;
	cout << "    a:    change Alien / Human mode." << endl;
	cout << "    e:    change Evil / Good character mode." << endl;
	cout << "    d:    change debug mode." << endl;
	cout << endl;

	//可简单调用cv::VideoCapture对象的open()方法(它是访问摄像设备的OpenCV方法)来访问计算机的摄像头或摄像机。
	//将默认的摄像机编号0传递给此函数。一些计算机有多个摄像机或将0作为默认摄像机编号使程序不能运行
	//解决这类问题的通常做法是将用户指定摄像机编号作为命令行参数,比如:若想指定摄像机编号为1、2或-1,这种方法就比较恰当。
	int cameraNumber = 0;
	if (argc > 1) {
		cameraNumber = atoi(argv[1]);
	}

	VideoCapture camera;
	initWebcam(camera, cameraNumber);

	// 为了让程序在高分辨率摄像机上运行得更快,可用vc::VideoCaptrue::set()将摄像机的分辨率设为640x480.
    // 由于摄像机的模式、驱动,或操作系统不同,OpenCV可能无法改变某些摄像机属性。
	camera.set(CV_CAP_PROP_FRAME_WIDTH, DESIRED_CAMERA_WIDTH);
	camera.set(CV_CAP_PROP_FRAME_HEIGHT, DESIRED_CAMERA_HEIGHT);

	// 创建一个GUI窗口在屏幕上显示
	namedWindow(windowName); // 可调整大小的窗口,可能无法在Windows上工作

    // 永远运行,直到用户点击Escape以“break”跳出这个循环
	while (true) {
		//在摄像机被初始化后,可通过cv::Mat对象(它是OpenCV的图像容器)来获取摄像机的图像。
		//可通过使用C++流运算符将cv::VideoCapture对象转换成cv::Mat对象,由此可获取摄像机的每帧,这就像从控制台获取输入一样。
		Mat cameraFrame;
		camera >> cameraFrame;
		if (cameraFrame.empty()) {
			cerr << "ERROR: Couldn't grab a frame." << endl;
			exit(1);
		}
		//创建一个空白的输出图像,我们将在上面绘制
		Mat displayedFrame(cameraFrame.size(), CV_8UC3);

		// 启用debug type 2(用于桌面),如果调试模式可用
		int debugType = 0;
		if (m_debugMode)
			debugType = 2;

		// 使用选定的模式运行漫画家过滤器
		cartoonifyImage(cameraFrame, displayedFrame, m_sketchMode, m_alienMode, m_evilMode, debugType);

		// 在短时间内显示人脸的简笔轮廓,这样用户就知道该把脸放在哪里。
		if (m_stickFigureIterations > 0) {
			drawFaceStickFigure(displayedFrame);
			m_stickFigureIterations--;
		}

		//可以调用cv::imshow()方法用OpenCV在屏幕上显示一个GUI窗口,单每显示一帧图像后必须调用cv::waitKey(0)方法,否则GUI窗口根本不会更新!
		imshow(windowName, displayedFrame);

		//cv::waitKey(0)会使程序暂停除非用户按下任意一个键,若将该函数的参数设为一个正数
		//例如:waitKey(20)或更大的值,则程序将会暂停一段时间,这段时间长度为该正数值个毫秒单位。
		char keypress = waitKey(20);// 如果你想看任何东西,这是必要的!
		if (keypress == VK_ESCAPE) {  //Escape key,前面定义了一个宏
			// 退出程序!
			break;
		}
		// 处理任何其他键
		if (keypress > 0) {
			onKeypress(keypress);
		}
	} //end while
	return EXIT_SUCCESS;
} 

3.添加.h文件,文件为cartoon.h

/*****************************************************************************
*   cartoon.h
*   Create a cartoon-like or painting-like image filter.
******************************************************************************
*   by Shervin Emami, 5th Dec 2012 (shervin.emami@gmail.com)
*   http://www.shervinemami.info/
******************************************************************************
*   Ch1 of the book "Mastering OpenCV with Practical Computer Vision Projects"
*   Copyright Packt Publishing 2012.
*   http://www.packtpub.com/cool-projects-with-opencv/book
*****************************************************************************/
#pragma once

#ifndef _CARTOONSKIN_H_
#define _CARTOONSKIN_H_

#include <stdio.h>
#include <iostream>
#include <vector>

// Include OpenCV's C++ Interface
#include "opencv2/opencv.hpp"

using namespace cv;
using namespace std;

// 将给定的照片转换成卡通或绘画样的图像。
// 如果你想画线条而不是绘画,把sketchMode设为true。
// 如果你想要外星人的皮肤而不是人类的皮肤,将alienMode设置为true。
// 如果你想要一个“邪恶”的角色而不是一个“善良”的角色,将邪恶模式设置为true。
// 将debugType设置为1以显示从何处获取皮肤颜色,将2设置为在新窗口中显示皮肤掩码(用于桌面)
void cartoonifyImage(Mat srcColor, Mat dst, bool sketchMode, bool alienMode, bool evilMode, bool debugType);

void changeFacialSkinColor(Mat smallImgBGR, Mat bigEdges, int debugType);
void removePepperNoise(Mat &mask);
//画一个反锯齿的脸部轮廓,这样用户就知道他们的脸应该放在哪里
void drawFaceStickFigure(Mat dst);
#endif // !

4添加.cpp文件,文件名为cartoon.cpp

#include "cartoon.h"

// 将给定的照片转换成卡通或绘画样的图像。
// 如果你想画线条而不是绘画,把sketchMode设为true。
// 如果你想要外星人的皮肤而不是人类的皮肤,将alienMode设置为true。
// 如果你想要一个“邪恶”的角色而不是一个“善良”的角色,将邪恶模式设置为true。
// 将debugType设置为1以显示从何处获取皮肤颜色,将2设置为在新窗口中显示皮肤掩码(用于桌面)
void cartoonifyImage(Mat srcColor, Mat dst, bool sketchMode, bool alienMode, bool evilMode, bool debugType) {
	// 将OpenCV默认的GBR格式转换为灰度格式
	Mat srcGray;
	cvtColor(srcColor, srcGray, CV_BGR2GRAY);

	// 在开始检测边缘之前,使用良好的中值滤波去除像素噪声.同时还可让边缘锐化,而且比双边滤波器的效率高。
	const int MEDIAN_BLUE_FILTER_SIZE = 7;
	medianBlur(srcGray, srcGray, MEDIAN_BLUE_FILTER_SIZE);

	Size size = srcColor.size();
	Mat mask = Mat(size, CV_8U);
	Mat edges = Mat(size, CV_8U);

	if (!evilMode) {
		// 生成一个漂亮的边缘掩码,类似于铅笔线绘制.
		// Laplacian滤波器所提取的边缘最接近手工素描,并且它与Canny边缘检测一样,可得到清晰的素描效果。
	    // 但Canny边缘检测更容易受视频帧中随机噪声影响,从而使得素描边缘在不同帧之间经常有剧烈变化。
		const int LAPLACIAN_FILTER_SIZE = 5;
		Laplacian(srcGray, edges, CV_8U, LAPLACIAN_FILTER_SIZE);

		// Laplacian边缘滤波器能生成不同亮度的边缘,为了使边缘看起来更像素描,可采用二值化阈值来使边缘只有白色或黑色。
		const int EDGES_THRESHOLD = 80;
		threshold(edges, mask, EDGES_THRESHOLD, 255, THRESH_BINARY_INV);
	}
	else {// 邪恶模式,让一切看起来像一个可怕的傻屌.

		// 通过对边缘滤波器的恰当组合,可将和蔼的人物画像变成凶恶的人物画像,其技巧是通过使用小的边缘滤波器,
		// 这些滤波器能找到图像的各处边缘,然后通过中值滤波器来合并这些边缘。

		// 在去噪后的灰度图像上执行以上操作,之前将原题像转换为灰度图像的代码和7x7的中值滤波器都可以用上。
		// 接下来不用Laplacian滤波器和二值化阈值这种组合方式,而是沿着x和y方向采用3x3的Scharr梯度滤波器
		Mat edges2;
		Scharr(srcGray, edges, CV_8U, 1, 0);
		Scharr(srcGray, edges2, CV_8U, 1, 0, -1);
		edges += edges2;  //Combine the x & y edges together.

		//然后再采用截断值很低的二值化阈值方法,最后用3x3的中值平滑滤波就可得到“怪物”掩码
		const int EVIL_EDGE_THRESHOLD = 12;
		threshold(edges, mask, EVIL_EDGE_THRESHOLD, 255, THRESH_BINARY_INV);
		medianBlur(mask, mask, 3);
	}

	//imshow("edges", edges);
	//imshow("mask", mask);

	// 对于素描模式,我们仅需要掩码
	if (sketchMode) {
		// 输出图像有3个通道,而不是一个通道.
		cvtColor(mask, dst, CV_GRAY2BGR);
		return;
	}

	//强大的双边滤波器可平滑平坦区域,同时保持边缘锐化,因此,它作为一个自动的卡通化或图像滤波很不错,其缺点是效率低。
	//最重要的技巧是在低分辨率下使用双边滤波器,这会得到与高分辨率下相识的结果,但运行速度更快。
	//可将分辨率减少为原图像的四分之一。
	Size smallSize;
	smallSize.width = size.width / 2;
	smallSize.height = size.height / 2;
	Mat smallImg = Mat(smallSize, CV_8UC3);
	resize(srcColor, smallImg, smallSize, 0, 0, INTER_LINEAR);

	//可通过多个小型双边滤波器来代替一个大型双边滤波器,从而在较短时间内得到很好的卡通效果。
	//截断滤波器会使用滤波器的主要部分,而不会浪费时间在小部分上,滤波器效率提高几倍。
	//控制双边滤波器可使用的4个参数分别是:色彩强度、位置强度、大小、重复技术。
	//bilateralFilter()函数不能覆盖它的输入值(这称为“就地处理”),因此需要一个临时的Mat变量,
	//该变量在一个滤波器中作为输入变量而在另一个滤波器中作为输入变量
	Mat tmp = Mat(smallSize, CV_8UC3);
	int repetitions = 7;        // Repetitions for strong cartoon effect.
	for (int i = 0; i < repetitions; i++) {
		int ksize = 9;           // Filter size. Has a large effect on speed.
		double sigmaColor = 9;  // Filter color strength.
		double sigmaSpace = 7;  // Positional strength. Effects speed.
		bilateralFilter(smallImg, tmp, ksize, sigmaColor, sigmaSpace);
		bilateralFilter(tmp, smallImg, ksize, sigmaColor, sigmaSpace);
	}
	if (alienMode) {
		// 当给定缩小的BGR图像和全分辨率边缘蒙版时,应用“alien”过滤器。
		// 检测图像中间像素的颜色,然后将该区域的颜色更改为绿色。
		changeFacialSkinColor(smallImg, edges, debugType);
	}

	//注意,该处理过程使用的是缩小后的图像,因此,在处理后需将图像恢复到原来的大小,然后叠加前面得到的边缘掩码。
	resize(smallImg, srcColor, size, 0, 0, INTER_LINEAR);

	//为了将边缘掩码(即素描)叠加到由双边滤波器所产生的图画上,需将目标变量dst的所有元素全置为0(即目标变量对应的图像全置为黑色)
	memset((char*)dst.data, 0, dst.step * dst.rows);

	//然后将源图像的像素复制到变量dst中,与“素描“掩码对应的源图像边缘的像素不会被复制。
	srcColor.copyTo(dst, mask);
}

//可正在图像进行卡通化之后再用外星人滤波器获得像卡通化一样的外星人模式:也就是说
//可获取缩小彩色图像和全尺寸的边缘掩码,其中彩色图像由双边滤波器产生
//皮肤检测通常在低分辨率下工作较好,因为它与分析高分辨率像素的近邻的平均值等价(也可看作是用低频信号代替高频噪声信号)
//接下来的工作是在对图像进行缩放后进行的,其缩放大小与用双边滤波器处理图像时一样(即只取图像一般的高度和宽度)

//无需去检测皮肤颜色然后看此区域是否有这样的颜色来确定皮肤区域数,而是直接使用OpenCV的floodFill()函数,该函数类似于一些图像处理软件中的颜料桶工具
//屏幕中间区域就是皮肤像素(因为在拍摄时会将人脸放在轮廓里,而人脸轮廓在整幅图像中间)
//为了使整个人脸变成绿色,只需对图像中心位置的像素进行绿色漫水填充。这样做至少会让人脸的某些部分变成绿色。.
void changeFacialSkinColor(Mat smallImgBGR, Mat bigEdges, int debugType) {
	//OpenCV的floodFill()函数有一个很好的特性:它会将漫水填充的效果存储到外部图像中而不修改输入图像
	//这一特性可得到一幅掩码图像,该图像可用来调整皮肤像素的颜色而不需要改变亮度和饱和度
	//也能在所有皮肤像素都为绿色的情况下得到更真实的图像。

	//在RGB颜色空间改变皮肤颜色效果不太好。因为改变皮肤颜色需要脸部图像的亮度变化,但皮肤颜色不允许变化太大,而RGB无法从色彩中获取亮度。
	//解决该问题的方法之一是采用HSV颜色空间,因为它能从色彩()色度和多彩(饱和度)中获取亮度
	//但HSV的色度取值以红色开始并以红色结束,若皮肤接近红色,就需要处理小于10%和大于90%的色度值,因为在这两个范围内都是红色
	//可使用Y'CrCb颜色空间(在OpenCV中,它是YUV的变种)来解决问题
	//Y'CrCb颜色空间不仅能从颜色中获取亮度,而且对于通常的皮肤颜色其取值唯一
	//实际上对于大多数摄像机,图像和视频在转换成RGB前都使用某种YUV颜色空间,所以在很多情况下不需要转换就可得到YUV格式的图像

	//可正在图像进行卡通化之后再用外星人滤波器获得像卡通化一样的外星人模式:也就是说,可获取缩小彩色图像和全尺寸的边缘掩码,其中彩色图像由双边滤波器产生
	//皮肤检测通常在低分辨率下工作较好,因为它与分析高分辨率像素的近邻的平均值等价(也可看作是用低频信号代替高频噪声信号)
	//接下来的工作是在对图像进行缩放后进行的,其缩放大小与用双边滤波器处理图像时一样(即只取图像一般的高度和宽度)

	//先将彩色图像转换为YUV格式
	Mat yuv = Mat(smallImgBGR.size(), CV_8UC3);
	cvtColor(smallImgBGR, yuv, CV_BGR2YCrCb);

	//另外还需要收缩边缘掩码,使其与彩色图像有相同的尺寸。
	//当存储一幅单独的掩码图像时,若用OpenCV的floodFill()函数会有困难,即掩码图像应在整个图像周围用1个像素作边界
	//若输入图像大小为W x H个像素,则单独的掩码图像大小为(W+2) x (H+2)个像素
	//但floodFill函数也允许初始化边缘掩码来确保漫水填充算法不会越界,这一特性可阻止漫水填充的区域扩展到人脸外面
	//因此,需要两幅掩码图像:大小为W x H的边缘掩码图像和大小为(W+2) x (H+2)的边缘掩码图像,该掩码图像包含了图像的边界
	//可让多个cv::Mat对象(或头部)引用同一数据,甚至可让一个cv::Mat对象引用另一个cv::Mat图像的某一区域
	//因此不必分配两个单独的图像,然后复制边缘掩码像素给它们,只需分配一个包含边界的掩码图像并创建一个大小为W x H的cv::Mat头(仅用它来引用没有边界的掩码图像)
	//也就是说,仅有一个(W+2) x (H+2)大小的像素数组,但有两个cv::Mat对象,一个用来指向大小为(W+2) x (H+2)的图像,另一个用来指向图像中间大小为W x H的区域
	int sw = smallImgBGR.cols;
	int sh = smallImgBGR.rows;
	Mat maskPlusBorder = Mat::zeros(sh + 2, sw + 2, CV_8U);
	Mat mask = maskPlusBorder(Rect(1, 1, sw, sh));  // mask is a ROI in maskPlusBorder.
	resize(bigEdges, mask, smallImgBGR.size());  //Put bigEdges in both of them

	// 整个边缘掩码既有强边缘也有弱边缘,可用二值化阈值法来得到所需的强边缘
	// 为了在边缘间加入一些间隙,可采用形态学算子dilate()和erode()来删除一些间隙(也会涉及"close"算子)
	const int EDGEG_THRESHOLD = 80;
	// 将掩码值设置为0或255,以移除弱边缘.
	threshold(mask, mask, EDGEG_THRESHOLD, 255, THRESH_BINARY);
	// 如果边缘之间存在像素间隙,则将它们连接在一起。
	dilate(mask, mask, Mat());
	erode(mask, mask, Mat());
	//imshow("constraints for floodFill", mask);

	//需要对人脸的许多像素点使用漫水填充算法,从而保证人脸图像的各种颜色和整个阴影都能被处理
	//可在鼻子、脸颊和前额选择6个点,注意,这些点的位置依赖于早期所确定的面板轮廓
	int const NUM_SKIN_POINTS = 6;
	Point skinPts[NUM_SKIN_POINTS];
	skinPts[0] = Point(sw / 2,           sh / 2 - sh / 6);
	skinPts[1] = Point(sw / 2 - sw / 11, sh / 2 - sh / 6);
	skinPts[2] = Point(sw / 2 + sw / 11, sh / 2 - sh / 6);
	skinPts[3] = Point(sw / 2,           sh / 2 + sh / 16);
	skinPts[4] = Point(sw / 2 - sw / 9,  sh / 2 + sh / 16);
	skinPts[5] = Point(sw / 2 + sw / 9,  sh / 2 + sh / 16);

	//现在仅需为漫水填充算法找到一些好的下界和上界。
	//漫水填充算法基于Y'CrCb颜色空间,因此基本上可决定亮度、红色分量和蓝色分量有多大变化
	//这里需要包括阴影和高度显示以及反射在内的亮度变化较大,而颜色不需要变化
	const int LOWER_Y = 60;
	const int UPPER_Y = 80;
	const int LOWER_Cr = 25;
	const int UPPER_Cr = 15;
	const int LOWER_Cb = 20;
	const int UPPER_Cb = 15;
	Scalar lowerDiff = Scalar(LOWER_Y, LOWER_Cr, LOWER_Cb);
	Scalar upperDiff = Scalar(UPPER_Y, UPPER_Cr, UPPER_Cb);

	//不要在“yuv”图像中绘制,只需在“maskPlusBorder”图像中绘制1,以便稍后应用
	//调用floodFill()时,为了存储外部掩码而必须为flags参数指定FLOODFILL_MASK_ONLY选项,该参数的其他选项均用默认值
	const int CONNECTED_COMPONENTS = 4;  //To fill diagonality, use 8
	const int flags = CONNECTED_COMPONENTS | FLOODFILL_FIXED_RANGE | FLOODFILL_MASK_ONLY;
	Mat edgeMask = mask.clone();    // Keep an duplicate copy of the edge mask.
	// The "maskPlusBorder" is initialized with the edges, because floodFill() will not go across non-zero mask pixels.
	for (int i = 0; i < NUM_SKIN_POINTS; i++) {
		floodFill(yuv, maskPlusBorder, skinPts[i], Scalar(), NULL, lowerDiff, upperDiff, flags);
		if (debugType >= 1)
			circle(smallImgBGR, skinPts[i], 5, CV_RGB(0, 0, 255), 1, CV_AA);
	}
	if (debugType >= 2)
		imshow("flood mask", mask * 120); // 将边缘绘制为白色,皮肤区域绘制为灰色.

	//图像变量mask包含:值为255的边缘像素,值为1的皮肤像素,剩下是值为0的像素
	//变量edgeMask仅包含边缘像素(其值为255)
	//为了得到皮肤像素,可从变mask中去掉边缘
	mask -= edgeMask;
	//图像变量mask现在仅包含值为1的皮肤像素和值为0的非皮肤像素。

	//为了改变原图像的皮肤颜色和亮度,可用cv::add来增加原图绿色分量,其区域由皮肤掩码(变量mask)决定
	int Red = 0;
	int Green = 70;
	int Blue = 0;
	add(smallImgBGR, Scalar(Blue, Green, Red), smallImgBGR, mask);
	//如果仅改变皮肤颜色而不使其变得更亮,可使用其他颜色变化方法
	//例如:将颜色分量加70,而将红色和蓝色分量减70,或使用cvtColor(src, dst, "CV_BGR2HSV_FULL")将图像转换成HSV颜色空间,然后调整色度和饱和度
}

//开启外星人模式的第一步是在相机屏幕上方画出人脸轮廓,以便用户知道将人脸放置在这个位置。
void drawFaceStickFigure(Mat dst)
{
	Size size = dst.size();
	int sw = size.width;
	int sh = size.height;

	// 在黑色背景上绘制彩色的脸
	Mat faceOutline = Mat::zeros(size, CV_8UC3);
	Scalar color = CV_RGB(255, 255, 0);   // Yellow
	int thickness = 4;
	// 以固定的宽高比0.72来画一个大椭圆,它占整个图像高度的70%,这个比率不会使人脸看上去太瘦或太胖.
	int faceH = sh / 2 * 70 / 100;  // "faceH" is actually half the face height (ie: radius of the ellipse).
	// 缩放宽度,使其与任何屏幕宽度的形状相同(基于屏幕高度)
	int faceW = faceH * 72 / 100;
	// 画脸部轮廓线
	ellipse(faceOutline, Point(sw / 2, sh / 2), Size(faceW, faceH), 0, 0, 360, color, thickness, CV_AA);

	//为了让人脸看上去更加明显,可再画两个眼睛的轮廓。
	//为了让所画的眼睛看上去更加真实,不要直接用椭圆作为眼睛轮廓,而是用上椭圆作为眼睛的上半部分,
	//用下椭圆作为眼睛的下半部分,可通过制定起始位置和角度来实现。
	int eyeW = faceW * 23 / 100;
	int eyeH = faceH * 11 / 100;
	int eyeX = faceW * 48 / 100;
	int eyeY = faceH * 13 / 100;
	//设置眼睛半椭圆的角度和位移
	int eyeA = 15; // 角度
	int eyeYshift = 11;
	// 画右眼的上半部分
	ellipse(faceOutline, Point(sw / 2 - eyeX, sh / 2 - eyeY), Size(eyeW, eyeH), 0, 180 + eyeA, 360 - eyeA, color, thickness, CV_AA);
	// 画右眼的下半部分
	ellipse(faceOutline, Point(sw / 2 - eyeX, sh / 2 - eyeY - eyeYshift), Size(eyeW, eyeH), 0, 0 + eyeA, 180 - eyeA, color, thickness, CV_AA);
	//画左眼的上半部分
	ellipse(faceOutline, Point(sw / 2 + eyeX, sh / 2 - eyeY), Size(eyeW, eyeH), 0, 180 + eyeA, 360 - eyeA, color, thickness, CV_AA);
	//画左眼的下半部分
	ellipse(faceOutline, Point(sw / 2 + eyeX, sh / 2 - eyeY - eyeYshift), Size(eyeW, eyeH), 0, 0 + eyeA, 180 - eyeA, color, thickness, CV_AA);

	//可用同样方法来画下嘴唇
	int mouthY = faceH * 53 / 100;
	int mouthW = faceW * 45 / 100;
	int mouthH = faceH * 6 / 100;
	ellipse(faceOutline, Point(sw / 2, sh / 2 + mouthY), Size(mouthW, mouthH), 0, 0, 180, color, thickness, CV_AA);

	//为了提示用户将脸放在指定位置上,可在屏幕上显示一条提示信息
	int fontFace = FONT_HERSHEY_COMPLEX;
	float fontScale = 1.0f;
	int fontThickness = 2;
	putText(faceOutline, "Put your face here", Point(sw * 23 / 100, sh * 10 / 100), fontFace, fontScale, color, fontThickness, CV_AA);
	//imshow("faceOutline", faceOutline);

	//现在可将已画好的人脸轮廓叠加到要显示的图像上
	//叠加过程采用alpha融合来将卡通化图像与该轮廓结合在一起
	addWeighted(dst, 1.0, faceOutline, 0.7, 0, dst, CV_8UC3);
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值