用深度学习做个艺术画家 ——模仿实现PRISMA

更新时间:2017-09-27 09:43:26 点击次数:1888次

使用深度学习作画的起源是有三个德国研究员想把计算机调教成梵高,他们研发了一种算法,模拟人类视觉的处理方式。具体是通过训练多层卷积神经网络,让计算机识别,并学会梵高的“风格”,然后将任何一张普通的照片变成梵高的《星空》。

图片描述

图9-1 deep art

后来他们开创了Deep Art公司,在Deep Art公司,负责绘画的程序员是卷积神经网络(CNN)。输入一个艺术作品,比如梵高的《星空》,卷积神经网络就会自动提取出这幅画作的“风格特征”,并转换成风格模板保存下来。也就是说,卷积神经网络可以被看作是一个机器艺术家。

Prisma次将这项艺术作画技术成功商业化。Prisma诞生于俄罗斯,是一个仅有4个年轻人历时一个半月开发出的图片处理应用。他们充分考虑了智能手机覆盖率的飞速增长,并且细致研究了用户行为。Prisma接入的是以亿数量级的市场,俄国总统梅德韦杰夫也成为了Prisma的用户,他在Instgram上晒出了一张Prisma作品,迅速获得8.7万个赞。

Google的Deep Dream也是一个会画画的计算机。它能够自动识别图像,筛选其中一些部分,进行夸张,以创造出一种迷幻效果。Deep Dream完全开源,在几个主流的深度学习库如Keras、Caffe的官方example中,都有Deep Dream的实现Demo。 
本章我们将探索实现类似Prisma的效果。

备注: 
本章完整项目地址:https://github.com/bbfamily/prisma_abu 
本项目演示视频:m.v.qq.com/play.html?&vid=v0397sv1fab,也可以在公众号abu_quant中直接观看视频。

机器学习初探艺术作画

好的艺术家模仿皮毛,伟大的艺术家窃取灵魂。 ——毕加索

本节介绍机器学习作画的简单原理,并展示输出效果。

艺术作画概念基础

第6章介绍了CNN如何提取图片中的图形特征,进而识别图片实物。现在,假设这里已经训练好了一个“识别毕加索绘制的猫”的深层卷积神经网络模型,如果把一张完全不同的照片输入模型,比如一张狗的照片,会发生什么呢?

图片描述

图9-2 CNN反馈修改输入图片

模型会给你反馈一个概率分数,表示它相信这是一张“毕加索猫”照片的程度。这中间经历了很多CNN层,每层CNN都在狗狗照片上寻找输入样本是毕加索猫的图形特征证据,越底层的神经元分析的特征越具体,越高层越抽象。当然,后模型会给出很低的分数。

上面在狗照上识别毕加索猫的过程中,如果让模型能够修改输入的样本又会怎样呢?

给模型网络中加一个反馈回路,让每一层网络可以朝着使后分数变大的方向上修改狗狗照片。每次迭代网络中的每层都会在狗照上增加一些毕加索猫的特征痕迹,可以迭代很多次,让狗狗照片中加入越来越多的毕加索猫的实物特征。

这就是使用卷积神经网络艺术作画的概念基础,让艺术风格模型的CNN按图形特征修改输入图片,叠加艺术效果。大致的实现思路如下:

直观感受一下机器艺术家

这里我们展示一下机器艺术作画的效果,原图如图9-3所示。

图片描述

图9-3 原图

下面我们用这几个浅层神经元对原图风格化的效果进行展示(备注:代码实现见本书Git库),如图9-4所示。

图片描述

图9-4 机器风格作画效果展示

有没有感觉到特征的识别由浅入深的一步一步增强,也就是从edge,到shape,再到复杂的shape循序渐进的过程,这里主要是卷基层把每层的特质放大进行夸张凸显。

图9-5展示了更多其他风格作画。

图片描述

图9-5 机器艺术作画效果图

一个有意思的实验

如果用Prisma做出一个图像,然后将它作为特征图像去引导新的图像生成会有什么效果呢?

