基于《Combining Sketch and Tone for Pencil Drawing Production》的图像铅笔画算法的实现

一,借鉴:

本文借鉴了CSDN博主风吹夏天对此论文算法的理解:风吹夏天的图像铅笔画算法,以及香港中文大学Cewu Lu等人写的该论文的主页。原文作者和博主风吹夏天都给过代码,但是代码不全。我仔细看了原论文和该博主的文章后,基本上,大致算法思想就理通了。本文对于具体算法细节就不细述了,个人建议还是先进行原论文的研读,对该论文所进行的步骤先有个大致的了解。

二,算法思路

1,首先需要产生笔画结构

大致思路:对原图像进行梯度运算,得出大致轮廓 -----> 设计8个方向的卷积核,并依据论文中的公式对像素依据最大值所在子图像进行方向分类 -----> 再依据论文中的公式对每个方向的像素分别和对应的卷积核进行卷积

几个注意的地方:

对于卷积核的大小,论文中写的是原图片高和宽的三十分之一,论文作者设计卷积核的目的是希望产生铅笔画中一笔一笔勾画出的痕迹,但如果太大,会导致轮廓图不清晰,卷积核大小应该设置在3~13间效果好些;

根据输入图片的差异,原图片可能需要去噪,若最后轮廓不清晰,可能需要直方图均衡化使轮廓相对清晰。

2,其次产生色调图

论文作者根据对大量铅笔图片的研究,发现它们的直方图如下,而我们要做的就是将原图片的色阶的直方图转换为以下的直方图,而以下的直方图可以由高斯分布函数,均匀分布函数和拉布拉斯分布函数带权值相加后近似得到(三个权值相加为1),它们的权值比为w1:w2:w3,此后,进行SML映射将原图片的直方图转换为以下的直方图就可以了。

注意的地方:一般采用w1:w2:w3 = 2:22:76,因为铅笔画较谈,w3越大,图片越亮,但对于有些图片,如果太亮,有些较暗的细节就会消失,此时可以选择w1:w2:w3 = 11:37:52,此外也可以根据最后效果调整三个参数大小,一般,w3介于52~76间,w1最好在10以下。

3,由铅笔画背景和色调图生成色调渲染图

我使用的是如下的铅笔背景图:

这一步主要是为了解下面这个方程组中的β矩阵,其中H(x)为上面这张铅笔背景图,J(x)为第二步生成的色调图,现在主要求解β(x),最后色调渲染图T(x) = H(x).^β(x),这样做的目的是使色调图有铅笔反复抹画的痕迹,且保持直方图与铅笔画的直方图一致:

下面是求解公式,风吹夏天博主的文章有详细的求解过程,大致思路是先把图像矩阵变成向量,再进行求解:

注意的地方:根据最后效果的考虑,需要对背景铅笔图片和生成的色调渲染图片进行gamma矫正。

4,最后将第一步生成的结构图片与第二三步生成的色调渲染图片直接点乘就可以了

注意的地方:由于实现过程中,有许多地方用到矩阵点乘和矩阵相乘,因此在计算时应该先用im2double函数将图像矩阵中每个像素映射到0~1间进行计算。

5,输入/得到彩色图像 VS 输入/得到灰度图像

输入灰度图像/彩色图像:输入时进行判断就维数判断就可以了,但在调用函数grayPencilDrawing时,输入的一定要是灰度图像;

输出灰度图像/彩色图像:输出灰度铅笔画图像,调用函数grayPencilDrawing,函数返回时,返回的就是灰度铅笔图像;输出彩色铅笔画图像,这时输入的肯定是彩色图像,需要先将原彩色图像由rgb空间转成yuv空间,对于yuv格式,其中y分量是色阶,uv分量是色调,只需要对y分量调用函数grayPencilDrawing,最后再由yuv空间转成rgb空间。以下是yuv和rgb转换公式:

注意的地方:需要对输入进行iif else条件判断。

