图片转素描图

简介
  • 如何将图片转换成素描图呢,只需要下面四个步骤即可:
  • 首先将彩色图转换成灰度图;
  • 对灰度图进行求其反色的操作;
  • 对第2步得到的结果采用一个高斯模糊的操作;
  • 采用颜色亮化(color dodge)的技术将第一步的灰度图和第三步操作后的图片进行混合。
  • 事先准备,首先是安装好 opencv,可以直接通过 pip 进行安装:
pip install opencv-python
  • 接着准备一张图片,最好是颜色鲜明一点的图片,方便对比转换的效果。

  • original.png

第一步:彩色图变灰度图

第一步变成灰度图,其实非常简单,直接调用 opencv 的函数即可,如下面代码所示:

import cv2

img_rgb = cv2.imread('example.jpg')
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
  • 图片转换效果如下所示:

  • gray.png
  • 上面的代码是读取图片后,再通过调用cv2.cvtColor函数将图片转换成灰度图,实际上我们可以直接在读取图片时候就直接转换图片,即:
img_gray = cv2.imread('example.jpg', cv2.IMREAD_GRAYSCALE)
  • 这里调用cv2.imread函数时,设置了cv2.IMREAD_GRAYSCALE的标志,表示加载灰度图。在imread函数中是设置了三种标志,分别是

  • cv2.IMREAD_COLOR : 默认使用该种标识。加载一张彩色图片,忽视它的透明度。

  • cv2.IMREAD_GRAYSCALE : 加载一张灰度图。

  • cv2.IMREAD_UNCHANGED : 加载图像,包括它的Alpha 通道(Alpha 表示图片的透明度)。

  • 另外,如果觉得以上标志太长,可以简单使用 1,0,-1 代替,效果是相同的。

第二步:灰度图进行反色操作
  • 第二步就是对灰度图进行反色操作,其实就是非常简单的采用灰度图的最大像素值 255 减去当前像素值即可(因为灰度图的范围是[0, 255]),代码如下所示:
img_gray_inv = 255 - img_gray
  • 结果如下所示:

  • gray_inv.png
  • 其实就是原本比较暗的地方变光亮了,而比较亮的地方变暗了。
第三步:高斯模糊
  • 高斯模糊操作是一个有效减少图片噪音以及对图片进行平滑操作的方法,在数学上等价于对图像采用高斯核进行卷积的操作。我们可以直接调用cv2.GaussianBlur来实现高斯模糊操作,这里需要设置参数ksize,表示高斯核的大小,sigmaX和sigmaY分别表示高斯核在 X 和 Y 方向上的标准差。
img_blur = cv2.GaussianBlur(img_gray_inv, ksize=(21, 21),
                            sigmaX=0, sigmaY=0)
  • 效果如下所示,右边图是进行高斯模糊后的结果,是有了一定的模糊效果。

  • gray_blur.png

第四步:混合操作

  • 第四步,就是见证奇迹的时刻!这一步骤自然就是需要得到最终的素描图结果了。在传统照相技术中,当需要对图片某个区域变得更亮或者变暗,可以通过控制它的曝光时间,这里就用到亮化(Dodging)和暗化(burning)的技术。

  • 在现代图像编辑工具,比如 PS 可以实现上述说的两种技术。比如对于颜色亮化技术,给定一张图片 A 和 蒙版 B,那么实现做法如下所示:

(B[idx] == 255)?B[idx]:min(255, ((A[idx] << 8) / (255-B[idx])))
  • 通过 python 代码实现上述公式,那么原始代码如下所示:
import cv2
import numpy as np

def dodgeNaive(image, mask):
    # determine the shape of the input image
    width, height = image.shape[:2]

    # prepare output argument with same size as image
    blend = np.zeros((width, height), np.uint8)

    for col in range(width):
        for row in range(height):
            # do for every pixel
            if mask[col, row] == 255:
                # avoid division by zero
                blend[col, row] = 255
            else:
                # shift image pixel value by 8 bits
                # divide by the inverse of the mask
                tmp = (image[col, row] << 8) / (255 - mask)

                # make sure resulting value stays within bounds
                if tmp > 255:
                    tmp = 255
                    blend[col, row] = tmp

    return blend
  • 上述代码虽然实现了这个功能,但是很明显会非常耗时,中间采用了一个两层循环,计算复杂度是 O(w*h) ,也就是如果图片的宽和高的乘积越大,耗时就越长,所以就有了升级版的代码版本:
def dodgeV2(image, mask):
    return cv2.divide(image, 255 - mask, scale=256)
  • 运行上述代码,得到的最终结果如下所示:

  • sketch.png
  • 效果看起来还可以,除了右下角部分对于原图中黑色区域处理得不是很好。
  • 完整版代码如下所示:
import cv2
import numpy as np


def dodgeNaive(image, mask):
    # determine the shape of the input image
    width, height = image.shape[:2]

    # prepare output argument with same size as image
    blend = np.zeros((width, height), np.uint8)

    for col in range(width):
        for row in range(height):
            # do for every pixel
            if mask[col, row] == 255:
                # avoid division by zero
                blend[col, row] = 255
            else:
                # shift image pixel value by 8 bits
                # divide by the inverse of the mask
                tmp = (image[col, row] << 8) / (255 - mask)
                # print('tmp={}'.format(tmp.shape))
                # make sure resulting value stays within bounds
                if tmp.any() > 255:
                    tmp = 255
                    blend[col, row] = tmp

    return blend


def dodgeV2(image, mask):
    return cv2.divide(image, 255 - mask, scale=256)


def burnV2(image, mask):
    return 255 - cv2.divide(255 - image, 255 - mask, scale=256)


def rgb_to_sketch(src_image_name, dst_image_name):
    img_rgb = cv2.imread(src_image_name)
    img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
    # 读取图片时直接转换操作
    # img_gray = cv2.imread('example.jpg', cv2.IMREAD_GRAYSCALE)

    img_gray_inv = 255 - img_gray
    img_blur = cv2.GaussianBlur(img_gray_inv, ksize=(21, 21),
                                sigmaX=0, sigmaY=0)
    img_blend = dodgeV2(img_gray, img_blur)

    cv2.imshow('original', img_rgb)
    cv2.imshow('gray', img_gray)
    cv2.imshow('gray_inv', img_gray_inv)
    cv2.imshow('gray_blur', img_blur)
    cv2.imshow("pencil sketch", img_blend)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    cv2.imwrite(dst_image_name, img_blend)


if __name__ == '__main__':
    src_image_name = 'example.jpg'
    dst_image_name = 'sketch_example.jpg'
    rgb_to_sketch(src_image_name, dst_image_name)
  • 最后,还有一种更加快速的实现,代码如下所示,仅需三行代码即可实现转换成素描图的效果。
def rgb_to_sketch_v2(src_image_name):
    img_gray = cv2.imread(src_image_name, 0)
    img_blur = cv2.GaussianBlur(img_gray, (21, 21), 0, 0)
    img_blend = cv2.divide(img_gray, img_blur, scale=256)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,099评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,828评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,540评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,848评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,971评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,132评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,193评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,934评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,376评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,687评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,846评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,537评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,175评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,887评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,134评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,674评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,741评论 2 351

推荐阅读更多精彩内容