guide = np.float32(
    tp.resize_img(PIL.Image.open('../prisma_gd/106480401.jpg')))
PrismaHelper.show_array_ipython(guide)

输出:

图片描述

图9-6 引导图

如图9-7所示,有些特征还是挖掘到了。

PrismaHelper.show_array_ipython(tp.fit_guide_img(s_file, gd_path, resize=True, size=640, iter_n=1500)) 

输出:

图片描述

图9-7 效果图

机器艺术作画的愿景

当机器能够根据图像的平面特征作画时,很多灵感也随之而来。比如我们可以从引导图中发现图像特征,从多个特征中寻找出存在的对象,并将这个特征融合到另一个图像中做特征融合。如果能够将特征识别融合做到极致,就可以完成如下假想场景。

图片描述

图9-8 特征融合的愿景图

回顾

本节我们介绍了深度学习艺术作画的原理,并展示了直观的效果,在使用Deep Dream等开源项目实现上述效果时,速度非常缓慢,所以从9.2节开始我们将使用自己的方式实现快速Prisma,实现秒级作画。

实现秒级艺术作画

天下武功,唯快不破。互联网竞争的利器就是快。——雷军

和Deep Art相比,Prisma的优势在于大大缩短了图像处理的时间,每张照片在Prisma系统内的处理时间控制在秒级别。而Deep Art更像是精工细作的手艺人,算法跑得虽然慢一些,但在细节表现力上更胜一筹。

在本书写作之前,笔者参考了几个艺术作画开源项目,都达不到真实Prisma的速度要求,本节将要使用的方式都是笔者原创的方法。首先笔者并不知道Prisma到底使用了什么方式使图像效果又好,速度又快,但是大概猜测的方向包括这几种可能:

第三种方式是说在拥有海量图片数据的前提下,作一个分类模型。对于一个输入图片,模型先分类出这是哪种类型,根据类型选择固定的特征提取方式进行渲染。而第四种方式是使用一些图片预处理技术减少机器学习算法的工作量。

由于我们没有足够的图片资源,而且第三点实现方式的速度瓶颈会在检索和相似度计算上,所以下面讲的内容是针对第四点展开试验的,可以在速度及渲染效果上都达到比较满意的效果。而的缺陷就在适用性上,实际使用时需要调整一下参数,所以在实际使用中可以结合上述第三种方式,针对一定数量的样本作为训练集x,对应的y是效果参数,对输入进行分类,再配合使用相似度等提高自动适配的能力。

主要实现思路分解讲解

下面还是使用abu1这张图片作为输入:

from PrismaCaffe import CaffePrismaClass import PrismaHelper import numpy as np import PIL.Image

abu1_file = '../sample/abu1.jpg' cp = CaffePrismaClass(dog_mode=False)
PrismaHelper.show_array_ipython(
    np.float32(cp.resize_img(PIL.Image.open(abu1_file)))) 

输出如图9.9所示。

图片描述

图9-9 原始图

下一步,挑一张引导特征图像:

gd_path = '../prisma_gd/tooopen_sy_127260228921.jpg' guide_img = np.float32(
    cp.resize_img(PIL.Image.open(gd_path), base_width=480,
                  keep_size=False))
PrismaHelper.show_array_ipython(guide_img) 

输出:

图片描述

图9-10 引导图

如下所示,首先将图像转化为单通道,用otsu寻找mask, 通过mask确定border和edges。

说明:otsu(大津算法, 自适应阈值)

输入:

r_img = cp.resize_img(PIL.Image.open(abu1_file), base_width=480,
                      keep_size=False) # rgb转化为单通道灰阶图像 l_img = np.float32(r_img.convert('L')) # filters.threshold_otsu需要(-1, 1)之间 l_img = np.float32(l_img / 255)
r_img = np.float32(r_img) # 找出大于otsu的阀值作为mask,需找border mask = l_img > filters.threshold_otsu(l_img) # 不是适用所有图像都要clear border,比如图像主题大部分需要保留时就不需要 clean_border = segmentation.clear_border(mask).astype(np.int)
coins_edges = segmentation.mark_boundaries(l_img, clean_border) # 将值再次转换到0-255 clean_border_img = np.float32(clean_border * 255)
clean_border_img = np.uint8(np.clip(clean_border_img, 0, 255))

