Opencv實戰【4】——圖片動漫化處理

動漫化風格的特點

(1)動漫中的細節相對少;
(2)動漫中的邊緣輪廓更突出;
(3)動漫的色彩更鮮豔;

處理手段

(1)突出邊緣線條
利用canny算子找出邊緣,然後利用copyTo函數將邊緣加到原圖上。
這裏用到copyTo函數的第二種用法:

copyTo(roi , mask)

作用是把mask和image重疊以後把mask中像素值爲非0(black)的點對應的image中的點變爲透明,而保留爲0的點。
然而我們發現,canny算子得到的結果是二值化圖,其中邊緣像素值爲255,是白色,非邊緣像素是0,是黑色。
若是直接將canny算子得到的結果進行累加,則會是這樣的結果。
結果

所以我們要進行處理:

change_g_cannyImage = 255 - g_cannyImage;

非邊緣轉化爲255,邊緣轉化爲0;非邊緣會在之後的處理會變爲透明,而邊緣則會保持原有的數據0,爲黑色,這樣就會有強化邊緣的效果。
主要代碼:

//【1】運行canny算子
	Canny(g_grayImage, g_cannyImage, g_nThresholdValue, g_nThresholdValue/3, 3);
	cv::Mat g_canny3Image(g_srcImage.rows, g_srcImage.cols, CV_8UC3, cv::Scalar(0,0,0));
	//【2】貼圖
	//(將邊緣變爲黑色)
	Mat change_g_cannyImage;
	change_g_cannyImage = 255 - g_cannyImage;
	//將單通道轉化爲三通道
	cvtColor(change_g_cannyImage, g_canny3Image, COLOR_GRAY2BGR);
	//image.copyTo(imageROI,mask), 作用是把mask和image重疊以後把mask中像素值爲0(black)的點對應的image中的點變爲透明,而保留其他點。
	Mat bianyuan_dst;
	g_srcImage.copyTo(bianyuan_dst, g_canny3Image);

效果:
效果
(2)弱化與去除細節
這裏的細節是除了邊緣外的,只在區域的內部進行弱化。這時就需要使用雙邊濾波。

所謂“細節”,從圖像處理的角度看來就是圖像中的高頻成分。要想去除高頻成分,自然而然就要用到濾波(filtering)的方法。常用的濾波器有均值濾波器、高斯濾波器、中值濾波器等。但是,這些常用濾波器都有一個共同的問題——會弱化所有的高頻信息。而很不幸的是,圖像中的邊緣也屬於高頻信息(因爲邊緣意味着圖像在這裏產生了突變,突變就意味着高頻)。因此常用濾波器會將我們本應突出的邊緣一起弱化模糊。
這種情況下就要讓雙邊濾波器(Bilateral filter)出場了。這種濾波器的特點是可以“保邊濾波”(或者叫“區域平滑”,Region smoothing)。顧名思義,就是可以只模糊區域內部而保留清晰的邊緣。爲了搞明白雙邊濾波器爲什麼有這樣的效果,首先來說一下高斯濾波器。高斯濾波器,或者說高斯濾波模板,其中的各個點的值僅與該點到模板中心點的空間距離有關,而並沒有考慮各個點與中心點的相似度(即像素值的接近程度),這樣就導致無論是變化不大的區域內部點,還是突變的邊緣點,只要和中心的距離相同,那就同等對待。
而雙邊濾波器就是在高斯濾波器基礎上加上了相似度權重,在高斯濾波模板的每個點上再乘以一個與中心點的相似度係數(即“相似度權重”),從而將邊緣與內部區分處理。相似度權重計算方法和高斯濾波模板中各點值(可以稱爲“高斯權重”)的計算方法相同,只不過高斯權重是將該點到中心的距離代入高斯函數計算,而相似度權重是將該點與中心的像素相似度(比如該點像素值與中心像素值的歐氏距離,或者直接求二者的差值)代入高斯函數計算得到。

