【opencv實戰】影象素描及卡通化

shuiyixin發表於2018-07-18

       因為要做一個專案,為了實現他的趣味性,所以想應用影象處理做一些東西,最先想到的就是素描和卡通化,所以通過一番辛苦的查詢及改錯,最終完成一個簡單小功能。

一、實現原理 

        應用opencv將圖片進行卡通化處理,基本的思路是將圖片的內容部分進行平滑處理,然後讓邊緣部分更加突出。

        1.通過邊緣檢測濾波器獲得影象的黑白素描圖,這一步得到素描圖,併為卡通化做準備。

        2.通過雙邊濾波器獲得平滑後的影象。

        3.將素描圖覆蓋到平滑後的影象上就可以得到類似於卡通圖片的效果圖。

二、應用方法

邊緣檢測運算元

二進位制影象處理

中值濾波

雙邊濾波

三、實現步驟

1.框架

        我希望實現的是實時將視訊影象卡通化,所以需要通過opencv呼叫攝像頭,並對其進行一系列設定。(這部分是opencv基礎,在這裡不在贅述,後期我會逐步完善opencv自學教程,會在裡面有詳細的介紹。有不懂的大家可以評論留言,大家一起交流。)原始碼如下:

/*--------------------------------------攝像頭操作-------------------------------------*/
	int capNum = 0;
	if (argc > 1)
		capNum = atoi(argv[1]);

	// 開啟攝像頭
	VideoCapture cap;
	cap.open(capNum);
	if (!cap.isOpened())
	{
		cout<< "Error: Could not load the cap.";
		exit(1);
	}

	// 調整攝像頭的解析度
	cap.set(CV_CAP_PROP_FRAME_WIDTH, 640);
	cap.set(CV_CAP_PROP_FRAME_HEIGHT, 480);

        獲取到每一幀的影象後,需要對影象做一定的處理,因為用了兩種方式做處理分別得到:卡通化及素描。所以在處理之前加一個整形變數,允許使用者輸入,自由選擇處理方式,為了防止使用者非法輸入,我設定迴圈做判斷。輸入合法後才允許執行下面的程式碼。並通過Switch語句設定兩種處理方式。程式碼如下:

        int mode = -1;//動畫處理模式
	cout << "請輸入型別:";
	cin >> mode;
	while (mode<0 || mode >= 2)
	{
		cout << "處理模式輸入錯誤,請重新輸入:";
		cin >> mode;
	}

	switch (mode)
	{
	case 0:
		while (1)
		{
			cap >> Frame;
			if (!Frame.data)
			{
				cout << "Couldn't load cap frame.";
				exit(1);
			}

			// 建立一個用於存放輸出影象的Mat類
			//Mat output(Frame.size(), CV_8UC3);
			TransformCartoon(Frame, output);
			imshow("【卡通處理】", output);
			char keypress = waitKey(30);

			if (keypress == 27)
			{
				break;
			}
		}
		break;
	case 1:
		while (1)
		{
			cap >> Frame;
			if (!Frame.data)
			{
				cout << "Couldn't load cap frame.";
				exit(1);
			}
			TransformSketch(Frame, output);
			imshow("【素描圖】", output);
			char keypress = waitKey(30);

			if (keypress == 27)
			{
				break;
			}
		}
		break;
	default:
		break;
	}

    大家能夠發現,在上面的程式碼裡有兩個函式是自己編寫的,他們是:TransformSketch(Frame, output) 和TransformCartoon(Frame, output),第一個函式是將影象轉化為素描,第二個函式是將影象轉化為卡通圖。如果後期還有新的模式加入,是可以通過新增case來新增功能的。

        上面所有的東西是程式碼的整個框架,具體實現方法都在這兩個寫的函式裡。接下來,我講解一下兩個函式的內部實現。