PrismaHelper.show_array_ipython(clean_border_img) 

输出: 
图片描述

图9-11 单通道图

目标就是只想摘取狗狗的图像,其他的都认为是噪音,可以使用ndimage.binary _opening。达到效果了吗,试试看:

clean_border_img = ndimage.binary_opening(
    np.float32(clean_border_img / 255),
    structure=np.ones((5, 5))).astype(np.int)
clean_border_img = ndimage.binary_opening(clean_border_img).astype(
    np.int)
PrismaHelper.show_array_ipython(clean_border_img * 255) 

输出:

图片描述

效果其实不太好,ndimage.binary_opening效果与CNN中的小池化层相似,目的就是去掉图中的小物体,下面通过自定义简单卷积核来过滤,实现我们的需求。

代码如下:

# 小的卷积核目的是保留大体图像结构 n = 5 small_window = np.ones((n, n)) small_window /= np.sum(small_window) clean_border_small = convolve2d(clean_border_img, small_window,
                                mode="same", boundary="fill") # 中号的卷积核是为了保留图像的内嵌部分,这里的作用就是狗狗的黑鼻子和嘴那部分 n = 25 median_window = np.ones((n, n)) median_window /= np.sum(median_window) clean_border_convd_median = \
    convolve2d(clean_border_img, median_window, mode="same",
               boundary="fill") # 大号的卷积核,只是为了去除散落的边缘,很多时候没有必要,影响速度和效果 n = 180 big_window = np.ones((n, n)) big_window /= np.sum(big_window) clean_border_convd_big = convolve2d(clean_border_img, big_window,
                                    mode="same", boundary="fill") l_imgs = []
for d in range(3): # 分别对三个通道进行滤波 rd_img = r_img[:, :, d]
    gd_img = guide_img[:, :, d] # 符合保留条件的使用原始图像,否则使用特征图像 d_img = np.where(np.logical_or(
        clean_border_convd_median > 5 * clean_border_convd_big.mean(),
        np.logical_and(clean_border_small > 0, clean_border_convd_big \
                       > 2 * clean_border_convd_big.mean())),
        rd_img, gd_img)
    l_imgs.append(d_img) img_cvt = np.stack(l_imgs, axis=2).astype("uint8") # 对转换出的图像进行一次简单浅层特征放大 d_img = cp.fit_img(nbk='conv2/3x3_reduce', iter_n=10, img_np=img_cvt)
PrismaHelper.show_array_ipython(np.float32(d_img)) 

输出:

图片描述

图9-13 后效果图

代码并不多,主要思路如下:

卷积的意义简单理解就是加权叠加,针对输入的单位相应得到输出。为什么要用卷积呢?工程上理解其实就是为了效率。如果上面的代码从目的出发,知道要滤除什么样的像素点,保留什么样的像素点,将这些编程为计算操作;然后使用for循环针对每一个像素点,在一定范围内(卷积核大小)执行计算操作, 后使用for循环一步一步前进,其实也能得出结果,但是运算的时间复杂度将大出几个数量级。

1.使用图像特征作为mask

上面的方法是使用otsu寻找图像边缘作为mask的依据,下面使用skimage中的corner_peaks抽取图像特征作为mask。