//【3】雙邊濾波
	Mat lvbo_dst;
	bilateralFilter(bianyuan_dst, lvbo_dst, g_nkernelValue, g_nkernelValue * 2, g_nkernelValue / 2);

(3)讓圖像色彩更鮮豔
提高色彩飽和度。
方法:
1、RGB轉HSV,且提取原圖像的S通道
2、乘上一個大於1的數,並且對值進行限幅
3、將修改後的S通道替換掉原本的S通道,並將3個通道合併
4、HSV轉爲RGB

//【4】修改圖像的顏色的飽和度
	Mat hsv_image,hsv_dst;
	cvtColor(lvbo_dst, hsv_image, COLOR_BGR2HSV);
	vector<Mat> channels;
	split(hsv_image, channels);
	Mat S_Mat;
	float k = g_nS*1.0f / 100;
	channels.at(1).copyTo(S_Mat);
	cv::Mat S_dst(S_Mat.rows, S_Mat.cols, CV_8UC1, cv::Scalar(0));
	//S_dst = S_Mat * k;
	H_mul_k(&S_Mat, &S_dst,k);
	//將修改後的S與原來的H,V進行merge
	channels[1] = S_dst.clone();	//深複製
	merge(channels, hsv_dst);
	//將修改後的HSV轉爲RGB圖
	Mat RGB_dst;
	cvtColor(hsv_dst, RGB_dst, COLOR_HSV2BGR);

代碼

#include <opencv2/opencv.hpp>
#include "opencv2/features2d.hpp"
#include <iostream>
#include "windows.h"
#include <stdio.h>
#include <time.h>
#include <math.h>  
#define WINDOW_NAME "【程序窗口】"			
using namespace cv;
using namespace std;
//RNG g_rng(12345);


//對照片進行動漫化一般需要四個步驟
//1、邊緣檢測
//2、將邊緣檢測得到的邊緣 以黑色的形式貼在原來的畫上。
//3、對貼了邊緣的圖進行雙邊濾波,雙邊濾波可以較好的濾波的同時保留邊緣。
//4、修改圖像的顏色的飽和度,本文采用的是將RGB轉化爲HSV空間,然後調整S分量。

//*--------------------------【全局變量聲明】-------------------------------------*/
int g_nThresholdValue = 100;	//canny參數值
int g_nkernelValue = 3;	//雙邊濾波核大小
int g_nS = 100;	//雙邊濾波核大小
Mat g_srcImage, g_grayImage,g_cannyImage,g_dstImage;

//*--------------------------【全局函數聲明】-------------------------------------*/
void on_CannyThreshold(int, void*);	//回調函數

/****照片動漫化示例**********/
int main()
{
	//載入原圖
	g_srcImage = imread("D:\\opencv_picture_test\\HOG行人檢測\\timg.jpg");//加載原圖
	if (g_srcImage.empty())
	{
		printf("Could not find the image!\n");
		return -1;
	}
	g_grayImage.create(g_srcImage.size(), g_srcImage.type());		//創建一個同大小類型的矩陣
	cvtColor(g_srcImage, g_grayImage,COLOR_BGR2GRAY);
	//imshow("【原圖的灰度圖】", g_grayImage);
	//進行均值濾波操作
	blur(g_grayImage, g_grayImage, Size(3, 3));
	namedWindow(WINDOW_NAME, WINDOW_NORMAL);//WINDOW_NORMAL允許用戶自由伸縮窗口
		//【3】創建窗口 並 顯示原圖
	//imshow("原始圖", g_srcImage);
	//【4】創建滑動條來控制閾值
	createTrackbar("canny參數值", WINDOW_NAME, &g_nThresholdValue, 255, on_CannyThreshold);
	createTrackbar("雙邊濾波核", WINDOW_NAME, &g_nkernelValue, 25, on_CannyThreshold);
	createTrackbar("S擴大", WINDOW_NAME, &g_nS, 300, on_CannyThreshold);
	//【5】初始化自定義的閾值回調函數
	on_CannyThreshold(0, 0);
	while (1)
	{
		if (waitKey(10) == 27) break;		//按下Esc 退出
	}
	return 0;

}

