Opencv实现素描的坤坤打篮球

蔡蔡子相信大家都很熟悉,他擅长唱、跳、Rap还有篮球!今天,就让我们用Opencv将蔡老师的篮球教学视频以素描的形式呈现出来。

1.安装Python的Opencv

首先我们装上Python版本的OpenCV,在命令行/终端输入

pip install opencv-python

如果是已经自带Python2.7的系统(Mac等)记得加上3:

pip3 install opencv-python

我的就是已经安装好了:

2.读取照片

安装好了Opencv后,就到了加载蔡老师照片的时候了。

先保存蔡老师的一张照片,并命名为“cxk.jpg”,并放置在与代码相同的路径下:

 代码:

import cv2

img_cxk = cv2.imread('cxk.jpg')
cv2.imshow('cxk', img_cxk)

cv2.waitKey(0)
cv2.destroyAllWindows()

 运行:

稍微解释一下上面的代码,我们利用cv2.imread('cxk.jpg')读取了这张图片,保存到img_cxk这个变量里面。

接下来用cv2.imshow('cxk', img_cxk)将这张照片通过一个窗口显示出来,并且这个窗口的名称叫做cxk。

然后我们用cv2.waitKey(0)来等待用户的按键操作,waitKey(n)里的n表示等待多少毫秒的时间,超过这个时间程序就会继续运行下去。我们把它设为0表示无限等待下去,也就是只要用户没有在这个窗口内按下任何按键,程序就会一直停在这里。

最后,当用户按下任意按键,程序执行cv2.destroyAllWindows(),把窗口都关掉,程序结束。

OK!我们继续跟着蔡老师一起打篮球!

3.彩色图片转成灰度图

接下来我们要把彩色图片转换成灰度图:

import cv2

img_cxk = cv2.imread('cxk.jpg')
cv2.imshow('cxk', img_cxk)

img_gray = cv2.cvtColor(img_cxk, cv2.COLOR_RGB2GRAY)
cv2.imshow('gray', img_gray)

cv2.waitKey(0)
cv2.destroyAllWindows() 

没错,将彩色RGB图片转换成灰度图用

cv2.cvtColor(img_cxk, cv2.COLOR_RGB2GRAY)

就可以啦。但是要注意这里我们用的是cv2.COLOR_RGB2GRAY,对于一些老版本的OpenCV或者其他模块可能还是采用BGR的通道顺序,也就是cv2.COLOR_BGR2GRAY,这里如果出问题了记得看看是不是彩色通道顺序问题。

上面这段代码执行后我们就多了一个窗口,里面显示的是灰色的蔡老师:

4.对灰度图进行高斯模糊

接下来让我们对这张灰度图进行高斯模糊:

import cv2

#加载照片
img_cxk = cv2.imread('cxk.jpg')
cv2.imshow('cxk', img_cxk)

#灰度图
img_gray = cv2.cvtColor(img_cxk, cv2.COLOR_RGB2GRAY)
cv2.imshow('gray', img_gray)

#高斯模糊
img_blurred = cv2.GaussianBlur(img_gray, (5, 5), 0)
cv2.imshow('blurred', img_blurred)

cv2.waitKey(0)
cv2.destroyAllWindows()

在这里,我们用

cv2.GaussianBlur(img_gray, (5, 5), 0)

完成了图像的高斯模糊,那么模糊效果是如何实现的呢?其实很简单,我们从最简单的模糊效果说起,假设图片中有一块区域如下:

我们要对中间5这个像素进行模糊,最简单的方式就是直接和周围八个像素一起取平均值嘛!中间值=(8*1+5)/9=1.44444……

高斯模糊也是类似,只不过不是简单的取平均值,而是通过高斯函数计算对应的数值,有兴趣的话可以去了解,这里就不多解释啦。

那么我们在

cv2.GaussianBlur(img_gray, (5, 5), 0)

中使用的(5,5)参数就表示高斯核的尺寸,这个核尺寸越大图像越模糊。但是记住尺寸得是奇数!这是为了保证中心位置是一个像素而不是四个像素。

这样我们就得到一个模糊的蔡老师(通过对比可以看出确实模糊了):

 尺寸换成(15,15),图片就更加模糊了:

5.图像二值化

