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()的形式详解

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