void H_mul_k(Mat* srcImage, Mat* dstImage, float k)
{
	int height = (*srcImage).rows;
	int width = (*srcImage).cols;
	for (int j = 0; j < height; j++)
	{
		for (int i = 0; i < width; i++)
		{
			int zhi = (*srcImage).at<uchar>(j, i) * k;
			if (zhi >= 255) zhi = 255;
			else if (zhi <= 0) zhi = 0;
			else zhi = zhi;
			(*dstImage).at<uchar>(j, i) = zhi;
		}
	}
}
//*--------------------------【on_Threshold 函數】-------------------------------------*/
void on_CannyThreshold(int, void*)
{
	//【1】運行canny算子
	Canny(g_grayImage, g_cannyImage, g_nThresholdValue, g_nThresholdValue/3, 3);
	cv::Mat g_canny3Image(g_srcImage.rows, g_srcImage.cols, CV_8UC3, cv::Scalar(0,0,0));
	//【2】貼圖
	//將canny圖反轉(將邊緣變爲黑色)
	Mat change_g_cannyImage;
	//change_g_cannyImage = g_cannyImage < 100;		//非邊緣轉化爲255,邊緣轉化爲0;非邊緣會在之後的處理會變爲透明,而邊緣則會保持原有的數據0
	change_g_cannyImage = 255 - g_cannyImage;
	//將單通道轉化爲三通道
	cvtColor(change_g_cannyImage, g_canny3Image, COLOR_GRAY2BGR);
	//image.copyTo(imageROI,mask), 作用是把mask和image重疊以後把mask中像素值爲0(black)的點對應的image中的點變爲透明,而保留其他點。
	Mat bianyuan_dst;
	g_srcImage.copyTo(bianyuan_dst, g_canny3Image);
	//【3】雙邊濾波
	Mat lvbo_dst;
	bilateralFilter(bianyuan_dst, lvbo_dst, g_nkernelValue, g_nkernelValue * 2, g_nkernelValue / 2);
	//【4】修改圖像的顏色的飽和度
	Mat hsv_image,hsv_dst;
	cvtColor(lvbo_dst, hsv_image, COLOR_BGR2HSV);
	vector<Mat> channels;
	split(hsv_image, channels);
	Mat S_Mat;
	float k = g_nS*1.0f / 100;
	channels.at(1).copyTo(S_Mat);
	cv::Mat S_dst(S_Mat.rows, S_Mat.cols, CV_8UC1, cv::Scalar(0));
	//S_dst = S_Mat * k;
	H_mul_k(&S_Mat, &S_dst,k);
	//將修改後的S與原來的H,V進行merge
	channels[1] = S_dst.clone();	//深複製
	merge(channels, hsv_dst);
	//將修改後的HSV轉爲RGB圖
	Mat RGB_dst;
	cvtColor(hsv_dst, RGB_dst, COLOR_HSV2BGR);
	imshow(WINDOW_NAME, RGB_dst);
}

實現效果

原圖

漫畫圖

總結

這種方法其實只能對部分景象圖有比較好的漫畫效果,對人的臉部面容效果其實並不是很好。
似乎是有根據深度學習訓練出來的算法,能夠對人臉面容實現很好的動漫化:
如何用深度學習模型爲自己做個漫畫畫像
零門檻人像轉卡通、GIF表情包
這兩個鏈接並沒有相關代碼,原理還是很複雜的,以後再進行研究。

Reference:

圖片動漫化處理原理
opencv 照片動漫風格
RGB、HSV、HSI顏色空間
OpenCV cvtColor()函數
opencv中的merge函數
openCv——copyTo()的形式詳解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章