三,具体代码

具体需要改的参数在第一个代码段中都有标识,可以先读懂代码,然后修改相关参数,此外,对于每一张图片,所需要的参数有可能不同,主要依据最后效果修改参数:

代码1:输入图片和背景图片:该代码可能作为一种接口代码,将论文中需要修改的参数都提取了出来,只需修改4~17行的相关参数,直接函数调用就可以查看结果了。此外,对最后rgb空间和yuv空间进行了考虑:

clc;
clear;
%==============================参数修改====================================
Input = imread('input1.jpg');%输入原始图片
dirNum = 8;%表示具有dirNum个方向的卷积核
[m, n, src] = size(Input);
ks = floor(min(m/50, n/50) + 0.5);%论文中要求为高或宽的1/30,实际上不能这么大
ks = 10;%卷积核大小,建筑类图片为10~13,风景花卉类图片为3~6
strokeDepth = 2;%对生成的轮廓图多描几遍

w1 = 0.57;%高斯分布权重
w2 = 0.37;%均匀分布权重
w3 = 0.06;%拉布拉斯分布权重

Back = rgb2gray(imread('pencil.jpg'));%输入背景铅笔图
backDepth = 0.7;%对铅笔背景图pencil.jpg多描几遍
renderDepth = 0.7;%对生成的纹理渲染图多描几遍

%如果输入的是灰度图像,对其进行处理
if src == 2
    gray_out = rgb2gray(Input);
    gray_out = grayPencilDrawing(gray_out, dirNum, ks, strokeDepth, w1, w2, w3, Back, backDepth, renderDepth);
    rgb_out = Input;
end
%如果输入的是彩色图像,对其进行处理
if src == 3
    rgb_out = mat2gray(Input);
    R = rgb_out(:, :, 1);
    G = rgb_out(:, :, 2);
    B = rgb_out(:, :, 3);
    Y = 0.299*R + 0.587*G + 0.114*B;
    U = -0.147*R- 0.289*G + 0.436*B;
    V = 0.615*R - 0.515*G - 0.100*B;
    Y = uint8(Y .* 255);
    Y = grayPencilDrawing(Y, dirNum, ks, strokeDepth, w1, w2, w3, Back, backDepth, renderDepth);
    %yuv_out = cat(3, Y, U, V);%按照第三维组合
    rgb_out(:,:,1) = Y + 1.14 * V;
    rgb_out(:,:,2) = Y - 0.39 * U - 0.58 * V;
    rgb_out(:,:,3) = Y + 2.03 * U;
    figure, imshow(rgb_out), title('彩色铅笔图');
    imwrite(rgb_out, 'coloredPencil.jpg');
end

代码2:具体调用函数的实现,这一步也是上述算法思路前四个的实现

function Out = grayPencilDrawing(Input, dirNum, ks, strokeDepth, w1, w2, w3, Back, backDepth, renderDepth)
%===============================1:获取轮廓图===============================
%读入单通道的图片
    [h, w] = size(Input);
    I_gray = Input;
    I_gray1 = I_gray;
    %Input = histeq(Input, 256); %针对有些图片层次不清晰,若最后轮廓图不清晰,加上这两句
    %figure, imshow(Input);
    I_gray = im2double(I_gray);
    imX = [abs(I_gray(:,1:(end-1)) - I_gray(:,2:end)), zeros(h, 1)];%最右边增加一列0
    imY = [abs(I_gray(1:(end-1),:) - I_gray(2:end,:)); zeros(1, w)];%最下边增加一行0
    imX = imX .^ 2;
    imY = imY .^ 2;
    imEdge = sqrt(imX + imY);%论文中边缘公式(1)实现
    imEdge = immultiply(imEdge, 5);
%%%%%figure, imshow(imEdge, []), title('边缘提取');
    imwrite(imEdge, 'stroke_tmp1.jpg');