接下来到关键的一步啦!让我们对这张模糊过的图片进行二值化:

import cv2

#加载照片
img_cxk = cv2.imread('cxk.jpg')
cv2.imshow('cxk', img_cxk)

#灰度图
img_gray = cv2.cvtColor(img_cxk, cv2.COLOR_RGB2GRAY)
cv2.imshow('gray', img_gray)

#高斯模糊
img_blurred = cv2.GaussianBlur(img_gray, (15, 15), 0)
cv2.imshow('blurred', img_blurred)

#图像二值化
img_threshold1 = cv2.adaptiveThreshold(img_blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 5, 2)
cv2.imshow('img_threshold1', img_threshold1)

cv2.waitKey(0)
cv2.destroyAllWindows()
 

二值化的概念其实很简单,就是对一张图片上的点,像素值大于等于某个值的都直接设为最大值,小于这个值的都直接设为最小值,这样这张图片上每个点都只可能是最大值或最小值其中之一了,其中我们比较的这个数值就是阈值。

当然如果对整张图片都规定同一个阈值,可能会出现下图右上的效果,因为实际图片还有阴影之类的问题会影响,这样就出

现了自适应二值化的方法,如下图下排两张图。

在代码中我们用cv2.adaptiveThreshold()来实现这种自适应二值化方法。其中参数255表示我们二值化后图像的最大值,cv2.ADAPTIVE_THRESH_GAUSSIAN_C表示我们采用的自适应方法,cv2.THRESH_BINARY表示我们是将大于阈值的像素点的值变成最大值,反之这里如果使用cv2.THRESH_BINARY_INV表示我们是将大于阈值的像素点的值变成0,倒数第二个参数5表示我们用多大尺寸的区块来计算阈值,倒数第一个参数2表示计算周边像素点均值时待减去的常数C。

运行后就可以得到一个二值化的蔡老师:

 6.再次对二值化图像进行模糊

由于采用了自适应二值化的方法,原本深色衣服的地方也自适应地变成了白色,实现了一个简单描边效果。现在我们已经初步实现了素描效果,但是还不够,让我们继续完善一下,让边线更宽,噪点更少一些。

import cv2

#加载照片
img_cxk = cv2.imread('cxk.jpg')
cv2.imshow('cxk', img_cxk)

#灰度图
img_gray = cv2.cvtColor(img_cxk, cv2.COLOR_RGB2GRAY)
cv2.imshow('gray', img_gray)

#高斯模糊
img_blurred = cv2.GaussianBlur(img_gray, (5, 5), 0)
cv2.imshow('blurred', img_blurred)

#图像二值化
img_threshold1 = cv2.adaptiveThreshold(img_blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 5, 2)
cv2.imshow('img_threshold1', img_threshold1)

#再次模糊
img_threshold1_blurred = cv2.GaussianBlur(img_threshold1, (5, 5), 0)
cv2.imshow('img_threshold1_blurred', img_threshold1_blurred)

cv2.waitKey(0)
cv2.destroyAllWindows()
 

和刚才一样我们用cv2.GaussianBlur()完成了高斯模糊,这样我们就可以得到一个模糊的描边蔡老师:

7.再次进行二值化 

接下来我们对这张图片再次进行二值化:

import cv2

#加载照片
img_cxk = cv2.imread('cxk.jpg')
cv2.imshow('cxk', img_cxk)

#灰度图
img_gray = cv2.cvtColor(img_cxk, cv2.COLOR_RGB2GRAY)
cv2.imshow('gray', img_gray)

#高斯模糊 尺寸越大就越模糊
img_blurred = cv2.GaussianBlur(img_gray, (5, 5), 0)
cv2.imshow('blurred', img_blurred)

#图像二值化
img_threshold1 = cv2.adaptiveThreshold(img_blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 5, 2)
cv2.imshow('img_threshold1', img_threshold1)

#再次模糊
img_threshold1_blurred = cv2.GaussianBlur(img_threshold1, (5, 5), 0)
cv2.imshow('img_threshold1_blurred', img_threshold1_blurred)

#再次二值化
_, img_threshold2 = cv2.threshold(img_threshold1_blurred, 200, 255, cv2.THRESH_BINARY)
cv2.imshow('img_threshold2', img_threshold2)