2.素描圖

        首先先講一下素描,人在畫素描用的是黑色鉛筆,畫出來的是黑白畫,所以第一步是需要先將影象轉為灰度影象。然後應用中值濾波,中值濾波法是一種非線性平滑技術,它將每一畫素點的灰度值設定為該點某鄰域視窗內的所有畫素點灰度值的中值。通過這個操作可以將影象進行平滑處理。

        cvtColor(Frame, Frame, CV_BGR2GRAY);
	const int MEDIAN_BLUR_FILTER_SIZE = 7;
	medianBlur(Frame, Frame, MEDIAN_BLUR_FILTER_SIZE);

       要將一幅影象轉換為素描效果圖,還需要使用不同的邊緣檢測演算法實現。如經常使用的基於Sobel、Canny、Robert、Prewitt、Laplacian等運算元的濾波器均能夠實現這一操作,但處理效果各異。

        1.Sobel運算元:邊緣檢測中最經常使用的一種方法,在技術上它是以離散型的差分運算元,用來運算影象亮度函式的梯度的近似值,缺點是Sobel運算元並沒有將影象的主題與背景嚴格地區分開來,換言之就是Sobel運算元並沒有基於影象灰度進行處理,因為Sobel運算元並沒有嚴格地模擬人的視覺生理特徵,所以提取的影象輪廓有時並不能令人愜意。

        2.Robert運算元:依據任一相互垂直方向上的差分都用來預計梯度。Robert運算元採用對角方向相鄰畫素之差。

        3.Prewitt運算元:該運算元與Sobel運算元相似。僅僅是權值有所變化,但兩者實現起來功能還是有差距的,據經驗得知Sobel要比Prewitt更能準確檢測影象邊緣。

        4.Laplacian運算元:該運算元是一種二階微分運算元,若僅僅考慮邊緣點的位置而不考慮周圍的灰度差時可用該運算元進行檢測。

對於階躍狀邊緣。其二階導數在邊緣點出現零交叉,並且邊緣點兩旁的畫素的二階導數異號。

        5.Canny運算元:該運算元的基本效能比前面幾種要好。可是相對來說演算法複雜。

        Canny運算元是一個具有濾波。增強,檢測的多階段的優化運算元。在進行處理前。Canny運算元先利用高斯平滑濾波器來平滑影象以除去噪聲,Canny切割演算法採用一階偏導的有限差分來計算梯度幅值和方向,在處理過程中。Canny運算元還將經過一個非極大值抑制的過程。最後Canny運算元還採用兩個閾值來連線邊緣。

        相比Sobel等其它運算元。Canny和Laplacian運算元能得到更清晰的素描效果,而Laplacian的噪聲抑制要優於Canny邊緣檢測,而其實素描邊緣在不同幀之間經常有劇烈的變化,因此我們選擇Laplacian邊緣濾波器進行影象處理。

        Mat edges_Image;
	const int LAPLACIAN_FILTER_SIZE = 5;
	Laplacian(Frame, edges_Image, CV_8U, LAPLACIAN_FILTER_SIZE);

經過上述操作之後的影象如下圖所示。

        變換後的影象邊緣亮度不同,為了使影象更畫素描,作二進位制處理。

        const int EDGES_THRESHOLD = 80;
	threshold(edges_Image, output, EDGES_THRESHOLD, 255, THRESH_BINARY_INV);

之後的影象如下:

3.卡通圖

後面的卡通效果是在此基礎上接著完成的,你可以呼叫上面這個函式,也可以將程式碼重寫一遍。我選擇的是重寫,因為需要呼叫中間變數。

接下來需要生成彩色影象,並完成卡通效果。由於演算法比較複雜,為了提高執行效率,特將圖片縮小為原來的1/4。

        Size size = Frame.size();
	Size reduceSize;
	reduceSize.width = size.width / 2;
	reduceSize.height = size.height / 2;
	Mat reduceImage = Mat(reduceSize, CV_8UC3);
	resize(Frame, reduceImage, reduceSize);

接下來我們用到了雙邊濾波。雙邊濾波是一種非線性的濾波方法,是結合影象的空間鄰近度和畫素值相似度的一種折衷處理,同時考慮空間與資訊和灰度相似性,達到保邊去噪的目的,具有簡單、非迭代、區域性處理的特點。雙邊濾波的好處在於:對影象進行平滑時,能進行邊緣保護。

        Mat tmp = Mat(reduceSize, CV_8UC3);
	int repetitions = 7;
	for (int i = 0; i < repetitions; i++)
	{
		int kernelSize = 9;
		double sigmaColor = 9;
		double sigmaSpace = 7;
		bilateralFilter(reduceImage, tmp, kernelSize, sigmaColor, sigmaSpace);
		bilateralFilter(tmp, reduceImage, kernelSize, sigmaColor, sigmaSpace);
	}

弄完上面的處理,接下來就要將影象還原到原來的大小了。

        Mat magnifyImage;
	resize(reduceImage, magnifyImage, size);

為了得到更好的效果。在以上程式碼中加入下面函式。將恢復尺寸後的影象與上一部分的素描結果相疊加。得到卡通版的影象~~

        Mat dst;
	dst.setTo(0);
	magnifyImage.copyTo(dst, Binaryzation);

效果如下:

如果大家有什麼問題可以給我留言哦。

 

 

 

相關文章