当初做这个算法时,参考的是相机360中的卡通特效。实际查找资料时,我看很多资料也把这种效果称为矢量风格画,严格意义讲,我也说不上来到底该叫什么。找了些资料,也没有找到准确的定义。况且我做的效果,和矢量风格画以及卡通画还有些差距,说白了,并不是那么像。不过,虽然不像,但从效果上,我觉得各有千秋吧,实际对比发现,有些图片处理后,效果要优于相机360中的效果。
计算过程主要在y通道进行。如果在rgb空间分三个通道处理,计算过程将会非常耗时,所以需要由rgb空间转换至yuv空间,单独对y通道进行处理,最后在变换回rgb空间进行显示、输出。下面把算法大体描述一下。rgb转yuv,得到y通道,单独对y通道做边缘检测,然后计算结构张量,得到整幅图像的矢量场,即计算每个像素点的水平分量及垂直分量。有了矢量场,就可以调用线积分卷积算法进行滤波,得到具有手绘风格的笔触效果。在由yuv空间转回rgb空间过程中,还可以根据不同需求,生成不同风格的结果图,比如黑白图,彩色图等。
下面贴出算法整体调用逻辑代码,基本可以了解本文提出的卡通画特效算法思路。
void* ImageVectorStylizationThread(void *arg)
{
VectorStylizationInfo *vecstylization_info = (VectorStylizationInfo *)arg;
BMPINFO *pSrcBitmap = vecstylization_info->pSrcBitmap;
int style_index = vecstylization_info->style_index;
int color_index = vecstylization_info->color_index;
int thread_id = vecstylization_info->thread_id;
int width = pSrcBitmap->lWidth;
int height= pSrcBitmap->lHeight;
int size = width*height;
int mem_size = size * sizeof(float);
float *rdata = (float *)malloc(mem_size);
float *gdata = (float *)malloc(mem_size);
float *bdata = (float *)malloc(mem_size);
float *ydata = (float *)malloc(mem_size);
float *udata = (float *)malloc(mem_size);
float *vdata = (float *)malloc(mem_size);
// 数据转换
ConvertToFloat(pSrcBitmap, rdata, gdata, bdata);
// rgb转yuv
for (int i = 0; i < size; i++)
{
Rgb2Yuv(rdata[i], gdata[i], bdata[i], &ydata[i], &udata[i], &vdata[i]);
}
// 抽取边缘
float *edgedata = (float *)malloc(mem_size);
ExtractEdge(ydata, edgedata, width, height, 15.7f, 0.017f, 105.5f);
// 计算矢量场
float *vec_x = (float *)malloc(size * sizeof(float));
float *vec_y = (float *)malloc(size * sizeof(float));
CalcVectorField(ydata, width, height, vec_x, vec_y, 1.5f);
// 线积分卷积
LICFilter(edgedata, ydata, vec_x, vec_y, width, height);
free(vec_x);
free(vec_y);
vec_x = NULL;
vec_y = NULL;
free(edgedata);
edgedata = NULL;
// 输出结果
switch (style_index)
{
case 0:
// 黑白
memcpy(rdata, ydata, mem_size);
memcpy(gdata, ydata, mem_size);
memcpy(bdata, ydata, mem_size);
ConvertToUchar(rdata, gdata, bdata, pSrcBitmap);
ImageBlend(pSrcBitmap, 0, 0);
break;
case 1:
// 单彩
memcpy(rdata, ydata, mem_size);
memcpy(gdata, ydata, mem_size);
memcpy(bdata, ydata, mem_size);
ConvertToUchar(rdata, gdata, bdata, pSrcBitmap);
ImageBlend(pSrcBitmap, 0, 0);
ImageBlend(pSrcBitmap, 1, color_index);
break;
case 2:
// 多彩
for (int i = 0; i < size; i++)
{
Yuv2Rgb(ydata[i], udata[i], vdata[i], &rdata[i], &gdata[i], &bdata[i]);
}
ConvertToUchar(rdata, gdata, bdata, pSrcBitmap);
ImageAdjust(pSrcBitmap);
break;
default:
break;
}
free(ydata);
free(udata);
free(vdata);
ydata = NULL;
udata = NULL;
vdata = NULL;
free(rdata);
free(gdata);
free(bdata);
rdata = NULL;
gdata = NULL;
bdata = NULL;
return NULL;
}
同样,本算法也是有局限性的,对于背景与前景简单一些的图效果比较好,如果图像背景或者前景比较杂乱,生成的结果可能不太理想。下面是一些效果图:
欢迎下载示例demo:http://download.csdn.net/detail/u013085897/9747177,另外本算法已经集成进我的安卓应用《铅笔画》, 大家有兴趣可以到360、安卓、安智等商店下载使用。