%水平方向为1的卷积核
    kerRef = zeros(ks*2+1);
    kerRef(ks+1,:) = 1;
%对卷积核依次旋转180/dirNum度并使用论文中的公式先计算8个方向卷积,计算出边缘
    response = zeros(h, w, dirNum);
    for n = 1: dirNum
        ker = imrotate(kerRef, (n-1)*180/dirNum, 'bilinear', 'crop');
        response(:, :, n) = conv2(imEdge, ker, 'same');%对梯度图进行八个方向卷积核,论文中边缘公式(2)实现
    end
    [~ , index] = max(response, [], 3); %index为一个m*n矩阵,每个元素为dirNum个矩形中对应元素的最大纵坐标号(范围为1~dirNum),最大值即为该点方向
    C = zeros(h, w, dirNum);
    for n=1:dirNum
        C(:,:,n) = imEdge .* (index == n); %index == 2也是一个矩阵,元素值为0或1。之后与imEdge对应元素相乘,论文中边缘公式(3)实现
    end
    Spn = zeros(h, w, dirNum);
    for n = 1: dirNum
        ker = imrotate(kerRef, (n-1)*180/dirNum, 'bilinear', 'crop');
        Spn(:, :, n) = conv2(C(:,:,n), ker, 'same'); %对得到的各个方面的响应再次进行方向卷积,论文中边缘公式(4)实现
    end
    Sp = sum(Spn, 3); %按照第三维对这dirNum个矩阵的元素各自累加
    Sp = (Sp - min(Sp(:))) / (max(Sp(:)) - min(Sp(:)));%min(sp(:))和max(sp(:))分别表示矩阵sp中元素的最小值和最大值,即灰度拉伸
    Stroke = 1 - Sp;%由于梯度图背景黑线白,现在使其背景白线黑
    Stroke = Stroke .^ strokeDepth;%轮廓图描深
    figure, imshow(Stroke), title('Stroke图');
    imwrite(Stroke, 'stroke.jpg');

%===============================2:获取色调图===============================
%直方图匹配
    p = zeros(1, 256);
    v = zeros(1, 256);
    i = 0: 255;
    p1 = (1/9) * exp(-(255 - i)/9);
    %axis([0 255 0 1]);
    p2 = zeros(1, 256);
    p2(105: 225) = 1/(225-105);
    p3 = (1/sqrt(2*pi*11)) * exp(-(i-90).*(i-90)/(2.0*11*11));
    p = p1*w1 + p2*w2 + p3*w3;
    t = p;
    %figure, plot(imhist(I_gray)/(h*w)), title('原始直方图');
    for i = 1: h
        for j = 1: w
            v(I_gray1(i, j)+1) = v(I_gray1(i, j)+1) + 1;
        end
    end
    v = v / (h*w);
    for i = 2: 256
        p(i) = p(i-1) + p(i);
        v(i) = v(i-1) + v(i);
    end
    p = p /p(256);
%计算源图像到目标图像累积直方图中各灰度级的差的绝对值
    scrMin = zeros(256, 256);
    for x = 1: 256
        for y = 1: 256
            scrMin(x, y) = abs(v(x) - p(y));
        end
    end
    for x = 1: 256
        minX = 1;
        minValue = scrMin(x, 1);
        for y = 2: 256
            if(minValue > scrMin(x, y))
                minValue = scrMin(x, y);
                minX = y;
            end
        end
        HistogramMapping(x) = minX;
    end
%对原图根据HistogramMapping进行灰度级映射
    I_tone = I_gray1;
    for x = 1: h
        for y = 1: w
            I_tone(x, y) = HistogramMapping(I_gray1(x, y) + 1) - 1;
        end
    end
%%%%%figure, subplot(211), plot(t), title('目标直方图');
%%%%%subplot(212), plot(imhist(I_tone)/(h*w)), title('色调映射图的直方图');%因为进行映射时,目标图像中有些灰度值不存在,因此曲线中接近0的曲折部分表示某些灰度值不存在
    figure;
    subplot(121), imshow(I_gray1), title('原始灰度图');
    subplot(122), imshow(I_tone), title('色调映射图');
    imwrite(I_tone, 'tone.jpg');    