cv2.waitKey(0)
cv2.destroyAllWindows()
 

和刚才不一样的是,由于这张图片已经比较干净没有什么阴影,我们直接采用最简单的二值化方法

cv2.threshold(img_threshold1_blurred, 200, 255, cv2.THRESH_BINARY)。其中200表示将图片中像素值为200以上的点都变成255,255就是白色。这样我们就能得到一个边线更宽的二值化效果:

8.图像开运算 

下面让我们去掉图片中一些细小的噪点,这种效果可以通过图像的开运算来实现:

import cv2

#加载照片
img_cxk = cv2.imread('cxk.jpg')
cv2.imshow('cxk', img_cxk)

#灰度图
img_gray = cv2.cvtColor(img_cxk, cv2.COLOR_RGB2GRAY)
cv2.imshow('gray', img_gray)

#高斯模糊 尺寸越大就越模糊
img_blurred = cv2.GaussianBlur(img_gray, (5, 5), 0)
cv2.imshow('blurred', img_blurred)

#图像二值化
img_threshold1 = cv2.adaptiveThreshold(img_blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 5, 2)
cv2.imshow('img_threshold1', img_threshold1)

#再次模糊
img_threshold1_blurred = cv2.GaussianBlur(img_threshold1, (5, 5), 0)
cv2.imshow('img_threshold1_blurred', img_threshold1_blurred)

#再次二值化
_, img_threshold2 = cv2.threshold(img_threshold1_blurred, 200, 255, cv2.THRESH_BINARY)
cv2.imshow('img_threshold2', img_threshold2)

kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(3, 3))
img_opening = cv2.bitwise_not(cv2.morphologyEx(cv2.bitwise_not(img_threshold2), cv2.MORPH_OPEN, kernel))
cv2.imshow('img_opening', img_opening)

cv2.waitKey(0)
cv2.destroyAllWindows()
 

要理解图像的开运算就要知道图像的腐蚀和膨胀,所谓的图像腐蚀就是如下的操作,类似于把一个胖子缩小一圈变瘦的感觉:

图像膨胀就是腐蚀的反向操作,把图像中的区块变大一圈,把瘦子变成胖子。

因此当我们对一个图像先腐蚀再膨胀的时候,一些小的区块就会由于腐蚀而消失,再膨胀回来的时候大块区域的边线的宽度没有发生变化,这样就起到了消除小的噪点的效果。图像先腐蚀再膨胀的操作就叫做开运算。

 

回到我们的代码,首先开运算要有一个运算的核,我们通过:

kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(3, 3))

可以得到一个3*3的核。

然后通过

cv2.morphologyEx(cv2.bitwise_not(img_threshold2), cv2.MORPH_OPEN, kernel)

来进行图像的开运算。

这里需要注意的是我们没有直接对img_threshold2进行开运算,而是对cv2.bitwise_not(img_threshold2)进行运算,那么cv2.bitwise_not(img_threshold2)又是干啥的呢?

cv2.bitwise_not()其实就是对图像进行一个简单的操作,原来是0的像素点变成最大值,原来是最大值的像素点变成0。相当于黑色的地方变成白色,白色的地方变成黑色。

那么为什么我们要把图像反色一下再进行开运算呢?首先要知道图像中一般0表示黑色,255表示白色,我们看到的大片的白色底少部分黑色线其实从数值上来看是图片大部分地方是255少部分地方是0。但是开运算的操作是对有数值的地方进行缩小,这样就必须先将图片反色一下,使得大部分地方是0而少部分线的地方是255,然后再进行开运算就能得到正确的结果了。

这样我们就得到一个更少噪点蔡老师啦:

9.第三次对图像进行高斯模糊

接下来我们对这张二值化的图像再简单进行高斯模糊,让图片更接近素描的效果。 

import cv2

#加载照片
img_cxk = cv2.imread('cxk.jpg')
cv2.imshow('cxk', img_cxk)

#灰度图
img_gray = cv2.cvtColor(img_cxk, cv2.COLOR_RGB2GRAY)
cv2.imshow('gray', img_gray)

#高斯模糊 尺寸越大就越模糊
img_blurred = cv2.GaussianBlur(img_gray, (5, 5), 0)
cv2.imshow('blurred', img_blurred)