def show_features(gd_file): r_img = cp.resize_img(PIL.Image.open(gd_file), base_width=480,
                          keep_size=False)
    l_img = np.float32(r_img.convert('L'))
    ll_img = np.float32(l_img / 255)

    coords = corner_peaks(corner_harris(ll_img), min_distance=5)
    coords_subpix = corner_subpix(ll_img, coords, window_size=25)

    plt.figure(figsize=(8, 8))
    plt.imshow(r_img, interpolation='nearest')
    plt.plot(coords_subpix[:, 1], coords_subpix[:, 0], '+r',
             markersize=15, mew=5)
    plt.plot(coords[:, 1], coords[:, 0], '.b', markersize=7)
    plt.axis('off')
    plt.show() def find_features(gd_file=None, r_img=None, l_img=None, loop_factor=1,
                  show=False): if gd_file is not None:
        r_img = cp.resize_img(PIL.Image.open(gd_file), base_width=480,
                              keep_size=False)
        l_img = np.float32(r_img.convert('L'))
        l_img = np.float32(l_img / 255)

    coords = corner_peaks(corner_harris(l_img), min_distance=5)
    coords_subpix = corner_subpix(l_img, coords, window_size=25)

    r_img_copy = np.zeros_like(l_img)
    rd_img = np.float32(r_img)

    r_img_copy[coords[:, 0], coords[:, 1]] = 1 f_loop = int(rd_img.shape[1] / 10 * loop_factor) for _ in np.arange(0, f_loop): """
         放大特征点,使用loop_factor来控制特征放大倍数
        """ r_img_copy = ndimage.binary_dilation(r_img_copy).astype(
            r_img_copy.dtype)
    r_img_copy_ret = r_img_copy * 255 if show:
        r_img_copy_d = [rd_img[:, :, d] * r_img_copy for d in range(3)]
        r_img_copy = np.stack(r_img_copy_d, axis=2)
        PrismaHelper.show_array_ipython(r_img_copy) return r_img_copy_ret 

显示抽取出的图像特征点:

show_features('../prisma_gd/71758PICxSa_1024.jpg')

输出如图9-14所示。

图片描述

图9-14 原始图

find_features使用ndimage.binary_dilation来放大特征点,使用loop_factor来控制特征放大倍数,目的是结合引导特征做渲染时提升原始图像的特征权重, find_features提取后的结果如下所示。

_ = find_features('../prisma_gd/71758PICxSa_1024.jpg', loop_factor=1,
                  show=True)

输出如图9-15所示。

图片描述

图9-15 抽取效果图

下面用IPython Notebook的可交互形式更直观地看一下特征的抽取与放大。

from ipywidgets import interact def find_features_interact(gd_file, loop_factor): r_img = cp.resize_img(PIL.Image.open(gd_file), base_width=480,
                          keep_size=False)
    l_img = np.float32(r_img.convert('L'))
    l_img = np.float32(l_img / 255)

    coords = corner_peaks(corner_harris(l_img), min_distance=5)
    coords_subpix = corner_subpix(l_img, coords, window_size=25)

    r_img_copy = np.zeros_like(l_img)
    rd_img = np.float32(r_img)

    r_img_copy[coords[:, 0], coords[:, 1]] = 1 f_loop = int(rd_img.shape[1] / 10 * loop_factor) for _ in np.arange(0, f_loop): """
            放大特征点,使用loop_factor来控制特征放大倍数
        """ r_img_copy = ndimage.binary_dilation(r_img_copy).astype(
            r_img_copy.dtype)
    r_img_copy_ret = r_img_copy * 255 r_img_copy_d = [rd_img[:, :, d] * r_img_copy for d in range(3)]
    r_img_copy = np.stack(r_img_copy_d, axis=2)
    PrismaHelper.show_array_ipython(r_img_copy)


gd_file = ('../prisma_gd/71758PICxSa_1024.jpg', '../prisma_gd/st.jpg', '../prisma_gd/g1.jpg', '../prisma_gd/31K58PICSuH.jpg')
loop_factor = (0, 2, 0.1)
interact(find_features_interact, gd_file=gd_file,
         loop_factor=loop_factor) 

输出如图9-16所示。

图片描述

图9-16 抽取效果图

使用统计参数期望与标准差寻找mask

mask的抽取方法可以有很多种方式,比如下面这个股票量化统计中经常使用的均值回复分析,用图像的均值和标准差来做滤波器,提取图像的mask。

下面重构一下代码,分别使用三种滤波图像的方式生成mask查看效果。

