基於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 ([email protected])
*   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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章