%===============================3:纹理渲染图===============================
    %subplot(251), imshow(I);%运行这9行可以查看背景铅笔图效果
    a = 1;
    for i = 0.2: 0.2: 1.8
        %a = floor(i / 0.2 + 0.5) + 1;
        a = a + 1;
        I0 = ((double(Back)/255).^i);
        %subplot(2, 5, a), imshow(I0);
    end
    I0 = ((double(Back)/255) .^ backDepth);
    %figure, imshow(I0);
    I0 = uint8(I0 * 255);
    imwrite(I0, 'New_texture.jpg');
%开始计算P .^ β = J中的β矩阵
    theta = 0.2;
    P = im2double(imread('New_texture.jpg'));
    J = im2double(imread('tone.jpg'));
%初始化为向量方便计算
    P = imresize(P, [h, w]);
    P = reshape(P, h*w, 1);
    logP = log(P);
    logP = spdiags(logP, 0, h*w, h*w);

    J = imresize(J, [h, w]);
    J = reshape(J, h*w, 1);
    logJ = log(J);
%两个梯度
    e = ones(h*w, 1);
    Dx = spdiags([-e, e], [0, h], h*w, h*w);
    Dy = spdiags([-e, e], [0, 1], h*w, h*w);
%带入求解公式计算
    A = theta * (Dx * Dx' + Dy * Dy') + (logP)' * logP;
    b = (logP)' * logJ;
%计算向量形式的beta
    beta = pcg(A, b, 1e-6, 60);
%转化成矩阵形式并拉伸
    beta = reshape(beta, h, w);
    beta = (beta - min(beta(:))) / (max(beta(:)) - min(beta(:)))*5;

    P = reshape(P, h, w);
    T = P .^ beta;
    A = T .^ renderDepth;
%%%%%imshow(A), title('色调渲染图');;
    imwrite(A, 'new_tone.jpg');

%===============================3:结构图与色调图整合===============================
    I = im2double(imread('stroke.jpg'));
    S = A .* I;
    figure, imshow(S), title('灰色铅笔画');
    imwrite(S, 'grayPencil.jpg');
    Out = S;
end

四,一些实现效果

原图                         灰度铅笔画                       彩色铅笔画

时间: 2024-10-09 02:59:23

基于《Combining Sketch and Tone for Pencil Drawing Production》的图像铅笔画算法的实现的相关文章

关于Cewu Lu的《Combining Sketch and Tone for Pencil Drawing Production》一文铅笔画算法的理解和笔录。

相关论文的链接:Combining Sketch and Tone for Pencil Drawing Production 第一次看<Combining Sketch and Tone for Pencil Drawing Production>一文是在两年前,随意看了一下,觉得论文里的公式比较多,以为实现有一定的难度,没有去细究,最近在作者主页上看到有 [code of direction classification] 部分代码,下载后觉得还是有自己实现的可能,下面记录下自己实现过程中

Combining Sketch and Tone for Pencil Drawing Production的优化过程

Combining Sketch and Tone for Pencil Drawing Production 是一个非常出色的自然图像转铅笔画的算法,具体的算法原理就不多说了,不了解的可以看一下这个博客,写得非常清楚了: http://blog.csdn.net/bluecol/article/details/45422763 这里主要写一下我的开发过程,和用到的优化方法. 这个算法主要有三个步骤: (1) Generate Structure Map (2) Generate Tone Ma

关于Cewu Lu等的《Combining Sketch and Tone for Pencil Drawing Production》一文铅笔画算法的理解和笔录。

