動漫化風格的特點
(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()的形式詳解