(1)def do_otsu(r_img, l_img, cb)代码封装。 
(2)def do_features(r_img, l_img, cb, loop_factor=1.0,前面介绍的抽取图像点集特征放大的方式。 
(3)def do_stdmean(r_img, l_img, cb, std_factor=1.0,用均值标准差统计方式抽取mask。

1.使用多种方式Prisma图片

输入:

def do_otsu(r_img, l_img, cb, dd=True): mask = l_img > filters.threshold_otsu(
        l_img) if dd else l_img < filters.threshold_otsu(l_img)
    clean_border = mask if cb:
        clean_border = segmentation.clear_border(mask).astype(np.int)
    clean_border_img = np.float32(clean_border * 255)
    clean_border_img = np.uint8(np.clip(clean_border_img, 0, 255)) return clean_border_img def do_features(r_img, l_img, cb, loop_factor=1.0): mask = find_features(r_img=r_img, l_img=l_img,
                         loop_factor=loop_factor)
    clean_border = mask if cb:
        clean_border = segmentation.clear_border(mask).astype(np.int)
    clean_border_img = np.float32(clean_border * 255)
    clean_border_img = np.uint8(np.clip(clean_border_img, 0, 255)) return clean_border_img """
    感受一下图像滤波的部分
""" def do_stdmean(r_img, l_img, cb, std_factor=1.0): mean_img = l_img.mean()
    std_img = l_img.std()

    mask1 = l_img > mean_img + (std_img * std_factor)
    mask2 = l_img < mean_img - (std_img * std_factor)

    clean_border = mask1 if cb:
        clean_border = segmentation.clear_border(mask1).astype(np.int)
    clean_border_img1 = np.float32(clean_border * 255)
    clean_border_img1 = np.uint8(np.clip(clean_border_img1, 0, 255))

    clean_border = mask2 if cb:
        clean_border = segmentation.clear_border(mask2).astype(np.int)
    clean_border_img2 = np.float32(clean_border * 255)
    clean_border_img2 = np.uint8(np.clip(clean_border_img2, 0, 255)) # 上下两部分组合 clean_border_img = clean_border_img1 + clean_border_img2
    clean_border_img = np.uint8(np.clip(clean_border_img, 0, 255)) return clean_border_img """
    将多个mask func用“与”的形式组合成mask滤波器 
    exp: tgt_mask_func = partial(together_mask_func,
        func_list=[do_otsu, mask_stdmean_func, mask_features_func])
""" def together_mask_func(r_img, l_img, cb, func_list): clean_border_img = None for func in func_list: if not callable(func): raise TypeError("together_mask_func must a func!!!")
        border_img = func(r_img, l_img, cb) if clean_border_img is None:
            clean_border_img = border_img else:
            clean_border_img = clean_border_img + border_img
    clean_border_img = np.uint8(np.clip(clean_border_img, 0, 255)) return clean_border_img """
    使用partial统一mask函数接口形式
""" mask_stdmean_func = partial(do_stdmean, std_factor=1.0)
mask_features_func = partial(do_features, loop_factor=0.88) def do_convd_filter(n1, n2, n3, rb_rate, r_img, guide_img,
                    clean_border_img, convd_median_factor,
                    convd_big_factor): n = n1
    small_window = np.ones((n, n))
    small_window /= np.sum(small_window)
    clean_border_small = convolve2d(clean_border_img, small_window,
                                    mode="same", boundary="fill")

    n = n2
    median_window = np.ones((n, n))
    median_window /= np.sum(median_window)
    clean_border_convd_median = \
        convolve2d(clean_border_img, median_window, mode="same",
                   boundary="fill")

    n = n3
    big_window = np.ones((n, n))
    big_window /= np.sum(big_window)
    clean_border_convd_big = \
        convolve2d(clean_border_img, big_window,
                   mode="same", boundary="fill")

    l_imgs = [] for d in range(3): """
            针对rgb各个通道处理
        """ rd_img = r_img[:, :, d]
        gd_img = guide_img[:, :, d]

        wn = [] for _ in np.arange(0, rd_img.shape[1]): """
                二项式概率分布
            """ wn.append(np.random.binomial(1, rb_rate, rd_img.shape[0])) if rb_rate <> 1: """
                针对rgb通道阶梯下降二项式概率
            """ rb_rate = rb_rate - 0.1 w = np.stack(wn, axis=1)

        d_img = np.where(np.logical_or(
            np.logical_and(
                clean_border_convd_median > convd_median_factor \
                * clean_border_convd_big.mean(), w == 1),
            np.logical_and(
                np.logical_and(clean_border_small > 0, w == 1),
                clean_border_convd_big > convd_big_factor \
                * clean_border_convd_big.mean())),
            rd_img, gd_img)

        l_imgs.append(d_img)
    img_cvt = np.stack(l_imgs, axis=2).astype("uint8") return img_cvt def mix_mask_with_convd(do_mask_func, org_file=None, gd_file=None,
                        nbk=None, enhance=None, n1=5, n2=38, n3=1,
                        convd_median_factor=5.0, convd_big_factor=0.0,
                        cb=False, rb_rate=1, r_img=None,
                        guide_img=None, all_mask=False, show=False): if r_img is None:
        r_img = cp.resize_img(PIL.Image.open(org_file),
                              base_width=480, keep_size=False)

    l_img = np.float32(r_img.convert('L'))
    l_img = np.float32(l_img / 255)
    r_img = np.float32(r_img) if show:
        PrismaHelper.show_array_ipython(np.float32(r_img)) if not callable(do_mask_func): raise TypeError( 'mix_mask_with_convd must do_mask_func a func')

    clean_border_img = np.ones_like(
        l_img) * 255 if all_mask else do_mask_func(r_img=r_img,
                                                   l_img=l_img, cb=cb) if show:
        PrismaHelper.show_array_ipython(np.float32(clean_border_img)) if guide_img is None: if gd_file is not None:
            guide_img = np.float32(
                cp.resize_img(PIL.Image.open(gd_file), base_width=480,
                              keep_size=False)) else:
            guide_img = np.zeros_like(r_img)

    img_cvt = do_convd_filter(n1, n2, n3, rb_rate, r_img, guide_img,
                              clean_border_img,
                              convd_median_factor=convd_median_factor,
                              convd_big_factor=convd_big_factor) if nbk is not None:
        img_cvt = cp.fit_img(org_file, nbk=nbk, iter_n=10,
                             enhance=enhance, img_np=img_cvt) if show:
        PrismaHelper.show_array_ipython(np.float32(img_cvt)) return img_cvt 

使用特征do_features mask方式,注意rb_rate=0.66的使用,这里使用它的目的是使特征边缘平滑过渡到引导特征中, 当然这里还可以有各种优化方式,比如向下调整convd_median_factor,使原始特征边缘提取更加圆润平滑。

_ = mix_mask_with_convd(partial(do_features, loop_factor=1.1), '../prisma_gd/71758PICxSa_1024.jpg', '../prisma_gd/cx6.jpg', 'conv2/3x3_reduce', rb_rate=0.66, show=True) 

输出如图9-17所示。

图片描述

图9-17 效果图

球员库里这张图使用均值回复mask方式可以对图片产生比较好的效果,使用其他两种方式效果均不佳,读者可以自行测试效果。

_ = mix_mask_with_convd(mask_stdmean_func, '../sample/kl.jpg', '../prisma_gd/cx7.jpg', 'conv2/3x3_reduce',
                        n2=88, convd_median_factor=0.1, rb_rate=1,
                        show=True) 

输出如图9-18所示。

图片描述

图9-18 效果图

使用abu2看看现在这种方式的运行效率,%time计算一下耗时。请注意参数,n3=1、convd_median_factor=0.2、convd_big_factor=0.0。也就是说,不使用大的卷积核,速度会非常快,只用了5.62 s,并且包含一些额外的代码工作,如显示原图等。

% time _ = mix_mask_with_convd(do_otsu, '../sample/abu2.jpg', '../prisma_gd/k5.jpg', 'conv2/3x3_reduce',
                        n3=1, convd_median_factor=0.2,
                        convd_big_factor=0.0, show=True)

输出如图9-19所示。

图片描述

图9-19 效果图

abu5使用partial(together_mask_func, func_list=[do_otsu, mask_features_func]),组合多个特征抽取mask函数,多个滤波函数以“与的关系”进行组合,对图像进行mask。

这里如果不使用together_mask_func,则单独每个都要再次调整一些参数。比如单独使用do_otsu,就要调大n2核的大小,影响了速度,而mask函数合并特征完美快速实现了需求。

tgt_mask_func = partial(together_mask_func,
                        func_list=[do_otsu, mask_features_func])
_ = mix_mask_with_convd(tgt_mask_func, '../sample/abu3.jpg', '../prisma_gd/cx3.jpg', 'conv2/3x3_reduce',
                        cb=False, n2=68, n3=1, convd_median_factor=1,
                        convd_big_factor=0.0, show=True) 

输出如图9-20所示。

图片描述

图9-20 效果图

2.配合使用预处理图像增强,随机rgb浅层edges等增强Prisma效果

下面对笔者的偶像艾弗森,使用图像预处理的方式来做图。首先用Contrast,注意这里设置了参数rb_rate=0.88,从代码来看它的作用是:

 wn = []
for _ in np.arange(0, rd_img.shape[0]):
    wn.append(np.random.binomial(1, rb_rate, rd_img.shape[1]))
if rb_rate <> 1:
    rb_rate = rb_rate - 0.1 w = np.stack(wn, axis=1)

d_img = np.where(np.logical_or(np.logical_and(
    clean_border_convd_median > convd_median_factor * \
    clean_border_convd_big.mean(), w == 1), np.logical_and(
    np.logical_and(clean_border_small > 0, w == 1),
    clean_border_convd_big > convd_big_factor * \
    clean_border_convd_big.mean())), rd_img, gd_img) 

逻辑中 np.logical_and添加w == 1的判断,这里使用二项式分布,增强渲染的迷幻效果,即随机在rgb某一个通道中渲染一下引导特征。

rb_rate = rb_rate - 0.1 的作用是3个通道的随机渲染概率阶梯下降,这里也可以有其他各种渲染变种。

例如,这里的w二项式分布矩阵就类似下面这个示例矩阵,针对图像中每一个像素点,0.8的概率为1,0.2的概率为0。

wn = []
for _ in np.arange(0, 20):
    wn.append(np.random.binomial(1, 0.8, 20))
w = np.stack(wn, axis=1)
w 

输出:

array([[1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1], [1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0], [1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0], [1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1], [0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1], [1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1], [1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0], [1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1], [0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1], [1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1], [1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0], [1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0], [1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], [1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1], [1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1], [0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1], [1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1]]) 

输入:

tgt_mask_func = partial(together_mask_func,
                        func_list=[do_otsu, mask_stdmean_func])
_ = mix_mask_with_convd(tgt_mask_func, '../sample/lfs.jpg', '../prisma_gd/cx11.jpg', 'conv2/norm2', enhance='Contrast',
                        rb_rate=0.88, n2=180, n3=1,
                        convd_median_factor=0.01,
                        convd_big_factor=0.0, show=True) 

输出如图9-21所示。

图片描述

图9-21 效果图

感觉不是很帅,那怎么行,前置一个Sharpness预处理效果看看:

_ = mix_mask_with_convd(do_otsu, '../sample/lfs.jpg', '../prisma_gd/cx11.jpg', 'conv2/norm2',
                        enhance='Sharpness', rb_rate=0.88, cb=False,
                        n2=188, n3=1, convd_median_factor=0.01,
                        convd_big_factor=0.0, show=True) 

输出如图9-22所示。

图片描述

图9-22 效果图

上面做出的两个效果,仔细观察就会发现,其实他们并没有使用引导图的sharp特征,只是通过阶梯rgb渲染,在原始图像上泼上了一层浅层的edges特征,这样实际上你不需要使用上面这种实现方式。注意mix_mask_with_convd的参数all_mask,当all_mask为true时,将整个图像的mask全设置255,代码如下:

clean_border_img = np.ones_like(l_img) * 255 if all_mask else do_mask_func(r_img=r_img, l_img=l_img, cb=cb)

所以如果想使用浅层特征edges,直接设置all_mask就可以了。

工程代码封装结构及使用示例

将上面的代码再次重构到文件PrismaWorker中, 代码详情请查阅PrismaWorker.py。

from PrismaWorker import PrismaWorkerClass
pw = PrismaWorkerClass() 

使用两个GTA5的图片,融合摩托车大哥到大部队中,作为图9-23所示的原始图片的引导特征图:

pw.cp.resize_img(PIL.Image.open('../prisma_gd/gta2.jpg'),
                 base_width=480, keep_size=False) 

输出如图9-23所示。

图片描述

图9-23 原始图片

注意,partial(do_otsu, dd=False)中的dd参数代表otsu后是取内部还是取反向的外部,如下面的黑白mask图,dd=False可以取到骑手,否则将取到外部背景。

_ = pw.mix_mask_with_convd(partial(pw.do_otsu, dd=False), '../sample/gta4.jpg', '../prisma_gd/gta2.jpg', 'conv2/3x3_reduce', enhance='Sharpness', n2=88, n3=1, convd_median_factor=1.5,
                           convd_big_factor=0.0, show=True) 

输出如图9-24所示。

图片描述

图9-24 效果图

效果还算不错,除了左下脚两个标准的重叠。

接下来使用批量处理引导图像,预处理、特征放大层等参数排列组合使用PrismaMaster,详情请查询代码PrismaMaster.py。如下代码所示,可以生成所有参数排列组合的输出结果。

import PrismaMaster

cb = False n1 = 5 n2 = 38 n3 = 1 convd_median_factor = 0.6 convd_big_factor = 0.0 loop_factor = 1.0 std_factor = 0.88 nbk_list = filter(lambda nbk: nbk[-8:-1] <> '_split_',
                  cp.net.blobs.keys()[1:-2])[:10]
org_file_list = ['../sample/bz1.jpg']
gd_file_list = [None, '../cx/cx3.jpg', '../cx/cx6.jpg', '../cx/cx7.jpg', '../cx/cx10.jpg']
enhance_list = [None, 'Sharpness', 'Contrast']
rb_rate_list = [0.85, 1.0]

save_dir = '../out/2016_11_24' PrismaMaster.product_prisma(org_file_list, gd_file_list, nbk_list,
                            enhance_list, rb_rate_list, 'otsu_func',
                            n1, n2, n3, std_factor, loop_factor,
                            convd_median_factor,
                            convd_median_factor, cb, save_dir) 

接下来再做一个GUI的可视化操作界面PrismaController, 使用了traitsui库,详情请查看PrismaController.py。

图片描述

图9-25 GUI控制界面

基于这样一个方便微调的GUI下可以很方便地对图像进行微调,做出很多酷炫的图像,比如图9-26所示的做的两张基于GTA风格的图像。

图片描述

图9-26 GTA风格效果图

图9-26这张犀利哥和GTA5合体的原图素材如图9-27所示。

图片描述

图9-27 原始图片

如果你不知道什么样效果好或者想要所有可能的效果图,那么你可以看到GUI的界面上还有个按钮“使用参数批量艺术图片”,它的作用是使用刚刚调整好的n1, n2, dd等参数作为固定参数,将引导特征图、放大层特征、预处理增强等作为所有可能的排列组合,通过一键生成成百上千张的风格图像,代码详情请查看PrismaController.py。

回顾和后记

总的来说,本章只想告诉你,如果希望机器学习技术无缝地落地到某个领域时,需要的不仅仅是深度学习模型技术,还有灵活的思路以及变通的智慧。

本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责,本站只提供参考并不构成任何投资及应用建议。本站是一个个人学习交流的平台,网站上部分文章为转载,并不用于任何商业目的,我们已经尽可能的对作者和来源进行了通告,但是能力有限或疏忽,造成漏登,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

回到顶部
嘿,我来帮您!