#图像二值化
img_threshold1 = cv2.adaptiveThreshold(img_blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 5, 2)
cv2.imshow('img_threshold1', img_threshold1)

#再次模糊
img_threshold1_blurred = cv2.GaussianBlur(img_threshold1, (5, 5), 0)
cv2.imshow('img_threshold1_blurred', img_threshold1_blurred)

#再次二值化
_, img_threshold2 = cv2.threshold(img_threshold1_blurred, 200, 255, cv2.THRESH_BINARY)
cv2.imshow('img_threshold2', img_threshold2)

#开运算
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(3, 3))
img_opening = cv2.bitwise_not(cv2.morphologyEx(cv2.bitwise_not(img_threshold2), cv2.MORPH_OPEN, kernel))
cv2.imshow('img_opening', img_opening)

#第三次高斯模糊
img_opening_blurred = cv2.GaussianBlur(img_opening, (3, 3), 0)
cv2.imshow('img_opening_blurred', img_opening_blurred)

cv2.waitKey(0)
cv2.destroyAllWindows()
 

这样下来我们就可以实现对一张彩色图片转换成素描的效果啦:

 

10.读取并处理视频中的图像

搞定了单张图片,对视频进行处理就非常简单了,只需要将视频里每一帧都做同样的处理再输出即可。

首先在开头位置加上读取视频的语句:

cap = cv2.VideoCapture('cxk.mp4')

然后创建一个while循环,将图像处理的语句都放进去:

while True:
    ret, frame = cap.read()
    if frame is None:
        break

    img_gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)

    img_blurred = cv2.GaussianBlur(img_gray, (5, 5), 0)

    img_threshold1 = cv2.adaptiveThreshold(img_blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 5, 2)

    img_threshold1_blurred = cv2.GaussianBlur(img_threshold1, (5, 5), 0)

    _, img_threshold2 = cv2.threshold(img_threshold1_blurred, 200, 255, cv2.THRESH_BINARY)

    kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(3, 3))
    img_opening = cv2.bitwise_not(cv2.morphologyEx(cv2.bitwise_not(img_threshold2), cv2.MORPH_OPEN, kernel))

    img_opening_blurred = cv2.GaussianBlur(img_opening, (3, 3), 0)
    cv2.imshow('img_opening_blurred', img_opening_blurred)

    if cv2.waitKey(40) & 0xFF == ord('q'):
        break

其中,cap.read()用来读取视频每一帧的数据,每一次调用就读取一帧图像,当读取到的frame是None时就说明视频结束,可以直接退出while循环。

在while循环的最后我们用cv2.waitKey(40) & 0xFF == ord('q')来判断用户有没有按下键盘上的q键,如果按下了就直接退出while循环。

而cv2.waitKey(40)中我们填40表示等待40毫秒,也就相当于每两张图片之间间隔40毫秒,即25帧/秒。

最后,完整的代码如下:

import cv2

cap = cv2.VideoCapture('cxk.mp4')

while True:
    ret, frame = cap.read()
    if frame is None:
        break

    img_gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)

    img_blurred = cv2.GaussianBlur(img_gray, (5, 5), 0)

    img_threshold1 = cv2.adaptiveThreshold(img_blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 5, 2)

    img_threshold1_blurred = cv2.GaussianBlur(img_threshold1, (5, 5), 0)

    _, img_threshold2 = cv2.threshold(img_threshold1_blurred, 200, 255, cv2.THRESH_BINARY)

    kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(3, 3))
    img_opening = cv2.bitwise_not(cv2.morphologyEx(cv2.bitwise_not(img_threshold2), cv2.MORPH_OPEN, kernel))

    img_opening_blurred = cv2.GaussianBlur(img_opening, (3, 3), 0)
    cv2.imshow('img_opening_blurred', img_opening_blurred)

    if cv2.waitKey(40) & 0xFF == ord('q'):
        break

cv2.destroyAllWindows()

 

最后,我们就完成了素描版的鸡你太美!

参考文章:蔡徐坤教你用OpenCV实现素描效果 – 知乎

来源:Horizon629

物联沃分享整理
物联沃-IOTWORD物联网 » Opencv实现素描的坤坤打篮球

发表评论