相关论文的链接:Combining Sketch and Tone for Pencil Drawing Production 第一次看<Combining Sketch and Tone for Pencil Drawing Production>一文是在两年前,随意看了一下,觉得论文里的公式比较多,以为实现有一定的难度,没有去细究,最近在作者主页上看到有 [code of direction classification] 部分代码,下载后觉得还是有自己实现的可能,下面记录下自己实现过程中

paper reading(1) - Combining Sketch and Tone for Pencil Drawing Production

目录 Combining Sketch and Tone for Pencil Drawing Production paper content understanding algorithm understand report outlines paper writing strategies note Abstract Introduction Related Work Combining Sketch and Tone for Pencil Drawing Production paper

基于FPGA的均值滤波算法的实现

前面实现了基于FPGA的彩色图像转灰度处理,减小了图像的体积,但是其中还是存在许多噪声,会影响图像的边缘检测,所以这一篇就要消除这些噪声,基于灰度图像进行图像的滤波处理,为图像的边缘检测做好夯实基础. 椒盐噪声(salt & pepper noise)是数字图像的一个常见噪声,所谓椒盐,椒就是黑,盐就是白,椒盐噪声就是在图像上随机出现黑色白色的像素.椒盐噪声是一种因为信号脉冲强度引起的噪声,产生该噪声的算法也比较简单. 均值滤波的方法将数据存储成3x3的矩阵,然后求这个矩阵.在图像上对目标像素给

基于数组二分查找算法的实现

基于数组二分查找算法的实现 二分查找 查找 算法 赵振江 二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好:其缺点是要求待查表为有序表,且插入删除困难.因此,折半查找方法适用于不经常变动而查找频繁的有序列表.首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功:否则利用中间位置记录将表分成前.后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表.重复以上过程,直到找到满足条件的记录,使查找成功

基于思岚A1激光雷达+OpenGL+VS2017的Ramer-Douglas-Peucker算法的实现

时隔两年 又借到了之前的那个激光雷达,最老版本的思岚A1,甚至不支持新的固件,并且转接板也不见了,看了下淘宝店卖¥80,但是官方提供了一个基于STM32的实现方式,于是我估摸着这个转接板只是一个普通的USB-TTL转接板,那我就用340搭一个试试吧 根据官方的datasheet,电机可以5V供电,核心也是5V,电机使能是VMOTO电压,即5V,因此将三个接口焊到一起,两个地焊到一起,然后剩下一组TXRX,因此七个接口变成四个接口了,正好能接上340,于是插上电试了试,当然...没有那么顺利,报错

基于torch学汪峰写歌词、聊天机器人、图像着色/生成、看图说话、生成字幕

手把手教你基于torch玩转 学汪峰写词.自动聊天机器人.图像着色.图像生成.看图说话.生成字幕 作者:骁哲.李伟.小蔡.July.说明:本教程出自七月在线开发/市场团队.及七月在线5月深度学习班学员之手,有何问题欢迎加Q群交流:472899334.时间:二零一六年十月十二日. 前言 我们教梵高作画的教程发布之后,国庆7天,上百位朋友一一陆续动手尝试,大有全民DL.全民实验之感.特别是来自DL班的小蔡同学,国庆7天连做10个开源实验,并把这10个实验的简易教程(含自动聊天机器人)发布在社区上:h

基于MapReduce的朴素贝叶斯算法的实现与分析

一.朴素贝叶斯(Na?ve Bayes)分类器 1.1 公式 朴素贝叶斯是一个概率分类器 文档 d 属于类别 c 的概率计算如下(多项式模型): nd是文档的长度(词条的个数) P(tk |c) 是词项tk 出现在类别c中文档的概率,即类别c文档的一元语言模型 P(tk |c) 度量的是当c是正确类别时tk 的贡献 P(c) 是类别c的先验概率 如果文档的词项无法提供属于哪个类别的信息,那么我们直接选择P(c)最高的那个类别 1.2 具有最大后验概率的类别 §朴素贝叶斯分类的目标是寻找"最佳&q