You are on page 1of 483

近期小白学视觉公众号推出了多篇 Python+OpenCV 实战项目的文章,深受小伙伴们的喜爱。最

近有小伙伴推荐,希望可以讲经典的项目整理一下,集成手册,便于小伙伴在日常的学习中使用。于
是小白挑选了# OpenCV 的应用#专栏中的 52 篇经典内容,集结成册,便于小伙伴们阅读和学习。

本手册中主要涉及以下几部分,首先是对 OpenCV 中自带的基本函数进行介绍。其次是 OpenCV


的实战项目,一方面是基于实际项目利用 OpenCV 实现特定对象的检测,例如车道线检测、路面的坑
洼检测、等;另一方面是基于 OpenCV 实现图像增强,例如利用 OpenCV 消除运动所引起的图像模
糊、去除阴影影响等。最后是 OpenCV 与深度学习等其他相结合实现图像分割、人脸检测、人脸跟踪、
运动检测等难度较大的问题。

所有的项目都是通过 Python 代码实现,并且配有文字讲解和源代码,便于小伙伴们入门学习,


通过实战项目,更加了解到计算机视觉在日常生活中都能做什么,一点带面,融会贯通。

因为本手册是处于实时更新和维护的状态,因此会有一些内容变动,为了使小伙伴们获取准确的
信息,因此手册中项目的源码不在书中给出。小伙伴们关注“小白学视觉”微信公众号,回复【Python
视觉实战项目源码】就可以获得最新的源码信息。

手册中的具体内容如下:
第一部分(源码在文章种):
1. 基于 OpenCV 的图像融合
2. 基于 OpenCV 的显著图绘制
3. 基于 OpenCV 的图像翻转和镜像
4. 基于 OpenCV 的条形码区域分割
5. 基于 OpenCV 的实用图像处理操作
6. 基于 OpenCV 的路面质量检测
7. 基于 OpenCV 修复表格缺失的轮廓
8. 基于 OpenCV 和 Tensorflow 的深蹲检测器
9. 利用 OpenCV 实现基于深度学习的超分辨率处理
10. 使用 OpenCV 在 Python 中访问 IP 摄像头
11. 使用 OpenCV 检测坑洼
12. 使用 OpenCV 进行图像全景拼接
13. 使用 OpenCV 进行颜色分割
14. 使用 OpenCV 实现图像覆盖
15. 使用 OpenCV 实现图像增强
16. 使用 OpenCV 自动去除背景色
17. 使用 OpenCV 构建运动检测器(Translate)

第二部分(源码在 Github 上):


18. 基于 OpenCV 的图像阴影去除
19. 基于 OpenCV 的车辆变道检测
20. 基于 OpenCV 的多位数检测器
21. 基于 OpenCV 的焊件缺陷检测
22. 基于 OpenCV 的人脸追踪
23. 基于 OpenCV 的人员剔除
24. 基于 OpenCV 的实时睡意检测系统
25. 基于 OpenCV 的实时停车地点查找
26. 基于 OpenCV 的图像强度操作
27. 基于 OpenCV 的网络实时视频流传输
28. 基于 OpenCV 的位姿估计
29. 基于 OpenCV 的直方图匹配
30. 基于 OpenCV 的阈值车道标记
31. 基于 OpenCV 建立视差图像
32. 使用 OpenCV 预处理神经网络中的面部图像
33. 使用 OpenCV 实现车道线检测
34. 基于 Python 进行相机校准
35. 基于 OpenCV 的车牌识别
36. 基于 OpenCV 的情绪检测
37. 基于 OpenCV 的表格文本内容提取
38. 基于 OpenCV 的实时面部识别
39. 基于 OpenCV 的图像卡通化
40. 基于 python 和 OpenCV 构建智能停车系统
41. 基于深度学习 OpenCV 与 python 进行字符识别
42. 基于自适应显着性的图像分割
43. 使用 OpenCV 对运动员的姿势进行检测
44. 使用 OpenCV 实现道路车辆计数
45. 使用 OpenCV 实现哈哈镜效果
46. 使用 OpenCV 为视频中美女加上眼线
47. 使用 Python,Keras 和 OpenCV 进行实时面部检测
48. 使用 TensorFlow 和 OpenCV 实现口罩检测
49. 使用 TensorFlow+OpenCV 的社交距离检测器
50. 使用深度学习和 OpenCV 的早期火灾检测系统
51. 用 OpenCV 实现猜词游戏
52. 基于 OpenCV 的图像分割
第一部分

源码在文章中
(17 个项目)
近期小白学视觉公众号推出了多篇 Python+OpenCV 实战项目的文章,深受小伙伴们的喜爱。最
近有小伙伴推荐,希望可以讲经典的项目整理一下,集成手册,便于小伙伴在日常的学习中使用。于
是小白挑选了# OpenCV 的应用#专栏中的 52 篇经典内容,集结成册,便于小伙伴们阅读和学习。

本手册中主要涉及以下几部分,首先是对 OpenCV 中自带的基本函数进行介绍。其次是 OpenCV


的实战项目,一方面是基于实际项目利用 OpenCV 实现特定对象的检测,例如车道线检测、路面的坑
洼检测、等;另一方面是基于 OpenCV 实现图像增强,例如利用 OpenCV 消除运动所引起的图像模
糊、去除阴影影响等。最后是 OpenCV 与深度学习等其他相结合实现图像分割、人脸检测、人脸跟踪、
运动检测等难度较大的问题。

所有的项目都是通过 Python 代码实现,并且配有文字讲解和源代码,便于小伙伴们入门学习,


通过实战项目,更加了解到计算机视觉在日常生活中都能做什么,一点带面,融会贯通。

因为本手册是处于实时更新和维护的状态,因此会有一些内容变动,为了使小伙伴们获取准确的
信息,因此手册中项目的源码不在书中给出。小伙伴们关注“小白学视觉”微信公众号,回复【Python
视觉实战项目源码】就可以获得最新的源码信息。

手册中的具体内容如下:
第一部分(源码在文章种):
1. 基于 OpenCV 的图像融合
2. 基于 OpenCV 的显著图绘制
3. 基于 OpenCV 的图像翻转和镜像
4. 基于 OpenCV 的条形码区域分割
5. 基于 OpenCV 的实用图像处理操作
6. 基于 OpenCV 的路面质量检测
7. 基于 OpenCV 修复表格缺失的轮廓
8. 基于 OpenCV 和 Tensorflow 的深蹲检测器
9. 利用 OpenCV 实现基于深度学习的超分辨率处理
10. 使用 OpenCV 在 Python 中访问 IP 摄像头
11. 使用 OpenCV 检测坑洼
12. 使用 OpenCV 进行图像全景拼接
13. 使用 OpenCV 进行颜色分割
14. 使用 OpenCV 实现图像覆盖
15. 使用 OpenCV 实现图像增强
16. 使用 OpenCV 自动去除背景色
17. 使用 OpenCV 构建运动检测器(Translate)

第二部分(源码在 Github 上):


18. 基于 OpenCV 的图像阴影去除
19. 基于 OpenCV 的车辆变道检测
20. 基于 OpenCV 的多位数检测器
21. 基于 OpenCV 的焊件缺陷检测
22. 基于 OpenCV 的人脸追踪
23. 基于 OpenCV 的人员剔除
24. 基于 OpenCV 的实时睡意检测系统
25. 基于 OpenCV 的实时停车地点查找
26. 基于 OpenCV 的图像强度操作
27. 基于 OpenCV 的网络实时视频流传输
28. 基于 OpenCV 的位姿估计
29. 基于 OpenCV 的直方图匹配
30. 基于 OpenCV 的阈值车道标记
31. 基于 OpenCV 建立视差图像
32. 使用 OpenCV 预处理神经网络中的面部图像
33. 使用 OpenCV 实现车道线检测
34. 基于 Python 进行相机校准
35. 基于 OpenCV 的车牌识别
36. 基于 OpenCV 的情绪检测
37. 基于 OpenCV 的表格文本内容提取
38. 基于 OpenCV 的实时面部识别
39. 基于 OpenCV 的图像卡通化
40. 基于 python 和 OpenCV 构建智能停车系统
41. 基于深度学习 OpenCV 与 python 进行字符识别
42. 基于自适应显着性的图像分割
43. 使用 OpenCV 对运动员的姿势进行检测
44. 使用 OpenCV 实现道路车辆计数
45. 使用 OpenCV 实现哈哈镜效果
46. 使用 OpenCV 为视频中美女加上眼线
47. 使用 Python,Keras 和 OpenCV 进行实时面部检测
48. 使用 TensorFlow 和 OpenCV 实现口罩检测
49. 使用 TensorFlow+OpenCV 的社交距离检测器
50. 使用深度学习和 OpenCV 的早期火灾检测系统
51. 用 OpenCV 实现猜词游戏
52. 基于 OpenCV 的图像分割
第一部分

源码在文章中
(17 个项目)
2020/7/29 基于OpenCV和Tensorflow的深蹲检测器

基于OpenCV和Tensorflow的深蹲检测器
原创 努比 小白学视觉 1周前

来自专辑

OpenCV应用

点击上方“小白学视觉”,选择加"星标"或“置顶”

重磅干货,第一时间送达

本期我们将介绍如和使用OpenCV以及Tensorflow实现深蹲检测

在检疫期间,我们的体育活动非常有限,这样并不好。在进行一些居家运动时,我们必须时刻保
持高度的注意力集中,以便记录自己每天的运动量。因此我们希望建立一个自动化的系统来实现
运动量计算。考虑到我们在深蹲时,有明确阶段和大幅度变化的基本运动,实现对深蹲的计数会
相对比较简单。

下面我们就一起尝试实现它吧!

数据采集
使用带相机的Raspberry Pi来获取图片是非常方便的,完成图像的拍摄后再利用OpenCV即可
将获取的图像写入文件系统。

运动识别
最初,我们打算使用图像分割完成人物的提取工作。但是我们都知道图像分割是一项非常繁琐的
操作,尤其是在Raspberry资源有限的情况下。

除此之外,图像分割忽略了一个事实。当前我们所拥有的是一系列图像帧,而不是单个图片。该
图像序列具有明显功能,并且我们后续将要使用到它。

因此,我们从OpenCV 着手进行背景去除,以提供了可靠的结果。

背景扣除
首先,创建一个背景减法器:
backSub = cv.createBackgroundSubtractorMOG2()

向其中添加图像帧:
mask = backSub.apply(frame)

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489276&idx=2&sn=c85b8c99f7a83cbce3d7e1113b806df4&chksm=fb56f410c… 1/6
2020/7/29 基于OpenCV和Tensorflow的深蹲检测器

最后我们可以得到一张带有身体轮廓的图片:

然后扩大图像以突出轮廓。
mask = cv.dilate(mask, None, 3)

将此算法应用于所有图像帧可以得出每一幅图像中的姿势。之后,我们将它们分类为站立,下蹲
以及无三种情况。

接下来我们要把图像中的人提取出来,OpenCV可以帮助我们找到相应的找到轮廓:
cnts, _ = cv.findContours(img, cv.RETR_CCOMP, cv.CHAIN_APPROX_SIMPLE)

这种方法或多或少适用于人物的最大轮廓的提取,但不幸的是,这样处理的结果并不稳定。例
如,检测得到最大的轮廓只能包括人的身体,而不包括他的脚。

但不管怎么说,拥有一系列图像对我很有帮助。通常情况下我们做深蹲运动都发生在同一地点,
因此我们可以假设所有动作都在某个区域内进行并且该区域是稳定的。为此我们可以迭代构建边
界矩形,如果需要,可以以最大轮廓增加边界矩形。
有一个例子:
• 最大的轮廓是红色

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489276&idx=2&sn=c85b8c99f7a83cbce3d7e1113b806df4&chksm=fb56f410c… 2/6
2020/7/29 基于OpenCV和Tensorflow的深蹲检测器

• 轮廓边界矩形为蓝色
• 图边界矩形为绿色

通过以上的边缘提取以及轮廓绘制,可以为进一步处理做好充足准备。

分类
接下来我们将从图像中提取出边界矩形,并将其转化为按尺寸64x64正方形。

以下Mask用作分类器输入:
站立姿势:

下蹲姿势:

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489276&idx=2&sn=c85b8c99f7a83cbce3d7e1113b806df4&chksm=fb56f410c… 3/6
2020/7/29 基于OpenCV和Tensorflow的深蹲检测器

接下来我们将使用Keras 与Tensorflow进行分类。

最初,我们使用了经典的Lenet-5模型,运行结果良好。随后由于阅读了一些有关Lenet-5变体
的文章后,我们决定尝试简化架构。

事实证明,简化后的CNN在当前示例中的精度几乎相同:

1 model = Sequential([
2 Convolution2D(8,(5,5), activation='relu', input_shape=input_shape),
3 MaxPooling2D(),
4 Flatten(),
5 Dense(512, activation='relu'),
6 Dense(3, activation='softmax')
7 ])
8 model.compile(loss="categorical_crossentropy", optimizer=SGD(lr=0.01), metrics=["accu

10个纪元的准确度为86%,20个的准确度为94%,而30个的准确度为96%。训练如果在增加
的话可能会导致过拟合引起准确度的下降,因此接下来我们将把这个模型运用到生活中去。

模型运用
我们将在Raspberry上运行。

加载模型:

1 with open(MODEL_JSON, 'r') as f:


2 model_data = f.read()
3 model = tf.keras.models.model_from_json(model_data)
4 model.load_weights(MODEL_H5)
5 graph = tf.get_default_graph()

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489276&idx=2&sn=c85b8c99f7a83cbce3d7e1113b806df4&chksm=fb56f410c… 4/6
2020/7/29 基于OpenCV和Tensorflow的深蹲检测器

并以此对下蹲Mask进行分类:

1 img = cv.imread(path + f, cv.IMREAD_GRAYSCALE)


2 img = np.reshape(img,[1,64,64,1])
3 with graph.as_default():
4 c = model.predict_classes(img)
5 return c[0] if c else None

在Raspberry上,输入为64x64的分类调用大约需要60-70毫秒,几乎接近实时。

最后让我们将以上所有部分整合到一个应用程序中:
• GET / —一个应用页面(下面有更多信息)
• GET / status-获取当前状态,下蹲次数和帧数
• POST / start —开始练习
• POST / stop —完成练习
• GET / stream —来自摄像机的视频流

如果本文对小伙伴有帮助,希望可以在文末来个“一键三连”。

交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、
检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),请扫描下面微信号加
群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备
注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会
请出群,谢谢理解~

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489276&idx=2&sn=c85b8c99f7a83cbce3d7e1113b806df4&chksm=fb56f410c… 5/6
2020/7/29 利用OpenCV实现基于深度学习的超分辨率处理

利用OpenCV实现基于深度学习的超分辨率处理
原创 小白 小白学视觉 5月24日

来自专辑

OpenCV应用

点击上方“小白学视觉”,选择“星标”公众号
重磅干货,第一时间送达

OpenCV 是 一 个 非 常 强 大 的 计 算 机 视 觉 处 理 的 工 具 库 。 很 多 小 伙 伴 在 入 门 图 像 处 理 时 都 需 要 学 习
OpenCV 的 使 用 。 但 是 随 着 计 算 机 视 觉 技 术 的 发 展 , 越 来 越 多 的 算 法 涌 现 出 来 , 人 们 逐 渐 觉 得
OpenCV比较落后而放弃了使用OpenCV。

但是,实际上OpenCV时一个与时俱进的开源代码库。正在逐渐的吸收和接纳最新的算法。本文我们
来介绍如何使用OpenCV实现基于深度学习的图像超分辨率(SR)。使用OpenCV的好处就是,我们
不需要知道任何图像超分辨率的相关知识,就可以使用这个代码,并实现图像超分辨率。

具体操作步骤:

1. 安装OpenCV contrib模块

OpenCV中的超分辨率功能被集成在了contrib模块中,因此我们首先需要安装OpenCV的扩展模
块。安装过程可以参考【从零学习OpenCV 4】opencv_contrib扩展模块的安装。超分辨率被集成在
dnn_superres模块中,如果小伙伴们电脑空间有限,可以只编译这一个模块。

近期有小伙伴反馈自己安装扩展模块失败,为了解决这个问题,小白近期在筹划搭建一个各个版本
opencv-contrib编译完成的数据库。各位小伙伴随时关注我们公众号的动态。

2. 下载训练的模型

由于某些模型比较大,因此OpenCV代码库中没有包含他们,因此我们在使用的时候需要单独的下载
经过训练的模型。目前,仅支持4种不同的超分辨率模型,他们可以实现2倍、3倍、4倍甚至8倍的图
像方法。这些模型具体如下:

EDSR:这个是表现最好的模型。但是这个模型也是最大的,所以运行速度会比较慢。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247487670&idx=1&sn=e99456df56fa609081ad559cfb7a9efc&chksm=fb56f25ac… 1/6
2020/7/29 利用OpenCV实现基于深度学习的超分辨率处理

ESPCN:这个模型具有速度快,效果好的特点,并且模型较小。它可以进行对视频进行实时处理(取
决于图像大小)。

FSRCNN:这也是具有快速准确推断功能的小型模型。也可以进行实时视频升频。

LapSRN:这是一个中等大小的模型,它的特点是最大可以将图像放大8倍。

公众号后台回复“SR模型”获取下载这四个模型的方式。

3. 通过程序实现超分辨率

我们首先给出C++完整程序,之后对程序中每一行代码进行介绍。完整程序如下:

1 #include <opencv2/dnn_superres.hpp>
2 #include <opencv2/imgproc.hpp>
3 #include <opencv2/highgui.hpp>
4
5 using namespace std;
6 using namespace cv;
7 using namespace dnn;
8 using namespace dnn_superres;
9
10 int main(int argc, char *argv[])
11 {
12 //Create the module's object
13 DnnSuperResImpl sr;
14
15 //Set the image you would like to upscale
16 string img_path = "image.png";
17 Mat img = cv::imread(img_path);
18
19 //Read the desired model
20 string path = "FSRCNN_x2.pb";
21 sr.readModel(path);
22
23 //Set the desired model and scale to get correct pre- and post-processing
24 sr.setModel("fsrcnn", 2);
25
26 //Upscale
27 Mat img_new;
28 sr.upsample(img, img_new);

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247487670&idx=1&sn=e99456df56fa609081ad559cfb7a9efc&chksm=fb56f25ac… 2/6
2020/7/29 利用OpenCV实现基于深度学习的超分辨率处理

29 cv::imwrite( "upscaled.png", img_new);


30
31 return 0;
32 }

首先加载我们选择的模型,并将其输入到神经网络的变量中。需要注意的是模型文件所存在的地址,
本文放置在了程序的根目录中。

1 //Read the desired model


2 string path = "FSRCNN_x2.pb";
3 sr.readModel(path);

之后设置模型的种类和放大系数。本文选择的模型是fsrcnn,放大系数选择的2。

1 //Set the desired model and scale to get correct pre- and post-processing
2 sr.setModel("fsrcnn", 2);

可以选择的模型有“ edsr”,“ fsrcnn”,“ lapsrn”,“ espcn”,这几个参数就是我们刚才介绍的4中模


型。需要注意的是,每个模型能够放大的倍数是不一致的。前三种模型能够放大2、3、4倍,最后一
个模型能够放大2、3、4、8倍。

之后通过upsample()函数进行超分辨率放大。

1 //Upscale
2 Mat img_new;
3 sr.upsample(img, img_new);
4 cv::imwrite( "upscaled.png", img_new);

上述是C++代码,接下来给出Python实现超分辨率的代码

1 import cv2
2 from cv2 import dnn_superres
3
4 # Create an SR object
5 sr = dnn_superres.DnnSuperResImpl_create()
6
7 # Read image
8 image = cv2.imread('./input.png')
https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247487670&idx=1&sn=e99456df56fa609081ad559cfb7a9efc&chksm=fb56f25ac… 3/6
2020/7/29 利用OpenCV实现基于深度学习的超分辨率处理

9
10 # Read the desired model
11 path = "EDSR_x3.pb"
12 sr.readModel(path)
13
14 # Set the desired model and scale to get correct pre- and post-processing
15 sr.setModel("edsr", 3)
16
17 # Upscale the image
18 result = sr.upsample(image)
19
20 # Save the image
21 cv2.imwrite("./upscaled.png", result)

不同于C++代码,在使用python代码时,需要先通过如下代码进行声明。

1 # Create an SR object
2 sr = dnn_superres.DnnSuperResImpl_create()

4. 处理结果

输入图像

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247487670&idx=1&sn=e99456df56fa609081ad559cfb7a9efc&chksm=fb56f25ac… 4/6
2020/7/29 利用OpenCV实现基于深度学习的超分辨率处理

双线性插值放大3倍

FSRCNN放大3倍

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247487670&idx=1&sn=e99456df56fa609081ad559cfb7a9efc&chksm=fb56f25ac… 5/6
2020/7/29 利用OpenCV实现基于深度学习的超分辨率处理

ESDR放大3倍

//
//

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247487670&idx=1&sn=e99456df56fa609081ad559cfb7a9efc&chksm=fb56f25ac… 6/6
2020/9/30 如何使用OpenCV在Python中访问IP摄像头

如何使用OpenCV在Python中访问IP摄像头
原创 花生 小白学视觉 9月7日

点击上方“小白学视觉”,选择加"星标"或“置顶”
重磅干货,第一时间送达

在此文章中,我将解释如何在Python中设置对IP摄像机流的访问。

首先,必须找出网址流是什么。通过在构造函数中提供摄像机的网址流,可以在OpenCV
中访问IP摄像机 cv2.VideoCapture 。可以使用某些网络扫描实用程序(例如在linux
上 的 arp-scan ) 找 到 摄 像 机 的 IP 地 址 。 网 址 进 一 步 的 细 节 , 如 Protocol ,
Credentials 和 Channel 应该可以在相机说明书或软件/手机应用程序中找到。我们
通过在网络上搜索相机的型号来找到相机的网址流。

通常,摄像机使用RTSP或HTTP协议来传输视频。IP摄像机网址流的示例如下所示:
rtsp://192.168.1.64/1

因此,可以通过以下代码实现使用OpenCV从相机获取快照:

1 capture = cv2.VideoCapture('rtsp://192.168.1.64/1')

由于大多数IP摄像机都有用于访问视频的用户名和密码。在这种情况下,必须在网址流中
提供凭据,如下所示:

1 capture = cv2.VideoCapture('rtsp://[username]:
2 [password]@192.168.1.64/1')

这是整个脚本,可以实现通过OpenCV捕获来自摄像机的视频流:

1 import cv2
2
3 #print("Before URL")
4 cap = cv2.VideoCapture('rtsp://admin:123456@192.168.1.216/H264?ch=1&sub
5 #print("After URL")
6
7 while True:

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490892&idx=1&sn=d79a3b91431b25f0d8abad2527931921&chksm=fb56ffa0… 1/3
2020/9/30 如何使用OpenCV在Python中访问IP摄像头

8
9 #print('About to start the Read command')
10 ret, frame = cap.read()
11 #print('About to show frame of Video.')
12 cv2.imshow("Capturing",frame)
13 #print('Running..')
14
15 if cv2.waitKey(1) & 0xFF == ord('q'):
16 break
17
18 cap.release()
19 cv2.destroyAllWindows()

我们需要启动 'While True' 循环以显示流。在循环中启动它很重要,这样可以中断循


环以按需释放流。

命令 'cv2.imshow' 用于显示视频流。

命令 'cv2.imshow' 带有两个参数。第一个是要显示在窗口顶部的名称。可以将其更改
为所需的任何内容,但是最好拥有它。第二个是存储捕获视频流的对象。在此示例中,它
称为“帧”。

然后,这个脚本会查找按键。因此,当按下q键时,它将释放捕获的流,然后运
行 'cv2.destroyAllWindows()' 。如果脚本中没有该部分,则可能最终导致流在
PC上引起大量延迟,直到强制关闭该流或该流因自然原因而死亡。

交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计
算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),
请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交
大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入
相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490892&idx=1&sn=d79a3b91431b25f0d8abad2527931921&chksm=fb56ffa0… 2/3
2020/7/29 使用OpenCV检测坑洼

使用OpenCV检测坑洼
原创 花生 小白学视觉 6月14日

来自专辑

OpenCV应用

点击上方“小白学视觉”,选择“星标”
重磅干货,第一时间送达

本文将向大家介绍如何使用OpenCV库进行坑洼检测。

为什么要检测坑洼?

坑洼是道路的结构性指标,事先发现坑洼地可以延长高速公路的使用寿命,防止事故的发生,同时降
低死亡率。
一种可行的解决方案是构建自动坑洞检测系统,该系统可通过云服务发送实时信息以提醒管理结构,
来杜绝每天人工检查所产生的不必要花费。
OpenCV是一个帮助研究人员处理图像问题的库,该库提供了大量处理图像的方法。OpenCV的使用将
有助于坑洼检测。

图像的基础知识

在了解代码之前,必须先了解图像的工作原理。
图像一般被划分为很多像素,每个像素的值范围介于 0 和 255 之间。转换为灰度时,范围从 0 到
1。

大小为28x28的灰度图像

可以操作图像的每个像素。例如,如果希望随机像素具有另一个值,则有两种方法。第一种是通过直接更
改矩阵中的点来更改这一点。第二种是使用 内核东西来实现。
https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488317&idx=1&sn=8c1e5192f1a526bef98f2c03031ab47e&chksm=fb56f1d1c… 1/6
2020/7/29 使用OpenCV检测坑洼

内核是具有一定值的小矩阵,通常为 3x3,叠加在图像上充当滤波器。

(40*0) + (42*1) + (46*0) + (46*0) + (50*0) + (55*0) + (52*0) + (56*0) +


(58*0) = 42

上图显示了图像与内核卷积的结果。卷积是通过数值的相乘相加得到输出结果的过程。卷积可以实现图像
的模糊,例如下图所示。

所选内核对输入图像进行了模糊

阈值

阈值的概念很简单,给定一个图像,绘制其直方图并选择一个值。比该值大的每个像素都将变为黑
色,比该值小的每个像素将变为白色,具体如下所示。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488317&idx=1&sn=8c1e5192f1a526bef98f2c03031ab47e&chksm=fb56f1d1c… 2/6
2020/7/29 使用OpenCV检测坑洼

根据照明选择不同阈值的自适应阈值方法(这一方法可用于检测坑洞)。更多算法可以在OpenCV阈
值文档中找到。

边缘检测

边缘检测算法将在图像中找到边缘。Canny是一种边缘检测算法,它将检测图像的边缘,并输出仅具
有轮廓的图像。进一步的解释可以在这里找到。

使用不同参数应用的 Canny 图像

坑洼检测

我们可以将前面介绍的内核+阈值+边缘检测结合起来,并在道路上找到坑洼。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488317&idx=1&sn=8c1e5192f1a526bef98f2c03031ab47e&chksm=fb56f1d1c… 3/6
2020/7/29 使用OpenCV检测坑洼

图1显示了从道路上拍摄的图像,该道路的坑洼直接位于汽车前

图2显示了应用了阈值处理的图像,坑洼和清洁街道区域被突出显示。

Canny应用于图片3,其中可以找到轮廓。在这里,可以创建一个算法,以便查看轮廓是否为坑洞。

图4显示了选中坑洞的图像。

最终图像,带有绿色标记的区域为坑洞的位置。

更多坑洼检测的结果如下图所示。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488317&idx=1&sn=8c1e5192f1a526bef98f2c03031ab47e&chksm=fb56f1d1c… 4/6
2020/7/29 使用OpenCV检测坑洼

使用OpenCV进行坑洞检测并不难。此外,我们可以构建检测系统并将其与云和地图服务结合,以便
提供有关选定区域坑洞的实时信息。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488317&idx=1&sn=8c1e5192f1a526bef98f2c03031ab47e&chksm=fb56f1d1c… 5/6
2020/7/29 使用OpenCV进行图像全景拼接

使用OpenCV进行图像全景拼接
原创 小白 小白学视觉 昨天

来自专辑

OpenCV应用

点击上方“AI小白学视觉”,选择加"星标"或“置顶”

重磅干货,第一时间送达

图像拼接是计算机视觉中最成功的应用之一。如今,很难找到不包含此功能的手机或图像处理API。在本
文中,我们将讨论如何使用Python和OpenCV进行图像拼接。也就是,给定两张共享某些公共区域的图
像,目标是“缝合”它们并创建一个全景图像场景。当然也可以是给定多张图像,但是总会转换成两张共享
某些公共区域图像拼接的问题,因此本文以最简单的形式进行介绍。

本文主要的知识点包含一下内容:
关键点检测
局部不变描述符(SIFT,SURF等)
特征匹配
使用RANSAC进行单应性估计
透视变换

我们需要拼接的两张图像如下:

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489717&idx=1&sn=a384aedfd25f5f081acd3eb21e98778d&chksm=fb56fa59… 1/11
2020/7/29 使用OpenCV进行图像全景拼接

特征检测与提取
给定上述一对图像,我们希望将它们缝合以创建全景场景。重要的是要注意,两个图像都需要
有一些公共区域。当然,我们上面给出的两张图像时比较理想的,有时候两个图像虽然具有公
共区域,但是同样还可能存在缩放、旋转、来自不同相机等因素的影响。但是无论哪种情况,
我们都需要检测图像中的特征点。

关键点检测

最初的并且可能是幼稚的方法是使用诸如Harris Corners之类的算法来提取关键点。然后,我
们可以尝试基于某种相似性度量(例如欧几里得距离)来匹配相应的关键点。众所周知,角点
具有一个不错的特性:角点 不变。这意味着,一旦检测到角点,即使旋转图像,该角点仍将存
在。

但是,如果我们旋转然后缩放图像怎么办?在这种情况下,我们会很困难,因为角点的大小不
变。也就是说,如果我们放大图像,先前检测到的角可能会变成一条线!

总而言之,我们需要旋转和缩放不变的特征。那就是更强大的方法(如SIFT,SURF和
ORB)。

关键点和描述符

诸如SIFT和SURF之类的方法试图解决角点检测算法的局限性。通常,角点检测器算法使用固
定大小的内核来检测图像上的感兴趣区域(角)。不难看出,当我们缩放图像时,该内核可能
变得太小或太大。为了解决此限制,诸如SIFT之类的方法使用高斯差分(DoD)。想法是将
DoD应用于同一图像的不同缩放版本。它还使用相邻像素信息来查找和完善关键点和相应的描
述符。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489717&idx=1&sn=a384aedfd25f5f081acd3eb21e98778d&chksm=fb56fa59… 2/11
2020/7/29 使用OpenCV进行图像全景拼接

首先,我们需要加载2个图像,一个查询图像和一个训练图像。最初,我们首先从两者中提取
关键点和描述符。通过使用OpenCV detectAndCompute()函数,我们可以一步完成它。请注
意,为了使用detectAndCompute(),我们需要一个关键点检测器和描述符对象的实例。它可
以是ORB,SIFT或SURF等。此外,在将图像输入给detectAndCompute()之前,我们将其转
换为灰度。

1 def detectAndDescribe(image, method=None):


2 """
3 Compute key points and feature descriptors using an specific method
4 """
5
6 assert method is not None, "You need to define a feature detection method. Value
7
8 # detect and extract features from the image
9 if method == 'sift':
10 descriptor = cv2.xfeatures2d.SIFT_create()
11 elif method == 'surf':
12 descriptor = cv2.xfeatures2d.SURF_create()
13 elif method == 'brisk':
14 descriptor = cv2.BRISK_create()
15 elif method == 'orb':
16 descriptor = cv2.ORB_create()
17
18 # get keypoints and descriptors
19 (kps, features) = descriptor.detectAndCompute(image, None)
20
21 return (kps, features)

我们为两个图像都设置了一组关键点和描述符。如果我们使用SIFT作为特征提取器,它将为
每个关键点返回一个128维特征向量。如果选择SURF,我们将获得64维特征向量。下图显示
了使用SIFT,SURF,BRISK和ORB得到的结果。

使用ORB和汉明距离检测关键点和描述符

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489717&idx=1&sn=a384aedfd25f5f081acd3eb21e98778d&chksm=fb56fa59… 3/11
2020/7/29 使用OpenCV进行图像全景拼接

使用SIFT检测关键点和描述符

使用SURF检测关键点和描述符

使用BRISK和汉明距离检测关键点和描述符

特征匹配

如我们所见,两个图像都有大量特征点。现在,我们想比较两组特征,并尽可能显示更多相似性的特征点
对 。 使 用 OpenCV , 特 征 点 匹 配 需 要 Matcher 对 象 。 在 这 里 , 我 们 探 索 两 种 方 式 : 暴 力 匹 配 器
(BruteForce)和KNN(k最近邻)。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489717&idx=1&sn=a384aedfd25f5f081acd3eb21e98778d&chksm=fb56fa59… 4/11
2020/7/29 使用OpenCV进行图像全景拼接

BruteForce(BF)Matcher的作用恰如其名。给定2组特征(来自图像A和图像B),将A组的每个特征与B
组的所有特征进行比较。默认情况下,BF Matcher计算两点之间的欧式距离。因此,对于集合A中的每个
特征,它都会返回集合B中最接近的特征。对于SIFT和SURF,OpenCV建议使用欧几里得距离。对于ORB
和BRISK等其他特征提取器,建议使用汉明距离。我们要使用OpenCV创建BruteForce Matcher,一般情
况下,我们只需要指定2个参数即可。第一个是距离度量。第二个是是否进行交叉检测的布尔参数。具体
代码如下:

1 def createMatcher(method,crossCheck):
2 "Create and return a Matcher Object"
3
4 if method == 'sift' or method == 'surf':
5 bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=crossCheck)
6 elif method == 'orb' or method == 'brisk':
7 bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=crossCheck)
8 return bf

交叉检查布尔参数表示这两个特征是否具有相互匹配才视为有效。换句话说,对于被认为有效的一对特征
(f1,f2),f1需要匹配f2,f2也必须匹配f1作为最接近的匹配。此过程可确保提供更强大的匹配功能集,
这在原始SIFT论文中进行了描述。

但是,对于要考虑多个候选匹配的情况,可以使用基于KNN的匹配过程。KNN 不会返回给定特征的单个
最佳匹配,而是返回k个最佳匹配。需要注意的是,k的值必须由用户预先定义。如我们所料,KNN提供
了更多的候选功能。但是,在进一步操作之前,我们需要确保所有这些匹配对都具有鲁棒性。

比率测试
为了确保KNN返回的特征具有很好的可比性,SIFT论文的作者提出了一种称为比率测试的技术。一般情
况下,我们遍历KNN得到匹配对,之后再执行距离测试。对于每对特征(f1,f2),如果f1和f2之间的距
离在一定比例之内,则将其保留,否则将其丢弃。同样,必须手动选择比率值。

本质上,比率测试与BruteForce Matcher的交叉检查选项具有相同的作用。两者都确保一对检测到的特征
确实足够接近以至于被认为是相似的。下面2个图显示了BF和KNN Matcher在SIFT特征上的匹配结果。我
们选择仅显示100个匹配点以清晰显示。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489717&idx=1&sn=a384aedfd25f5f081acd3eb21e98778d&chksm=fb56fa59… 5/11
2020/7/29 使用OpenCV进行图像全景拼接

使用KNN和SIFT的定量测试进行功能匹配

在SIFT特征上使用暴力匹配器进行特征匹配

需要注意的是,即使做了多种筛选来保证匹配的正确性,也无法完全保证特征点完全正确匹配。尽管如
此,Matcher算法仍将为我们提供两幅图像中最佳(更相似)的特征集。接下来,我们利用这些点来计算
将两个图像的匹配点拼接在一起的变换矩阵。

这种变换称为单应矩阵。简而言之,单应性是一个3x3矩阵,可用于许多应用中,例如相机姿态估计,透
视校正和图像拼接。它将点从一个平面(图像)映射到另一平面。

估计单应性

随机采样一致性(RANSAC)是用于拟合线性模型的迭代算法。与其他线性回归器不同,RANSAC被设计
为对异常值具有鲁棒性。

像线性回归这样的模型使用最小二乘估计将最佳模型拟合到数据。但是,普通最小二乘法对异常值非常敏
感。如果异常值数量很大,则可能会失败。RANSAC通过仅使用数据中的一组数据估计参数来解决此问
题。下图显示了线性回归和RANSAC之间的比较。需要注意数据集包含相当多的离群值。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489717&idx=1&sn=a384aedfd25f5f081acd3eb21e98778d&chksm=fb56fa59… 6/11
2020/7/29 使用OpenCV进行图像全景拼接

我们可以看到线性回归模型很容易受到异常值的影响。那是因为它试图减少平均误差。因此,它倾向于支
持使所有数据点到模型本身的总距离最小的模型。包括异常值。相反,RANSAC仅将模型拟合为被识别为
点的点的子集。

这个特性对我们的用例非常重要。在这里,我们将使用RANSAC来估计单应矩阵。事实证明,单应矩阵对
我们传递给它的数据质量非常敏感。因此,重要的是要有一种算法(RANSAC),该算法可以从不属于数
据分布的点中筛选出明显属于数据分布的点。

估计了单应矩阵后,我们需要将其中一张图像变换到一个公共平面上。在这里,我们将对其中一张图像应
用透视变换。透视变换可以组合一个或多个操作,例如旋转,缩放,平 移 或 剪 切 。 我 们 可 以 使 用
OpenCV warpPerspective()函数。它以图像和单应矩阵作为输入。

1 # Apply panorama correction


2 width = trainImg.shape[1] + queryImg.shape[1]
3 height = trainImg.shape[0] + queryImg.shape[0]
4
5 result = cv2.warpPerspective(trainImg, H, (width, height))
6 result[0:queryImg.shape[0], 0:queryImg.shape[1]] = queryImg
7
8 plt.figure(figsize=(20,10))
9 plt.imshow(result)
10
11 plt.axis('off')

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489717&idx=1&sn=a384aedfd25f5f081acd3eb21e98778d&chksm=fb56fa59… 7/11
2020/7/29 使用OpenCV进行图像全景拼接

12 plt.show()

生成的全景图像如下所示。如我们所见,结果中包含了两个图像中的内容。另外,我们可以看
到一些与照明条件和图像边界边缘效应有关的问题。理想情况下,我们可以执行一些处理技术
来标准化亮度,例如 直方图匹配,这会使结果看起来更真实和自然一些。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489717&idx=1&sn=a384aedfd25f5f081acd3eb21e98778d&chksm=fb56fa59… 8/11
2020/7/29 使用OpenCV进行图像全景拼接

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489717&idx=1&sn=a384aedfd25f5f081acd3eb21e98778d&chksm=fb56fa59… 9/11
2020/7/29 使用OpenCV进行图像全景拼接

交流群
欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、
检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),请扫描下面微信号加
群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备
注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会
请出群,谢谢理解~

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489717&idx=1&sn=a384aedfd25f5f081acd3eb21e98778d&chksm=fb56fa5… 10/11
2020/7/29 使用OpenCV进行颜色分割

使用OpenCV进行颜色分割
原创 小白 小白学视觉 7月4日

来自专辑

OpenCV应用

点击上方“小白学视觉”,选择加"星标"或“置顶”

重磅干货,第一时间送达

在滤波、变换、缩放等任务中,图像分割具有重要的意义。图像分割是将不同的对象划分为不同
的部分,并将这些区域以明显的颜色或者记号标记出来。图像分割是使用轮廓、边界框等概念进
行其他高级计算机视觉任务(例如对象分类和对象检测)的基础。良好的图像分割为我们后续的
图像分类以及检测奠定了基础。

在计算机视觉中主要有3种不同的图像分割类型:
1.颜色分割或阈值分割
2.语义分割
3.边缘检测
在本文里,我们将介绍基于颜色的图像分割,并通过OpenCV将其实现。小伙伴可能会问,当我
们 拥 有 像 Caffe 和 Keras 这 样 的 工 具 时 , 为 什 么 要 使 用 拥 有 21 年 历 史 的 OpenCV 库 。与Caffe和

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488684&idx=1&sn=cc7e3116b9c7172ca96b3187149b9131&chksm=fb56f640… 1/7
2020/7/29 使用OpenCV进行颜色分割

Keras等现代SOTA DL方法相比,OpenCV虽然在准确性方面有一些落后,但是运行速度相较于上
述方法具有得天独厚的优势。

跨框架进行图像分类任务的CPU性能比较

即使使用最著名的神经网络框架之一的YOLOv3进行对象检测时,其运行速度也是不尽如人意
的。此外,Darknet使用OpenMP(应用程序编程接口)进行编译的时间几乎是OpenCV的18倍。这
更加说明了使用OpenCV的速度是比较快速的。

在OpenCV和Darknet上进行YOLOv3培训时CPU性能

颜色分割可用于检测身体肿瘤、从森林或海洋背景中提取野生动物的图像,或者从单一的背景图
像中提取其他彩色物体。下面几幅图是图像分割的几个典型示例。:

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488684&idx=1&sn=cc7e3116b9c7172ca96b3187149b9131&chksm=fb56f640… 2/7
2020/7/29 使用OpenCV进行颜色分割

医学中的颜色分割

颜色分割示例

从以上示例中可以看出,尽管OpenCV是一种更快的方法,但是它对于图像的分割结果并不是非
常的理想,有时会出现分割误差或者错误分割的情况

接下来我们将介绍如何通过OpenCV对图像进行颜色的分割。这里我们有一张含有鸟的图片,我
们的目标是通过颜色分割尝试从图片中提取这只鸟。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488684&idx=1&sn=cc7e3116b9c7172ca96b3187149b9131&chksm=fb56f640… 3/7
2020/7/29 使用OpenCV进行颜色分割

含鸟的图片

首先我们导入完成该任务所需的所有库和这张图像:

1 import cv2 as cv
2 import matplotlib.pyplot as pltfrom PIL
3 import Image
4 !wget -nv https://static.independent.co.uk/s3fs-public/thumbnails/image/2018/04/10/19
5 img = Image.open('./bird.png')

接下来我们使用滤波器对该图像进行预处理,对图像进行模糊操作,以减少图像中的细微差异。
在OpenCV中提供了4个内置的滤波器,以满足用户对图像进行不同滤波的需求。这4种滤波器的
使用方式在下面的代码中给出。但是,针对于本文中需要分割的图像,我们并不需要将4种滤波
器都使用。

1 blur = cv.blur(img,(5,5))
2 blur0=cv.medianBlur(blur,5)
3 blur1= cv.GaussianBlur(blur0,(5,5),0)
4 blur2= cv.bilateralFilter(blur1,9,75,75)

下图是图像滤波模糊后的结果:

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488684&idx=1&sn=cc7e3116b9c7172ca96b3187149b9131&chksm=fb56f640… 4/7
2020/7/29 使用OpenCV进行颜色分割

模糊后的图像

如 果 小 伙 伴 对 图 像 滤 波 感 兴 趣 , 可 以 在 这 里 进 行 了 解 https://opencv-python-
tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_filtering/py_filteri
ng,这里再做过多的介绍。

接下来我们需要将图像从BGR(蓝绿色红色)转换为HSV(色相饱和度值)。为什么我们要从BGR
空间中转到HSV空间中?因为像素B,G和R的取值与落在物体上的光相关,因此这些值也彼此相
关,无法准确描述像素。相反,HSV空间中,三者相对独立,可以准确描述像素的亮度,饱和度
和色度。

1 hsv = cv.cvtColor(blur2, cv.COLOR_BGR2HSV)

这个操作看似很小,但当我们尝试找到要提取的阈值或像素范围时,它会使我们的工作变得更加
简单。

接下来是“颜色分割”的最重要一步,即“阈值分割”。这里我们将确定要提取的所有像素的阈
值。使用OpenCV进行颜色分割中最重要步骤——阈值分割,这可能是一个相当繁琐的任务。即
使我们可能想到通过使用颜色选择器工具来了解像素值,但是仍然需要进行不断的尝试,以便在
所有像素中获取期望的像素,有些时候这也可能是一项艰巨的任务。具体操作如下:

1 low_blue = np.array([55, 0, 0])


2 high_blue = np.array([118, 255, 255])
3 mask = cv.inRange(hsv, low_blue, high_blue)

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488684&idx=1&sn=cc7e3116b9c7172ca96b3187149b9131&chksm=fb56f640… 5/7
2020/7/29 使用OpenCV进行颜色分割

上面代码中最后一行的“Mask”将所有不在描述对象范围内的其他像素进行覆盖。程序运行结
果如下图所示:

Mask

接下来,运行最后的代码以显示由Mask作为边界的图像。所使用的代码和程序运行结果在下面
给出:

1 res = cv.bitwise_and(img,img, mask= mask)

从颜色分割中提取图像

那么通过上面的方式,我们就实现了基于颜色的图像分割,感兴趣的小伙伴们可以通过上面的代
码和步骤进行尝试,看看能否满足自己的图像分割需求。

交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、检测、分割、
识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),请扫描下面微信号加群,备注:”昵称+学校/公
司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研
究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488684&idx=1&sn=cc7e3116b9c7172ca96b3187149b9131&chksm=fb56f640… 6/7
2020/7/29 使用OpenCV进行颜色分割

//
//

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488684&idx=1&sn=cc7e3116b9c7172ca96b3187149b9131&chksm=fb56f640… 7/7
2020/7/29 使用OpenCV实现图像覆盖

使用OpenCV实现图像覆盖
原创 花生 小白学视觉 6月26日

来自专辑

OpenCV应用

点击上方“小白学视觉”,选择加"星标"或“置顶”

重磅干货,第一时间送达

每张图像都包括RGB三个通道,分别代表红色、绿色和蓝色,使用它们来定义图像中任意一点的像素
值,红绿蓝的值在0-255之间。

例如:一个像素值[255,0,0]代表全部为红色,像素值[255,255,0]是红色和绿色的混合,将显示为
黄色。

但是,如果使用OpenCV读取图像,它将以BGR格式生成图像,那么[255,0,0]将代表蓝色。

使用OpenCV读取一张图像

任何图像都可以通过OpenCV使用 cv2.imread()命令读取。不过,OpenCV不支持HEIC格式的图像,所以
不得不使用其它类型的库,如Pillow来读取HEIC类型的图像(或者先将它们转换为JPEG格式)

1 import cv2image = cv2.imread(‘image.jpg’)

当读取图像之后,如果有必要的话可以将其从BGR格式转换为RGB格式,通过使用cv2.cvtColor()命令
实现。

1 image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)


2 image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

覆盖

图像可以看作是是一堆像素值以类似矩阵的格式存储。任何像素的值都可以独立于其他像素进行更
改。这里有一张图像,使用OpenCV读取图像:

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488514&idx=1&sn=6d3d315b30e9c736ecf23ab8816dac19&chksm=fb56f6ee… 1/6
2020/7/29 使用OpenCV实现图像覆盖

image_1

1 image_1 = cv2.imread(‘image_1.jpg’)
2 print(image_1)

这里将给出矩阵形式的一系列像素值

1 array([[[107, 108, 106],[107, 108, 106],[107, 108, 106],…,[ 77, 78, 76],[ 77, 78, 76]

如果只改变图像某一区域的像素值,比如更改为[0,0,0],这部分区域将变成黑色,因为这是颜色为
黑色的像素值。同样,如果将像素值更改为[255,0,0],则该区域将变为蓝色(OpenCV以BGR格式读取
图像)。

1 image_1[50: 100, 50:100] = [255, 0, 0]

同样,这些像素值可以被另一幅图像替换,只需通过使用该图像的像素值。

为了做到这一点,我们需要将覆盖图像修改为要替换的像素值的大小。可以通过使用 cv2.resize()函
数来实现

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488514&idx=1&sn=6d3d315b30e9c736ecf23ab8816dac19&chksm=fb56f6ee… 2/6
2020/7/29 使用OpenCV实现图像覆盖

1 image_2 = cv2.imread(‘image_2.jpg’)
2 resized_image_2 = cv2.resize(image_2, dsize=(100, 100))

其中, dsize 代表图像要被修改的尺寸。

现在,可以将第二张图像够覆盖在第一张图片的上面

1 image_1[50:150, 50:150] = resized_image_2

覆盖PNG图像

与JPEG图像不同,PNG图像有第四个通道,它定义了给定像素的ALPHA(不透明度)。

除非另有规定,否则OpenCV以与JPEG图像相同的方式读取PNG图像。

为了读取带有Alpha值的PNG图像,我们需要在读取一张图像时指定标志 cv2.IMREAD_UNCHANGED。现
在,这个图像已经有了四个通道:BGRA

1 image_3 = cv2.imread(‘image_3.png’, cv2.IMREAD_UNCHANGED)


2 print(image_3)
3 array([[[0 0 0 0][0 0 0 0][0 0 0 0]…[0 0 0 0][0 0 0 0][0 0 0 0]]…[[0 0 0 0][0 0 0 0][

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488514&idx=1&sn=6d3d315b30e9c736ecf23ab8816dac19&chksm=fb56f6ee… 3/6
2020/7/29 使用OpenCV实现图像覆盖

然而,这个图像有4个通道,但是我们的JPEG图像只有3个通道,所以这些值不能简单地替换。

我们需要在我们的JPEG图像中添加一个虚拟通道。

为此,我们将使用 numpy。可以使用 pip install numpy命令安装它。

numpy提供了一个函数 numpy.dstack()
来根据深度叠加值。
首先,我们需要一个与图像大小相同的虚拟数组。

为了创建虚拟通道,我们可以使用 numpy.ones()函数创建一个数组。

1 import numpy as npones = np.ones((image_1.shape[0], image_1.shape[1]))*255


2 image_1 = np.dstack([image_1, ones])

我们将其数组与255相乘,因为alpha通道的值也存在于0-255之间。
现在,我们可以用PNG图像替换图像的像素值。

1 image_1[150:250, 150:250] = image_3

然而,它不会给出期望的结果,因为我们将alpha通道的值改为了零。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488514&idx=1&sn=6d3d315b30e9c736ecf23ab8816dac19&chksm=fb56f6ee… 4/6
2020/7/29 使用OpenCV实现图像覆盖

我们只需要替换那些具有非零值的像素值。为了做到这一点,我们可以通过检查每个像素值和替换非
零值来强行执行,但这很耗时。

这里有一个更好的方法。我们可以获取要覆盖图像的alpha值。

1 alpha_image_3 = image_3[:, :, 3] / 255.0

我们将像素值除以255.0,以保持值在0-1之间。

和 image_3的alpha之和需要等于255。因此,我们可以创建另一个数组,其中包含和等于255
image_1
的所需alpha值 。

1 alpha_image = 1 — alpha_image_3

现在,我们可以简单的取每个图像的alpha值和每个通道的图像像素值的元素乘积,并取它们的和。

1 for c in range(0, 3): image_1[150:250, 150:250, c] = ((alpha_image*image_1[150:250,

交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、检测、
分 割 、 识 别 、 医 学 影 像 、 GAN 、 算 法 竞 赛 等 微 信 群 ( 以 后 会 逐 渐 细 分 ) , 请 扫 描 下 面 微 信 号 加 群 , 备
注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通
过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488514&idx=1&sn=6d3d315b30e9c736ecf23ab8816dac19&chksm=fb56f6ee… 5/6
2020/7/29 使用OpenCV实现图像覆盖

//
//

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488514&idx=1&sn=6d3d315b30e9c736ecf23ab8816dac19&chksm=fb56f6ee… 6/6
2020/9/30 使用OpenCV实现图像增强

使用OpenCV实现图像增强
原创 努比 小白学视觉 8月6日

点击上方“小白学视觉”,选择加"星标"或“置顶”
重磅干货,第一时间送达

本期将介绍如何通过图像处理从低分辨率/模糊/低对比度的图像中提取有用信息。

下面让我们一起来探究这个过程:

首先我们获取了一个LPG气瓶图像,该图像取自在传送带上运行的仓库。我们的目标
是找出LPG气瓶的批号,以便更新已检测的LPG气瓶数量。

步骤1:导入必要的库

1 import cv2
2 import numpy as np
3 import matplotlib.pyplot as plt

步骤2:加载图像并显示示例图像。

1 img= cv2.imread('cylinder1.png')

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490015&idx=1&sn=518e97c9de7c898b6d2595fc61c4dc65&chksm=fb56fb3… 1/11
2020/9/30 使用OpenCV实现图像增强

2 img1=cv2.imread('cylinder.png')
3 images=np.concatenate(img(img,img1),axis=1)
4 cv2.imshow("Images",images)
5 cv2.waitKey(0)
6 cv2.destroyAllWindows()

LPG气瓶图片(a)批次-D26(b)批次C27

该图像的对比度非常差。我们几乎看不到批号。这是在灯光条件不足的仓库中的常见
问题。接下来我们将讨论对比度受限的自适应直方图均衡化,并尝试对数据集使用不
同的算法进行实验。

步骤3:将图像转换为灰度图像

1 gray_img=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
2 gray_img1=cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY)

步骤4:找到灰度图像的直方图后,寻找强度的分布。

1 hist=cv2.calcHist(gray_img,[0],None,[256],[0,256])
2 hist1=cv2.calcHist(gray_img1,[0],None,[256],[0,256])
3 plt.subplot(121)
4 plt.title("Image1")
5 plt.xlabel('bins')
6 plt.ylabel("No of pixels")
https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490015&idx=1&sn=518e97c9de7c898b6d2595fc61c4dc65&chksm=fb56fb3… 2/11
2020/9/30 使用OpenCV实现图像增强

7 plt.plot(hist)
8 plt.subplot(122)
9 plt.title("Image2")
10 plt.xlabel('bins')
11 plt.ylabel("No of pixels")
12 plt.plot(hist1)
13 plt.show()

步骤5:现在,使用cv2.equalizeHist()函数来均衡给定灰度图像的对比度。
cv2.equalizeHist()函数可标准化亮度并增加对比度。

1 gray_img_eqhist=cv2.equalizeHist(gray_img)
2 gray_img1_eqhist=cv2.equalizeHist(gray_img1)
3 hist=cv2.calcHist(gray_img_eqhist,[0],None,[256],[0,256])
4 hist1=cv2.calcHist(gray_img1_eqhist,[0],None,[256],[0,256])
5 plt.subplot(121)
6 plt.plot(hist)
7 plt.subplot(122)
8 plt.plot(hist1)
9 plt.show()

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490015&idx=1&sn=518e97c9de7c898b6d2595fc61c4dc65&chksm=fb56fb3… 3/11
2020/9/30 使用OpenCV实现图像增强

步骤6:显示灰度直方图均衡图像

1 eqhist_images=np.concatenate((gray_img_eqhist,gray_img1_eqhist),axis=1)
2 cv2.imshow("Images",eqhist_images)
3 cv2.waitKey(0)
4 cv2.destroyAllWindows()

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490015&idx=1&sn=518e97c9de7c898b6d2595fc61c4dc65&chksm=fb56fb3… 4/11
2020/9/30 使用OpenCV实现图像增强

灰度直方图均衡

让我们进一步深入了解CLAHE

步骤7:

对比度有限的自适应直方图均衡

该算法可以用于改善图像的对比度。该算法通过创建图像的多个直方图来工作,并使
用所有这些直方图重新分配图像的亮度。CLAHE可以应用于灰度图像和彩色图像。有
2个参数需要调整。

1. 限幅设置了对比度限制的阈值。默认值为40

2. tileGridsize设置行和列中标题的数量。在应用CLAHE时,为了执行计算,图像
被分为称为图块(8 * 8)的小块。

1 clahe=cv2.createCLAHE(clipLimit=40)
2 gray_img_clahe=clahe.apply(gray_img_eqhist)
3 gray_img1_clahe=clahe.apply(gray_img1_eqhist)
4 images=np.concatenate((gray_img_clahe,gray_img1_clahe),axis=1)
5 cv2.imshow("Images",images)
6 cv2.waitKey(0)
7 cv2.destroyAllWindows()

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490015&idx=1&sn=518e97c9de7c898b6d2595fc61c4dc65&chksm=fb56fb3… 5/11
2020/9/30 使用OpenCV实现图像增强

步骤8:

门槛技术

阈值处理是一种将图像划分为前景和背景的简单但有效的方法。如果像素强度小于某
个预定义常数(阈值),则最简单的阈值化方法将源图像中的每个像素替换为黑色像
素;如果像素强度大于阈值,则使用白色像素替换源像素。阈值的不同类型是:
cv2.THRESH_BINARY
cv2.THRESH_BINARY_INV
cv2.THRESH_TRUNC
cv2.THRESH_TOZERO
cv2.THRESH_TOZERO_INV
cv2.THRESH_OTSU
cv2.THRESH_TRIANGLE

尝试更改阈值和max_val以获得不同的结果。

1 th=80
2 max_val=255
3 ret, o1 = cv2.threshold(gray_img_clahe, th, max_val, cv2.THRESH_BINARY
4 cv2.putText(o1,"Thresh_Binary",(40,100),cv2.FONT_HERSHEY_SIMPLEX,2,(25
5 ret, o2 = cv2.threshold(gray_img_clahe, th, max_val, cv2.THRESH_BINARY_
6 cv2.putText(o2,"Thresh_Binary_inv",(40,100),cv2.FONT_HERSHEY_SIMPLEX,2
7 ret, o3 = cv2.threshold(gray_img_clahe, th, max_val, cv2.THRESH_TOZERO
8 cv2.putText(o3,"Thresh_Tozero",(40,100),cv2.FONT_HERSHEY_SIMPLEX,2,(25

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490015&idx=1&sn=518e97c9de7c898b6d2595fc61c4dc65&chksm=fb56fb3… 6/11
2020/9/30 使用OpenCV实现图像增强

9 ret, o4 = cv2.threshold(gray_img_clahe, th, max_val, cv2.THRESH_TOZERO_


10 cv2.putText(o4,"Thresh_Tozero_inv",(40,100),cv2.FONT_HERSHEY_SIMPLEX,2
11 ret, o5 = cv2.threshold(gray_img_clahe, th, max_val, cv2.THRESH_TRUNC)
12 cv2.putText(o5,"Thresh_trunc",(40,100),cv2.FONT_HERSHEY_SIMPLEX,2,(255
13 ret ,o6= cv2.threshold(gray_img_clahe, th, max_val, cv2.THRESH_OTSU)
14 cv2.putText(o6,"Thresh_OSTU",(40,100),cv2.FONT_HERSHEY_SIMPLEX,2,(255,
15
16 final=np.concatenate((o1,o2,o3),axis=1)
17 final1=np.concatenate((o4,o5,o6),axis=1)
18
19 cv2.imwrite("Image1.jpg",final)
20 cv2.imwrite("Image2.jpg",final1)

Thresh_Binary_inv,Thresh_Binary_inv,Thresh_Tozero

Thresh_Tozero_inv,Thresh_trunc,Thresh_OSTU

步骤9:自适应阈值

在上一节中,我们使用了全局阈值来应用cv2.threshold()。如我们所见,由于图像
不同区域的照明条件不同,因此获得的结果不是很好。在这些情况下,您可以尝试自
https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490015&idx=1&sn=518e97c9de7c898b6d2595fc61c4dc65&chksm=fb56fb3… 7/11
2020/9/30 使用OpenCV实现图像增强

适应阈值化。在OpenCV中,自适应阈值处理由cv2.adapativeThreshold()函数
执行

此功能将自适应阈值应用于src阵列(8位单通道图像)。maxValue参数设置dst图像
中满足条件的像素的值。adaptiveMethod参数设置要使用的自适应阈值算法。

cv2.ADAPTIVE_THRESH_MEAN_C:将T(x,y)阈值计算为(x,y)的
blockSize x blockSize邻域的平均值减去C参数。
cv2.ADAPTIVE_THRESH_GAUSSIAN_C:将T(x,y)阈值计算为(x,y)的
blockSize x blockSize邻域的加权总和减去C参数。

blockSize参数设置用于计算像素阈值的邻域的大小,它可以取值3、5、7等。

C参数只是从均值或加权均值中减去的常数(取决于adaptiveMethod参数设置的自适
应方法)。通常,此值为正,但可以为零或负。

1 gray_image = cv2.imread('cylinder1.png',0)
2 gray_image1 = cv2.imread('cylinder.png',0)
3 thresh1 = cv2.adaptiveThreshold(gray_image, 255, cv2.ADAPTIVE_THRESH_M
4 thresh2 = cv2.adaptiveThreshold(gray_image, 255, cv2.ADAPTIVE_THRESH_M
5 thresh3 = cv2.adaptiveThreshold(gray_image, 255, cv2.ADAPTIVE_THRESH_GA
6 thresh4 = cv2.adaptiveThreshold(gray_image, 255, cv2.ADAPTIVE_THRESH_GA
7 thresh11 = cv2.adaptiveThreshold(gray_image1, 255, cv2.ADAPTIVE_THRESH_
8 thresh21 = cv2.adaptiveThreshold(gray_image1, 255, cv2.ADAPTIVE_THRESH_
9 thresh31 = cv2.adaptiveThreshold(gray_image1, 255, cv2.ADAPTIVE_THRESH_
10 thresh41 = cv2.adaptiveThreshold(gray_image1, 255, cv2.ADAPTIVE_THRESH_
11
12 final=np.concatenate((thresh1,thresh2,thresh3,thresh4),axis=1)
13 final1=np.concatenate((thresh11,thresh21,thresh31,thresh41),axis=1)
14 cv2.imwrite('rect.jpg',final)
15 cv2.imwrite('rect1.jpg',final1)

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490015&idx=1&sn=518e97c9de7c898b6d2595fc61c4dc65&chksm=fb56fb3… 8/11
2020/9/30 使用OpenCV实现图像增强

自适应阈值

自适应阈值

步骤10:OTSU二值化
Otsu的二值化算法,在处理双峰图像时是一种很好的方法。双峰图像可以通过其包含
两个峰的直方图来表征。Otsu的算法通过最大化两类像素之间的方差来自动计算将两
个峰分开的最佳阈值。等效地,最佳阈值使组内差异最小化。Otsu的二值化算法是一
种统计方法,因为它依赖于从直方图得出的统计信息(例如,均值,方差或熵)

1 gray_image = cv2.imread('cylinder1.png',0)
2 gray_image1 = cv2.imread('cylinder.png',0)
3 ret,thresh1 = cv2.threshold(gray_image,0, 255, cv2.THRESH_BINARY+cv2.TH
4 ret,thresh2 = cv2.threshold(gray_image1,0, 255, cv2.THRESH_BINARY+cv2.T
5
6 cv2.imwrite('rect.jpeg',np.concatenate((thresh1,thresh2),axis=1))

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490015&idx=1&sn=518e97c9de7c898b6d2595fc61c4dc65&chksm=fb56fb3… 9/11
2020/9/30 使用OpenCV实现图像增强

OTSU二值化

现在,我们已经从低对比度的图像中清楚地识别出批号。

交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计
算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),
请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交
大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入
相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490015&idx=1&sn=518e97c9de7c898b6d2595fc61c4dc65&chksm=fb56fb… 10/11
2020/9/30 使用OpenCV自动去除背景色

使用OpenCV自动去除背景色
原创 小白 小白学视觉 8月31日

点击上方“小白学视觉”,选择加"星标"或“置顶”
重磅干货,第一时间送达

几天前,我遇到了一个项目,要求将草图放到某个文件夹中时删除草图的白色背景。
这都是在硬件扫描仪中发生的。

下面是一个草图示例:

采集

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490629&idx=1&sn=2ce1924c6463f4a39c6271926df10d79&chksm=fb56fea9… 1/7
2020/9/30 使用OpenCV自动去除背景色

第一步是安装此项目的依赖关系,具体需要内容我们将在下面列出。此外,我们还将
使用Python 3.7。

1 opencv_python==4.1.0.25
2 pip install opencv-python
3 numpy==1.16.4
4 pip install numpy

之后,我们将导入项目所需的所有模块
https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490629&idx=1&sn=2ce1924c6463f4a39c6271926df10d79&chksm=fb56fea9… 2/7
2020/9/30 使用OpenCV自动去除背景色

1 import cv2
2 import os
3 import string
4 import random
5 from os import listdir
6 from os.path import isfile, join, splitext
7 import time
8 import sys
9 import numpy as np
10 import argparse

然后,我们创建三个不同的变量:要处理的文件夹的名称,图像在处理后存储的
文件夹的名称,以及在监视文件夹时的轮询时间(即,它检查文件夹中更改的频
率,在我们这里设置的是一秒钟)

1 watch_folder = ‘toprocess’
2 processed_folder = ‘processed’
3 poll_time = 1

文件夹“ toprocess”和“ processed”放置在和我们的python脚本的同一目录中。

然后,我们将介绍我们程序主要功能的代码,它将监视我们的“ toprocess”目
录,如果没有发生任何更改,程序将处理存入在该文件夹的所有图像。

1 before = dict([(f, None) for f in os.listdir(watch_folder)])


2 while 1:
3 time.sleep(poll_time)
4 after = dict([(f, None) for f in os.listdir(watch_folder)])
5 added = [f for f in after if not f in before]
6 removed = [f for f in before if not f in after]
7 if added:
8 print(“Added “, “, “.join(added))
9 if added[0] is not None:

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490629&idx=1&sn=2ce1924c6463f4a39c6271926df10d79&chksm=fb56fea9… 3/7
2020/9/30 使用OpenCV自动去除背景色

10 processImage(added[0])
11 if removed:
12 print(“Removed “, “, “.join(removed))
13 before = after

这段代码将无限循环运行,直到脚本被杀死为止。启动后,它将文件存储在名为“
before”的词典目录中。接下来,下面将分解介绍无限循环中的步骤:
睡眠指定的poll_time(1秒)。
将文件信息存储在名为after的字典目录中。
通过比较之后的IN和之前的NOT来存储已添加的内容
检查最后添加的元素(added [0])(如果存在),然后调用一个函数,我
们将在文件上稍作介绍的processImage进行讨论。
如果已删除,请通过打印一些信息来让用户知道。
最后,将目录中的最新文件进行更新。

接下来介绍processImage函数,这是程序的核心。这就是OpenCV后台删除魔
术发生的地方。下面的注释解释了该代码(需要基本的OpenCV知识):

1 def processImage(fileName):
2 # Load in the image using the typical imread function using our watch
3 image = cv2.imread(watch_folder + ‘/’ + fileName)
4 output = image
5 # Set thresholds. Here, we are using the Hue, Saturation, Value colo
6 THESE VALUES CAN BE PLAYED AROUND FOR DIFFERENT COLORS
7 hMin = 29 # Hue minimum
8 sMin = 30 # Saturation minimum
9 vMin = 0 # Value minimum (Also referred to as brightness)
10 hMax = 179 # Hue maximum
11 sMax = 255 # Saturation maximum
12 vMax = 255 # Value maximum
13 # Set the minimum and max HSV values to display in the output image
14 lower = np.array([hMin, sMin, vMin])
15 upper = np.array([hMax, sMax, vMax])
16 # Create HSV Image and threshold it into the proper range.
17 hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) # Converting color spac
18 mask = cv2.inRange(hsv, lower, upper) # Create a mask based on the lo
19 # Create the output image, using the mask created above. This will p
20 output = cv2.bitwise_and(image, image, mask=mask)

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490629&idx=1&sn=2ce1924c6463f4a39c6271926df10d79&chksm=fb56fea9… 4/7
2020/9/30 使用OpenCV自动去除背景色

21 # Add an alpha channel, and update the output image variable


22 *_, alpha = cv2.split(output)
23 dst = cv2.merge((output, alpha))
24 output = dst
25 # Resize the image to 512, 512 (This can be put into a variable for m
26 dim = (512, 512)
27 output = cv2.resize(output, dim)
28 # Generate a random file name using a mini helper function called ran
29 file_name = randomString(5) + ‘.png’
30 cv2.imwrite(processed_folder + ‘/’ + file_name, output)

接下来是一个非常简单的功能,可以正确地完成工作。再次强调,使用阈值可以
提供更好的结果。我们需要讨论的最后一件事是mini helper函数,该函数为文件
名生成随机字符串。

1 def randomString(length):
2 letters = string.ascii_lowercase
3 return ‘’.join(random.choice(letters) for i in range(length))

这是一个简单的功能。它使用“string”库获取字母,然后根据我们传入的长度加
入随机选择的字符。传入5的长度将生成5个字符的字符串。

整个程序的处理结果如下所示:

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490629&idx=1&sn=2ce1924c6463f4a39c6271926df10d79&chksm=fb56fea9… 5/7
2020/9/30 使用OpenCV自动去除背景色

交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计
算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),
请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交
大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入
相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490629&idx=1&sn=2ce1924c6463f4a39c6271926df10d79&chksm=fb56fea9… 6/7
2020/7/29 使用网络摄像头和Python中的OpenCV构建运动检测器(Translate)

使用网络摄像头和Python中的OpenCV构建运动检测器(Translate)
原创 小林 小白学视觉 7月14日

来自专辑

OpenCV应用

点击上方“小白学视觉”,选择加"星标"或“置顶”

重磅干货,第一时间送达

本期我们将学习如何使用OpenCV实现运动检测

运动检测是指检测物体相对于周围环境的位置是否发生了变化。接下来,让我们一起使用Python实现
一个运动检测器应用程序吧!

该运动检测器可以完成以下任务:
1)在家工作时在屏幕前查找时间
2) 监控孩子在屏幕前的时间
3) 在你的后院发现非法侵入
4) 在你的房间/房子/小巷周围找到不需要的公共/动物活动……。

想要实现该运动检测器程序我们需要具备以下条件:
1)硬件要求:装有网络摄像机或任何类型摄像机的计算机。
2)软件需求:Pyhton3或者更高版本。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489004&idx=1&sn=d6000de5b1fcf5cf092a860953833c22&chksm=fb56f700c… 1/9
2020/7/29 使用网络摄像头和Python中的OpenCV构建运动检测器(Translate)

3)附加要求:对运动检测有一定的兴趣。

接下来我们将一步步的完成该应用程序的构建。

首先,我们将通过网络摄像头捕获第一帧,并将它视为基准帧,如下图所示。通过计算该基准帧中的
对象与新帧对象之间的相位差来检测运动。我们也将得到的结果称为Delta帧。

接下来,我们将使用像素强度来优化Delta帧,优化后的帧称为阈值帧。并且,我们将应用一些复杂的
图像处理技术,例如阴影消除、扩张轮廓等,以完成在阈值帧上提取对象物体。以下是您要实现的目
标:
被探测对象

当这个对象进入帧和退出帧时,我们能够很容易的捕获这两帧的时间戳。因此,将能够准确的在视频
中找到相关片段。

我们希望小伙伴都能自己实现这个程序,因此我们就不直接嵌入代码了。

从最基本的安装开始,我们需要安装Python3或更高版本,并使用pip安装pandas和OpenCV这两个库。
这些工作做好,我们的准备工作就完成了。

第一步:导入需要的库:

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489004&idx=1&sn=d6000de5b1fcf5cf092a860953833c22&chksm=fb56f700c… 2/9
2020/7/29 使用网络摄像头和Python中的OpenCV构建运动检测器(Translate)

第二步:初始化变量,列表,data frame:

在下面的代码中,我们将会了解到在什么时候需要使用上面涉及到的每一项。

第三步:使用网络摄像机捕获视频帧:

在OpenCV中有能够打开相机并捕获视频帧的内置函数。其中输入参数“0”表示计算机硬件端口号为0的
摄像机。如果我们拥有了多个摄像头或闭路电视等设置,可以通过该参数提供相应的端口号。

第四步:将捕捉到的帧转换为灰度图像,并应用高斯模糊去除噪声:

由于彩色图片中每个像素均具有三个颜色通道,实际上我们并不需要使用这么多的信息,因此首先将
彩色帧转换成灰度帧。再利用高斯模糊对图像进行平滑处理,进而提高检测精度。在高斯模糊函数
中,我们利用第2个参数定义了高斯核的宽度和高度;利用第3个参数,定义了标准偏差值。在这里我
们可以使用核大小为(21,21),标准偏差为0的标准值。想要了解有关高斯平滑的更多信息,请参
考:
Smoothing Images - OpenCV 2.4.13.7 documentation In an analogous way as the Gaussian filter, the
bilateral filter also considers the neighboring pixels with weights…
docs.opencv.org

第五步:捕获第一个灰度帧

第一帧是整个处理过程中的基准帧。通过计算此基准帧与新帧之间特定对象的相位差来检测运动。在
拍摄第一帧时,特定对象相机前不应有任何移动。但是得到的第一帧并不需要后续处理,因此我们可
以用continue语句跳过后续过程。

第六步:创建Delta帧和阈值帧

现在,我们需要找出第一帧和当前帧之间的区别。因此,我们使用absdiff函数并将得到的结果称为
delta帧。对于我们的用例来说,仅仅找到一个差异是不够的,所以我们需要定义一个像素阈值,它可
以被视为真实的对象。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489004&idx=1&sn=d6000de5b1fcf5cf092a860953833c22&chksm=fb56f700c… 3/9
2020/7/29 使用网络摄像头和Python中的OpenCV构建运动检测器(Translate)

我们可以选择30像素作为标准阈值,并将标准阈值的颜色定义为白色(颜色代码:255). 二元阈值函数
THRESH_BINARY返回一个元组值,其中只有第二项([0]是第一项,[1]是第二项)包含生成的阈值帧。
二元阈值函数用于处理含有2个离散值的非连续函数:如0或1。如果摄影机前面没有对象,我们将当
前帧的状态视为0;如果摄影机前面存在对象,则将当前帧的状态视为1。

更多阈值图像处理相关知识,请参考:
Miscellaneous Image Transformations - OpenCV 2.4.13.7 documentation Performs a marker-based image
segmentation using the watershed algorithm. The function implements one of the variants…
docs.opencv.org

第七步:膨胀阈值帧并在其中找到轮廓像素

“我们的眼睛总是被光线吸引,但阴影处有更多内容。”—格雷戈里·马奎尔

对象的每个部分都会在背景或自身的其他部分留下一定的阴影。这似乎总是让我们感到很困惑。例
如,鼻子投射在嘴唇上的阴影,较大的静止物体在旁边的小物体上投射的阴影。飘动的光源,不同发
光强度的多个光源,你房间的窗帘,光源的方向和视角等等都会对阴影造成一定的影响。

以下是在实时捕获的帧中发现的一些干扰。因此,为了使这些噪声最小化,我们需要对图像进行滤
波。在膨胀函数Dilate中,我们可以通过设置迭代次数来设置平滑度。迭代次数越多,平滑度越高,处
理时间也就越长。因此,建议保持标准化设置为3。膨胀函数中的“None”参数表示我们的应用中不需
要元素结构。

关于膨胀的更多知识,你可以参考:
Image Filtering - OpenCV 2.4.13.7 documentation Functions and classes described in this section are
used to perform various linear or non-linear filtering operations…
docs.opencv.org

完成过滤以后,我们需要在该帧中找到对象轮廓。我们用当前帧中的轮廓来识别对象的大小和位置。
为了实现这一点,我们将该帧的一个副本传递到findCounters方法中,使用这个副本来查找轮廓。使用
副本的原因是,我们不希望轮廓识别影响到原始过滤帧。

这里有个麻烦,因为我们必须将轮廓存储在一个元组中,并且只需要使用该元组的第一个值。请参阅
Python3中声明元组的语法:(name,_)。

现在,我们只需要在过滤层上找到对象的外部轮廓。对于我们的用例来说,除了极端外部轮廓以外的
其他轮廓都是无用的。因此我们必须使用一些近似方法来优化轮廓的提取过程。例如使用曲线近似或

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489004&idx=1&sn=d6000de5b1fcf5cf092a860953833c22&chksm=fb56f700c… 4/9
2020/7/29 使用网络摄像头和Python中的OpenCV构建运动检测器(Translate)

曲线插值,也可以使用简单链近似规则,即压缩水平、垂直和对角线线段,只保留其端点。因此,我
们能够很快得到最佳拟合轮廓。

第八步:找到轮廓区域,并在矩形中形成端点:

实际上我们并不想捕捉像昆虫这样的小物体,而是要捕捉像人或动物这样的大物体。因此我们采用轮
廓区域的概念,即跳过那些面积小于10000像素的对象。对于大于此区域的轮廓,我们将状态设置为
1,即检测到对象。

想知道关于图像处理中的轮廓,可以参考:
Structural Analysis and Shape Descriptors - OpenCV 2.4.13.7 documentation Draws contours outlines or
filled contours. The function draws contour outlines in the image if or fills the area…
docs.opencv.org

现在我们使用boundingRect函数捕捉轮廓的坐标。然后,我们使用这些坐标在彩色帧上绘制一个特定
颜色、特定厚度的矩形。此矩形描述了实际检测到的对象。

第九步:捕获对象进入帧(场景)和退出帧(场景)时的时间戳

“状态”列表status_list存储值0:代表未检测到对象,1:代表检测到对象。此状态值从0更改为1的时刻
就是对象进入帧的那一时刻。同样,此状态值从1变为0的时刻就是对象从帧中消失的那一时刻。因
此,我们从状态列表的最后两个值可以获得这两个切换事件的时间戳。

第十步:显示所有不同的画面(帧)

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489004&idx=1&sn=d6000de5b1fcf5cf092a860953833c22&chksm=fb56f700c… 5/9
2020/7/29 使用网络摄像头和Python中的OpenCV构建运动检测器(Translate)

使用imshow()方法,我们将在一个独立的窗口中显示每个帧并进行比较。

我们使用waitKey函数来延迟进程,直到按下某个键。在这里,我们使用waitKey(1)从摄像机获得连
续的实时反馈。想停止拍摄视频时,只需按键盘上的“Q”键即可。

我们同时需要在按下“Q”的同时捕获最后一个时间戳,因为这将帮助程序结束从摄像机捕获视频的过
程,并生成时间数据。

下面是使用该应用程序生成的实际图像输出。第一个图像表示基准帧的4个帧类型,第二个图像表示带
有对象的帧的4种类型的帧。你能比较一下区别吗?

采集

Baseline First Frame

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489004&idx=1&sn=d6000de5b1fcf5cf092a860953833c22&chksm=fb56f700c… 6/9
2020/7/29 使用网络摄像头和Python中的OpenCV构建运动检测器(Translate)

Frame with a detected object

第十一步:生成时间数据

到目前为止,所有的时间戳都存储在pandas的data-frame变量中。为了从生成的数据中获得更多信
息,我们将把data-frame变量导出到本地磁盘的csv文件中。

请不要忘记释放视频变量,因为它在内存中占用了不少空间。同时销毁所有窗口以避免出现不必要的
错误

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489004&idx=1&sn=d6000de5b1fcf5cf092a860953833c22&chksm=fb56f700c… 7/9
2020/7/29 使用网络摄像头和Python中的OpenCV构建运动检测器(Translate)

这就是生成的csv的样子。正如我们所看到的那样,在程序结束之前,这个对象已经被检测了3次。您
可以查看开始时间和结束时间,并计算对象在摄影机前面的时间。

这个应用程序还不够令人兴奋吗?这个应用程序是不是远离了典型的无聊编程?物联网爱好者甚至可
以把这个程序部署到树莓派服务器Raspberry Pi上,并创造奇迹!

如果本文对小伙伴有帮助,希望可以在文末来个“一键三连”。

交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、检测、
分 割 、 识 别 、 医 学 影 像 、 GAN 、 算 法 竞 赛 等 微 信 群 ( 以 后 会 逐 渐 细 分 ) , 请 扫 描 下 面 微 信 号 加 群 , 备
注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通
过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489004&idx=1&sn=d6000de5b1fcf5cf092a860953833c22&chksm=fb56f700c… 8/9
第二部分

含有源码项目
(35 个项目)
2020/7/29 快速指南:使用OpenCV预处理神经网络中的面部图像的

快速指南:使用OpenCV预处理神经网络中的面部图像的
原创 努比 小白学视觉 3天前

来自专辑

OpenCV应用

点击上方“小白学视觉”,选择加"星标"或“置顶”

重磅干货,第一时间送达

本期将介绍脸部检测、眼睛检测;图像拉直、裁剪、调整大小、归一化等内容

目前,涉及面部分类的计算机视觉问题,通常都需要使用深度学习。因此在将图像输入神经网络之
前,需要经过一个预处理阶段,以便达到更好的分类效果。

图像预处理通常来说非常简单,只需执行几个简单的步骤即可轻松完成。但为了提高模型的准确性,
这也是一项非常重要的任务。对于这些问题,我们可以使用OpenCV完成:一个针对(实时)计算机
视觉应用程序的高度优化的开源库,包括C ++,Java和Python语言。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489691&idx=1&sn=ed98af224018e09ecaae1419b1a03307&chksm=fb56fa7… 1/13
2020/7/29 快速指南:使用OpenCV预处理神经网络中的面部图像的

接下来我们将一起探索可能会应用在每个面部分类或识别问题上应用的基本原理,示例和代码。

注意:下面使用的所有图像均来自memes.。

图片载入

我们使用该imread()函数加载图像,并指定文件路径和图像模式。第二个参数对于运行基本通道和
深度转换很重要。
img = cv2.imread('path/image.jpg', cv2.IMREAD_COLOR)

要查看图像可以使用imshow()功能:
cv2.imshow(img)

如果使用的type(img)话,将显示该图像的尺寸包括 高度 、 重量 、 通道数 。

彩色图像有3个通道:蓝色,绿色和红色(在OpenCV中按此顺序)。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489691&idx=1&sn=ed98af224018e09ecaae1419b1a03307&chksm=fb56fa7… 2/13
2020/7/29 快速指南:使用OpenCV预处理神经网络中的面部图像的

我们可以很轻松查看单个通道:
# Example for green channel
img[:, :, 0]; img[:, :, 2]; cv2.imshow(img)

Grayscale version

灰度图像

为了避免在人脸图像分类过程中存在的干扰,通常选择黑白图像(当然也可以使用彩图!请小伙伴们
自行尝试两者并比较结果)。要获得灰度图像,我们只需要在图像加载函数中通过将适当的值作为第
二个参数传递来指定它:
img = cv2.imread('path/image.jpg', cv2.IMREAD_GRAYSCALE)

现在,我们的图像只有一个灰度通道了!

面部和眼睛检测

在处理人脸分类问题时,我们可能需要先对图形进行裁剪和拉直,再进行人脸检测以验证是否有人脸
的存在。为此,我们将使用OpenCV中自带的的基于Haar特征的级联分类器进行对象检测。

首先,我们选择用于面部和眼睛检测的预训练分类器。以下时可用的XML文件列表:

1)对于面部检测,OpenCV提供了这些(从最松的先验到最严格的先验):

• haarcascade_frontalface_default.xml

• haarcascade_frontalface_alt.xml

• haarcascade_frontalface_alt2.xml
https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489691&idx=1&sn=ed98af224018e09ecaae1419b1a03307&chksm=fb56fa7… 3/13
2020/7/29 快速指南:使用OpenCV预处理神经网络中的面部图像的

• haarcascade_frontalface_alt_tree.xml

2)对于眼睛检测,我们可以选择以下两种:

• haarcascade_eye.xml

• haarcascade_eye_tree_eyeglasses.xml (正在尝试处理眼镜!)

我们以这种方式加载预训练的分类器:
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades +
‘haarcascade_frontalface_default.xml’)eyes_cascade =
cv2.CascadeClassifier(cv2.data.haarcascades + ‘haarcascade_eye.xml’)

我们可以测试几种组合,但我们要记住一点,没有一种分类器在所有情况下都是最好的(如果第一个
分类失败,您可以尝试第二个分类,甚至尝试所有分类)。

对于人脸检测,我们可使用以下代码:
faces_detected = face_cascade.detectMultiScale(img, scaleFactor=1.1,
minNeighbors=5)

结果是一个数组,其中包含所有检测到的脸部特征的矩形位置。我们可以很容易地绘制它:
(x, y, w, h) = faces_detected[0]
cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 1);
cv2.imshow(img)

对于眼睛,我们以类似的方式进行,但将搜索范围缩小到刚刚提取出来的面部矩形框内:

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489691&idx=1&sn=ed98af224018e09ecaae1419b1a03307&chksm=fb56fa7… 4/13
2020/7/29 快速指南:使用OpenCV预处理神经网络中的面部图像的

eyes = eyes_cascade.detectMultiScale(img[y:y+h, x:x+w])for (ex, ey, ew,


eh) in eyes:
cv2.rectangle(img, (x+ex, y+ey), (x+ex+ew, y+ey+eh),
(255, 255, 255), 1)

尽管这是预期的结果,但是很多时候再提取的过程中我们会遇到一些难以解决的问题。比如我们没有
正面清晰的人脸视图。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489691&idx=1&sn=ed98af224018e09ecaae1419b1a03307&chksm=fb56fa7… 5/13
2020/7/29 快速指南:使用OpenCV预处理神经网络中的面部图像的

不能正确检测的案例

脸部旋转

通过计算两只眼睛之间的角度,我们就可以拉直面部图像(这很容易)。计算之后,我们仅需两个步
骤即可旋转图像:
rows, cols = img.shape[:2]
M = cv2.getRotationMatrix2D((cols/2, rows/2), <angle>, 1)
img_rotated = cv2.warpAffine(face_orig, M, (cols,rows))

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489691&idx=1&sn=ed98af224018e09ecaae1419b1a03307&chksm=fb56fa7… 6/13
2020/7/29 快速指南:使用OpenCV预处理神经网络中的面部图像的

裁脸

为了帮助我们的神经网络完成面部分类任务,最好去除外界无关信息,例如背景,衣服或配件。在这
些情况下,面部裁切非常方便。

我们需要做的第一件事是再次从旋转后的图像中获取面部矩形。然后我们需要做出决定:我们可以 按
原样裁剪矩形区域,也可以添加额外的填充,以便在周围获得更多空间。这取决于要解决的具体问题
(按年龄,性别,种族等分类);也许我们需要保留头发,也许不需要。

最后进行裁剪(p用于 填充 ):
cv2.imwrite('crop.jpg', img_rotated[y-p+1:y+h+p, x-p+1:x+w+p])

现在这张脸的图像是非常单一的,基本可用于深度学习:

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489691&idx=1&sn=ed98af224018e09ecaae1419b1a03307&chksm=fb56fa7… 7/13
2020/7/29 快速指南:使用OpenCV预处理神经网络中的面部图像的

图像调整大小

神经网络需要的所有输入图像具有相同的形状和大小,因为GPU应用相同的指令处理一批相同大小图
像,可以达到较快的速度。我们虽然可以随时调整它们的大小,但这并不是一个好主意,因为需要在
训练期间将对每个文件执行几次转换。因此,如果我们的数据集包含大量图像,我们应该考虑在训练
阶段之前实施批量调整大小的过程。

在OpenCV中,我们可以与同时执行缩小和升频resize(),有几个插值方法可用。指定最终大小的
示例:
cv2.resize(img, (<width>, <height>), interpolation=cv2.INTER_LINEAR)

要缩小图像,OpenCV建议使用INTER_AREA插值法,而放大图像时,可以使用INTER_CUBIC(慢
速)或INTER_LINEAR(更快,但效果仍然不错)。最后,这是质量和时间之间的权衡。

我对升级进行了快速比较:

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489691&idx=1&sn=ed98af224018e09ecaae1419b1a03307&chksm=fb56fa7… 8/13
2020/7/29 快速指南:使用OpenCV预处理神经网络中的面部图像的

前两个图像似乎质量更高(但是您可以观察到一些压缩伪像)。线性方法的结果显然更平滑(没有对
比度)并且噪点更少(黑白图像证明)。最后一个像素化。

归一化

我们可以使用normalize()功能使视觉图像标准化,以修复非常暗/亮的图像(甚至可以修复低对比
度)。该归一化类型是在函数参数指定:
norm_img = np.zeros((300, 300))
norm_img = cv2.normalize(img, norm_img, 0, 255, cv2.NORM_MINMAX)

例子:

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489691&idx=1&sn=ed98af224018e09ecaae1419b1a03307&chksm=fb56fa7… 9/13
2020/7/29 快速指南:使用OpenCV预处理神经网络中的面部图像的

当使用图像作为深度卷积神经网络的输入时,无需应用这种归一化(上面的结果对我们来说似乎不
错,但是并不针对 他们的 眼睛)。在实践中,我们将对每个通道进行适当的归一化,例如减去均值并
除以像素级别的标准差(这样我们得到均值0和偏差1)。如果我们使用转移学习,最好的方法总是使
用预先训练的模型统计信息。

结论

当我们处理面部分类/识别问题时,如果输入的图像不是护照照片时,检测和分离面部是一项常见的任
务。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489691&idx=1&sn=ed98af224018e09ecaae1419b1a03307&chksm=fb56fa… 10/13
2020/7/29 快速指南:使用OpenCV预处理神经网络中的面部图像的

OpenCV是一个很好的图像预处理任务库,不仅限于此。对于许多计算机视觉应用来说,它也是一个
很好的工具……

https://www.youtube.com/watch?v=GebcshN4OdE

https://www.youtube.com/watch?v=z1Cvn3_4yGo

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489691&idx=1&sn=ed98af224018e09ecaae1419b1a03307&chksm=fb56fa… 11/13
2020/7/29 快速指南:使用OpenCV预处理神经网络中的面部图像的

https://github.com/vjgpt/Face-and-Emotion-Recognition

交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、
检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),请扫描下面微信号加
群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备
注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会
请出群,谢谢理解~

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489691&idx=1&sn=ed98af224018e09ecaae1419b1a03307&chksm=fb56fa… 12/13
2020/7/29 使用OpenCV实现车道线检测

使用OpenCV实现车道线检测
原创 小林 小白学视觉 6月18日

来自专辑

OpenCV应用

点击上方“小白学视觉”,选择“星标”公众号
重磅干货,第一时间送达

采集

图0 印度泰米尔纳德邦安纳马莱森林公路上的车道检测

本文源码:https://github.com/KushalBKusram/AdvancedLaneDetection

计算机视觉在自动化系统观测环境、预测该系统控制器输入值等方面起着至关重要的作用。本文
介绍了使用计算机视觉技术进行车道检测的过程,并引导我们完成识别车道区域、计算道路RoC
和估计车道中心距离的步骤。

摄像机校准(calibrateCamera.py)

几乎所有摄像机使用的镜头在聚焦光线以捕捉图像时都存在一定的误差,因为这些光线由于折射
在镜头边缘发生了弯曲。这种现象会导致图像边缘的扭曲。以下视频用示例解释了两种主要的失
真类型,强烈建议观看。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488373&idx=1&sn=af55fb1dec79b2281b0923ca6c18216c&chksm=fb56f199c… 1/8
2020/7/29 使用OpenCV实现车道线检测

02:50

假设我们现在了解什么是径向失真,需要利用失真系数(k1、k2 和 k3)来校正径向失真。
calibrateCamera.py是摄像机校准程序,默认情况下不运行该程序。建议在生成目标上的特征
点 和 图 像 上 的 特 征 点 的 过 程 中 至 少 使 用 20 个 棋 盘 图 像 。 Main 中 的 calibrate() 将
在/data/calibration中查找图像,但是我们也可以选择其他目录。

图1 左图:图像失真;右:未失真的图像

去除图像失真的整个过程是相当有趣的,OpenCV有一个很好的教程,解释了概念并举出一些
例子。

透视变换(preprocess.py:8–19)

检测车道的第一步是调整我们的视觉系统,以鸟瞰的角度来观察前方的道路,这将有助于计算道
路的曲率,因此将有助于我们预测未来几百米的转向角大小。自上而下视图的另一个好处是,它
解决了车道线相交的问题。实际上只要沿道路行驶,车道线就是平行线。

鸟瞰图可以通过应用透视变换来实现,即将输入图像中车道区域四个点映射到所需点上,从而生
成自顶向下的视图。这些点是根据个案确定,决定因素主要是摄像头在车辆中的位置及其视野。
图2中的图片分别表示输入和转换后输出图像。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488373&idx=1&sn=af55fb1dec79b2281b0923ca6c18216c&chksm=fb56f199c… 2/8
2020/7/29 使用OpenCV实现车道线检测

图2 左图:之前、右侧:之后

阈值(preprocess.py:22)

现在车道线是平行的,下一步将它们从输入图像上分割出来。输入图像包含RGB3个通道,车道
线为白色或黄色。基于这个假设,输入图像可以转换为单个通道灰度图像,从而消除我们不需要
的通道。另一个要转换为的颜色空间是HLS颜色空间,其中S通道可能会根据照明情况提供较好
的结果。在以下示例中,将使用图像阈值,因为在给定的输入图像中它可以正常工作。图3在阈
值处理后可视化输出。

图3 cv2.threshold(image, 220, 225, cv2.THRESH_BINARY)

下阈值(220)和上阈值(225)将根据输入图像手动调整。OpenCV有基于整体嵌套边缘检测
的先进技术,而无需对阈值进行任何手动调整,但本文仍然使用的是简单的阈值技术。

车道像素查找(laneDetection.py:4~70)

预处理输入图像后,将在图像空间中确定并绘制车道。方法是在二进制图像(阈值图像)的下半
部分绘制非零像素直方图,以观察模式:

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488373&idx=1&sn=af55fb1dec79b2281b0923ca6c18216c&chksm=fb56f199c… 3/8
2020/7/29 使用OpenCV实现车道线检测

图4直方图x=像素,y = 计数

由于像素值是二进制的,峰值代表大多数非零像素的位置,因此可以很好地指示车道线。直方图
中的x坐标用作搜索相应通道的起点。滑动窗口方法的概念将应用在这里,以下视频说明了滑动
窗口的概念,图5中是结果。

00:25

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488373&idx=1&sn=af55fb1dec79b2281b0923ca6c18216c&chksm=fb56f199c… 4/8
2020/7/29 使用OpenCV实现车道线检测

图5.滑动窗口的概念应用于图 4 的结果。

识别车道面积(laneDetection.py:85~149)

滑动窗口有助于估计每个车道区域的中心,使用这些 x 和 y 像素定位函数
search_around_poly()可以适合二阶多项曲线。该函数适合 f(y)而不是 f(x),因为通道在
图像中是垂直的。图6很好地说明了这一步。

图6 在这些通道上检测到二阶多项形

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488373&idx=1&sn=af55fb1dec79b2281b0923ca6c18216c&chksm=fb56f199c… 5/8
2020/7/29 使用OpenCV实现车道线检测

下一步是计算曲率半径,该半径可以使用与曲线局部部分附近的点紧密拟合的圆进行计算,如图
7 所示。曲线在特定点的曲率半径可以定义为近似圆的半径。此半径可以使用图 7 中的公式计
算。

图7 曲率概念图的半径和用于计算 RoC 的方程

最后一步是在这些点之间放置一个多边形,并将其投影回原始图像,来突出显示车道区域。曲率
的车道面积和半径是根据像素值计算的,像素值与真实世界空间不同,因此必须转换为现实世界
的值,这涉及到测量我们投射扭曲图像的车道部分的长度和宽度。为简单起见,我们可以假设大
多数车道通常长 30 米,宽 3.7 米,并且代码使用这些值将像素空间值转换为实际仪表测量值。

图 8 最终预期结果突出显示车道区域

结果

下面的视频显示,我们的结果还是非常不错的。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488373&idx=1&sn=af55fb1dec79b2281b0923ca6c18216c&chksm=fb56f199c… 6/8
2020/7/29 使用OpenCV实现车道线检测

00:50

交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、检测、
分 割 、 识 别 、 医 学 影 像 、 GAN 、 算 法 竞 赛 等 微 信 群 ( 以 后 会 逐 渐 细 分 ) , 请 扫 描 下 面 微 信 号 加 群 , 备
注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通
过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488373&idx=1&sn=af55fb1dec79b2281b0923ca6c18216c&chksm=fb56f199c… 7/8
2020/9/30 基于Python进行相机校准

基于Python进行相机校准
原创 努比 小白学视觉 1周前

点击上方“小白学视觉”,选择加"星标"或“置顶”
重磅干货,第一时间送达

相机校准的目的是找到相机的内在和外在参数。

总览
为了校准相机,我们对3D对象(例如图案立方体)成像,并使用3D对象与其2d图像
之间的3D-2D点对应关系来查找相机参数。

我们需要找到两组参数:内在参数和外在参数。固有参数是摄像机内部的那些参数,
例如焦距,主要点等,而固有参数是规定摄像机相对于摄像机的位置t(平移矢量)和
方向R(旋转矩阵)的参数。外部坐标系(通常称为世界坐标系)。在第一部分中,我
们将仅计算内部参数(假设外部参数是已知的),而在第二部分中,我们将共同计算
内部参数和外部参数。

内部参数计算

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247491213&idx=1&sn=9fec74a9f2842dcf947f17e16e4ba751&chksm=fb56fc61cc… 1/6
2020/9/30 基于Python进行相机校准

我们使用的校准对象是魔方。

我们对立方体进行成像,如下图所示。然后,我们获得许多3D-2D点对应关系。在这
一部分中,我们已经计算了点对应关系,您要做的就是从它们中计算出固有参数。3D-
2D对应关系在数据文件“ pt_corres.mat”中给出。该文件包含“ pts_2D”,2D点和“
cam_pts_3D”以及所有对应的3D点。现在,我们必须找到K矩阵

K 矩阵
使3D与2D点相关的矩阵K是具有以下形状的上三角矩阵。

其中αx,αy表示以x和y像素尺寸表示的焦距,px和py是主要点,s是偏斜参数。

2D点(x,y)与相应3D点(X,Y,Z)之间的关系由下式给出

1. x =αx(X / Z)+ s(Y / Z)+ px

2. y =αy(Y / Z)+ py

内部和外部参数计算

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247491213&idx=1&sn=9fec74a9f2842dcf947f17e16e4ba751&chksm=fb56fc61cc… 2/6
2020/9/30 基于Python进行相机校准

在上一部分中,我们假设已知外部参数,然后计算了内部参数,即假设我们知道了相
机坐标系中的3D点对应关系。但是这种情况很少发生。几乎总是我们仅在世界坐标系
中知道3D点的对应关系,因此我们需要估算内在和外在参数。但是在此之前,我们需
要获取3D-2D点对应关系。如图1所示,相对于世界坐标系描述了3D点。该图显示了
世界坐标系的x,y和z轴以及一些示例3D点,它们是正方形的角。有28点。

1. 世 界 坐 标 系 中 的 3D 点 在 rubik_3D_pts.mat 中 提 供 , 图 像 上 相 应 的 2D 点 在
rubik_2D_pts.mat中提供

2. 接下来,我们要计算相机投影矩阵P = K [R t],其中K是内部/本征校准矩阵,R是
旋转矩阵,用于指定相机坐标系与世界坐标系的方向,而t是转换向量,可以确定摄影
机中心在世界坐标系中的位置。

3. 为了计算P,我们使用“直接线性变换(DLT)”。DLT是要理解的重要算法,下面将
对其进行详细说明。

离散线性变换(DLT )
离散线性变换(DLT)是一种简单的线性算法,用于从相应的3空间和图像实体估计摄
像机投影矩阵P。相机矩阵的这种计算称为切除。最简单的这种对应关系是在未知相机
映射下的3D点X及其图像x之间。给定足够多的这种对应关系,可以确定相机矩阵。

算法
假设给出了3D点和2D图像点之间的许多点对应关系。相机矩阵是一个3x4矩阵,它通
过xi = P.Xi将点关联起来。对于每个对应关系Xi xi xi,我们得到三个方程,其中两个
线性独立,在下面进行描述

步骤
1. 从一组n个点对应关系中,我们通过为每个对应关系堆叠以上形式的方程式来获得
2nx12矩阵A
2. 获 得 A 的 SVD 。 对 应 于 最 小 奇 异 值 的 单 位 奇 异 向 量 是 解 p 。 具 体 来 说 , 如 果 A =
UDVT,D对角线带有对角线正项,并按对角线降序排列,则p是V的最后一列
3. 获得p并以矩阵形式写入以获得矩阵P

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247491213&idx=1&sn=9fec74a9f2842dcf947f17e16e4ba751&chksm=fb56fc61cc… 3/6
2020/9/30 基于Python进行相机校准

通过解方程组Ap = 0来计算投影矩阵P,其中p是包含矩阵P项的向量。

计算 P 所需的最小点对应数量

3×4矩阵P具有12个元素,但比例是任意的,因此具有11个自由度。由于每个点的对应
关系都有2个方程,因此至少需要5.5个对应关系才能求解P。0.5表示从第六个点开始
仅使用一个方程,即我们选择x坐标或y-第六个图像点的坐标。

在此最小数量的对应关系下,该解决方案是精确的,并且可以通过求解Ap = 0来获
得,其中A在这种情况下为11x12矩阵。

如果数据不精确,则给出n≥6个点对应关系,那么将没有精确的解决方案,我们通过
最小化代数或几何误差来解决。

从投影矩阵 P 获得参数 K , R 和 t

通过RQ分解将P分解为K,R,t。它涉及计算分解A = RQ,其中Q为unit /正交,R为


上三角。

验证计算参数的准确性

为此,我们将计算重新投影误差,该误差是对2D点与通过使用计算出的相机参数投影
3D点而获得的2D点之间距离的度量。

该图以橙色显示了原始2D点,并以绿色显示了重新投影的点。可以看出,重新投影的
点与实际点非常匹配。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247491213&idx=1&sn=9fec74a9f2842dcf947f17e16e4ba751&chksm=fb56fc61cc… 4/6
2020/9/30 基于Python进行相机校准

参考文献

[1] R. Hartley and A. Zissermann, Multiview geometry, 2nd edition, Cambridge


University Press.

[2] Z. Zhang. A flexible new technique for camera calibration. IEEE Transactions
on Pattern Analysis and Machine Intelligence, 22(11):1330–1334, 2000.

有关详细代码,请访问https://github.com/sreenithy/Camera-Calibration。

交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计
算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),
请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交
大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入
相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247491213&idx=1&sn=9fec74a9f2842dcf947f17e16e4ba751&chksm=fb56fc61cc… 5/6
2020/9/30 基于Python进行相机校准

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247491213&idx=1&sn=9fec74a9f2842dcf947f17e16e4ba751&chksm=fb56fc61cc… 6/6
2020/9/30 基于OpenCV 的车牌识别

基于OpenCV 的车牌识别
原创 努比 小白学视觉 8月28日

点击上方“小白学视觉”,选择加"星标"或“置顶”
重磅干货,第一时间送达

车牌识别是一种图像处理技术,用于识别不同车辆。这项技术被广泛用于各种安全检
测中。现在让我一起基于OpenCV编写Python代码来完成这一任务。

车牌识别的相关步骤
1.车牌检测:第一步是从汽车上检测车牌所在位置。我们将使用OpenCV中矩形的轮廓
检测来寻找车牌。如果我们知道车牌的确切尺寸,颜色和大致位置,则可以提高准确
性。通常,也会将根据摄像机的位置和该特定国家/地区所使用的车牌类型来训练检测
算法。但是图像可能并没有汽车的存在,在这种情况下我们将先进行汽车的,然后是
车牌。

2.字符分割:检测到车牌后,我们必须将其裁剪并保存为新图像。同样,这可以使用
OpenCV来完成。

3. 字符识别:现在,我们在上一步中获得的新图像肯定可以写上一些字符(数字/字
母)。因此,我们可以对其执行OCR(光学字符识别)以检测数字。

1.车牌检测
让我们以汽车的样本图像为例,首先检测该汽车上的车牌。然后,我们还将使用相同
的图像进行字符分割和字符识别。如果您想直接进入代码而无需解释,则可以向下滚
动至此页面的底部,提供完整的代码,或访问以下链接。
https://github.com/GeekyPRAVEE/OpenCV-
Projects/blob/master/LicensePlateRecoginition.ipynb

在次使用的测试图像如下所示。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490602&idx=1&sn=ab6b2b1a9e84ff81df2edc3cd0652ba1&chksm=fb56fec6… 1/13
2020/9/30 基于OpenCV 的车牌识别

图片来源链接:https : //rb.gy/lxmiuv

第1步: 将图像调整为所需大小,然后将其灰度。相同的代码如下

1 img = cv2.resize(img, (620,480) )


2 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) #convert to grey scale

调整大小后,可以避免使用较大分辨率的图像而出现的以下问题,但是我们要确保在
调整大小后,车号牌仍保留在框架中。在处理图像时如果不再需要处理颜色细节,那
么灰度变化就必不可少,这加快了其他后续处理的速度。完成此步骤后,图像将像这
样被转换

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490602&idx=1&sn=ab6b2b1a9e84ff81df2edc3cd0652ba1&chksm=fb56fec6… 2/13
2020/9/30 基于OpenCV 的车牌识别

步骤2:每张图片都会包含有用和无用的信息,在这种情况下,对于我们来说,只有牌
照是有用的信息,其余的对于我们的程序几乎是无用的。这种无用的信息称为噪声。
通常,使用双边滤波(模糊)会从图像中删除不需要的细节。

1 gray = cv2.bilateralFilter(gray, 13, 15, 15)

语法为 destination_image = cv2.bilateralFilter(source_image, diameter of pixel,


sigmaColor, sigmaSpace)。我们也可以将sigma颜色和sigma空间从15增加到更高的
值,以模糊掉更多的背景信息,但请注意不要使有用的部分模糊。输出图像如下所示
可以看到该图像中的背景细节(树木和建筑物)模糊了。这样,我们可以避免程序处
理这些区域。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490602&idx=1&sn=ab6b2b1a9e84ff81df2edc3cd0652ba1&chksm=fb56fec6… 3/13
2020/9/30 基于OpenCV 的车牌识别

步骤3:下一步是我们执行边缘检测的有趣步骤。有很多方法可以做到,最简单和流行
的方法是使用OpenCV中的canny edge方法。执行相同操作的行如下所示

1 edged = cv2.Canny(gray, 30, 200) #Perform Edge detection

语法为destination_image = cv2.Canny ( source_image , thresholdValue 1 ,


thresholdValue 2 )。 阈值谷1和阈值2是最小和最大阈值。仅显示强度梯度大于最小
阈值且小于最大阈值的边缘。结果图像如下所示

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490602&idx=1&sn=ab6b2b1a9e84ff81df2edc3cd0652ba1&chksm=fb56fec6… 4/13
2020/9/30 基于OpenCV 的车牌识别

步骤4:现在我们可以开始在图像上寻找轮廓

1 contours=cv2.findContours(edged.copy(),cv2.RETR_TREE,
2 cv2.CHAIN_APPROX_SIMPLE)
3 contours = imutils.grab_contours(contours)
4 contours = sorted(contours,key=cv2.contourArea, reverse = True)[:10]
5 screenCnt = None

一旦检测到计数器,我们就将它们从大到小进行排序,并只考虑前10个结果而忽略其
他结果。在我们的图像中,计数器可以是具有闭合表面的任何事物,但是在所有获得
的结果中,牌照号码也将存在,因为它也是闭合表面。

为了过滤获得的结果中的车牌图像,我们将遍历所有结果,并检查其具有四个侧面和
闭合图形的矩形轮廓。由于车牌肯定是四边形的矩形。

1 for c in cnts:
2 # approximate the contour
3 peri = cv2.arcLength(c, True)
4 approx = cv2.approxPolyDP(c, 0.018 * peri, True)
5 # if our approximated contour has four points, then

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490602&idx=1&sn=ab6b2b1a9e84ff81df2edc3cd0652ba1&chksm=fb56fec6… 5/13
2020/9/30 基于OpenCV 的车牌识别

6 # we can assume that we have found our screen


7 if len(approx) == 4:
8 screenCnt = approx
9 break

找到正确的计数器后,我们将其保存在名为screenCnt的变量中,然后在其周围绘制一
个矩形框,以确保我们已正确检测到车牌。

步骤5:现在我们知道车牌在哪里,剩下的信息对我们来说几乎没有用。因此,我们可
以对整个图片进行遮罩,除了车牌所在的地方。相同的代码如下所示

1 # Masking the part other than the number plate


2 mask = np.zeros(gray.shape,np.uint8)
3 new_image = cv2.drawContours(mask,[screenCnt],0,255,-1,)
4 new_image = cv2.bitwise_and(img,img,mask=mask)

被遮罩的新图像将如下所示

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490602&idx=1&sn=ab6b2b1a9e84ff81df2edc3cd0652ba1&chksm=fb56fec6… 6/13
2020/9/30 基于OpenCV 的车牌识别

2. 字符分割
车牌识别的下一步是通过裁剪车牌并将其保存为新图像,将车牌从图像中分割出来。
然后,我们可以使用此图像来检测其中的字符。下面显示了从主图像裁剪出ROI(感
兴趣区域)图像的代码

1 # Now crop
2 (x, y) = np.where(mask == 255)
3 (topx, topy) = (np.min(x), np.min(y))
4 (bottomx, bottomy) = (np.max(x), np.max(y))
5 Cropped = gray[topx:bottomx+1, topy:bottomy+1]

结果图像如下所示。通常添加到裁剪图像中,如果需要,我们还可以对其进行灰色处
理和边缘化。这样做是为了改善下一步的字符识别。但是我发现即使使用原始图像也
可以正常工作。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490602&idx=1&sn=ab6b2b1a9e84ff81df2edc3cd0652ba1&chksm=fb56fec6… 7/13
2020/9/30 基于OpenCV 的车牌识别

3.字符识别
该车牌识别的最后一步是从分割的图像中实际读取车牌信息。就像前面的教程一样,
我们将使用pytesseract包从图像读取字符。相同的代码如下

1 #Read the number plate


2 text = pytesseract.image_to_string(Cropped, config='--psm 11')
3 print("Detected license plate Number is:",text)

原始图像上印有数字“ CZ20FSE”,并且我们的程序检测到它在jupyter笔记本上打印了
相同的值。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490602&idx=1&sn=ab6b2b1a9e84ff81df2edc3cd0652ba1&chksm=fb56fec6… 8/13
2020/9/30 基于OpenCV 的车牌识别

车牌识别失败案例
车牌识别的完整代码,其中包含程序和我们用来检查程序的测试图像。要记住,此方
法的结果将不准确。准确度取决于图像的清晰度,方向,曝光等。为了获得更好的结
果,您可以尝试同时实现机器学习算法。

这个案例中我们的程序能够正确检测车牌并进行裁剪。但是,Tesseract库无法正确识
别字符。OCR已将其识别为“ MH13CD 0036”,而不是实际的“ MH 13 CD 0096”。通
过使用更好的方向图像或配置Tesseract引擎,可以纠正此类问题。

其他成功的例子
大多数时候,图像质量和方向都是正确的,程序能够识别车牌并从中读取编号。下面
的快照显示了获得的成功结果。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490602&idx=1&sn=ab6b2b1a9e84ff81df2edc3cd0652ba1&chksm=fb56fec6… 9/13
2020/9/30 基于OpenCV 的车牌识别

完整代码

1 #@programming_fever
2 import cv2
3 import imutils
4 import numpy as np
5 import pytesseract
6 pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files (x86)\Tesse
7
8 img = cv2.imread('D://skoda1.jpg',cv2.IMREAD_COLOR)

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490602&idx=1&sn=ab6b2b1a9e84ff81df2edc3cd0652ba1&chksm=fb56fec… 10/13
2020/9/30 基于OpenCV 的车牌识别

9 img = cv2.resize(img, (600,400) )


10
11 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
12 gray = cv2.bilateralFilter(gray, 13, 15, 15)
13
14 edged = cv2.Canny(gray, 30, 200)
15 contours = cv2.findContours(edged.copy(), cv2.RETR_TREE, cv2.CHAIN_APP
16 contours = imutils.grab_contours(contours)
17 contours = sorted(contours, key = cv2.contourArea, reverse = True)[:10
18 screenCnt = None
19
20 for c in contours:
21
22 peri = cv2.arcLength(c, True)
23 approx = cv2.approxPolyDP(c, 0.018 * peri, True)
24
25 if len(approx) == 4:
26 screenCnt = approx
27 break
28
29 if screenCnt is None:
30 detected = 0
31 print ("No contour detected")
32 else:
33 detected = 1
34
35 if detected == 1:
36 cv2.drawContours(img, [screenCnt], -1, (0, 0, 255), 3)
37
38 mask = np.zeros(gray.shape,np.uint8)
39 new_image = cv2.drawContours(mask,[screenCnt],0,255,-1,)
40 new_image = cv2.bitwise_and(img,img,mask=mask)
41
42 (x, y) = np.where(mask == 255)
43 (topx, topy) = (np.min(x), np.min(y))
44 (bottomx, bottomy) = (np.max(x), np.max(y))
45 Cropped = gray[topx:bottomx+1, topy:bottomy+1]
46
47 text = pytesseract.image_to_string(Cropped, config='--psm 11')
48 print("programming_fever's License Plate Recognition\n")

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490602&idx=1&sn=ab6b2b1a9e84ff81df2edc3cd0652ba1&chksm=fb56fec… 11/13
2020/9/30 基于OpenCV 的车牌识别

49 print("Detected license plate Number is:",text)


50 img = cv2.resize(img,(500,300))
51 Cropped = cv2.resize(Cropped,(400,200))
52 cv2.imshow('car',img)
53 cv2.imshow('Cropped',Cropped)
54
55 cv2.waitKey(0)
56 cv2.destroyAllWindows()

Github链接-https: //github.com/GeekyPRAVEE/OpenCV-
Projects/blob/master/LicensePlateRecoginition.ipynb

交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计
算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),
请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交
大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入
相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490602&idx=1&sn=ab6b2b1a9e84ff81df2edc3cd0652ba1&chksm=fb56fec… 12/13
2020/9/30 基于OpenCV 的车牌识别

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490602&idx=1&sn=ab6b2b1a9e84ff81df2edc3cd0652ba1&chksm=fb56fec… 13/13
2020/9/30 基于OpencvCV的情绪检测

基于OpencvCV的情绪检测
原创 努比 小白学视觉 8月16日

点击上方“小白学视觉”,选择加"星标"或“置顶”
重磅干货,第一时间送达

情绪检测或表情分类在深度学习领域中有着广泛的研究。使用相机和一些简单的代码
我们就可以对情绪进行实时分类,这也是迈向高级人机交互的一步。

前言
本期我们将首先介绍如何使用Keras 创建卷积神经网络模型,再使用摄像头获取图片
进行情绪检测。

为了更好的阅读体验,我们最好具备一下知识:

• Python

• OpenCV的

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490278&idx=1&sn=feeed6d9e4a622a60907bfa7dd7557e5&chksm=fb56f80… 1/16
2020/9/30 基于OpencvCV的情绪检测

• 卷积神经网络(CNN)

• numpy
(注意:我们使用的Tensorflow是1.13.1版本、keras是 版本2.3.1)

模型制作
首先,我们将创建模型代码并解释其中的含义。代码的创建总共分为以下5个部分。

任务 1 :
导入该项目所需的必需模块。

1 import keras
2 from keras.preprocessing.image import ImageDataGenerator
3 from keras.models import Sequential
4 from keras.layers import Dense,Dropout,Activation,Flatten,BatchNormaliza
5 from keras.layers import Conv2D,MaxPooling2D
6 import os

现在让我们定义一些变量,这些变量将节省手动输入的时间。

1 num_classes=5
2 img_rows,img_cols=48,48
3 batch_size=32

以上变量的说明如下:

• num_classses = 5:训练模型时要处理的类即情感的种类数。

• img_rows=48,img_cols = 48:馈送到神经网络中的图像阵列大小。

• batch_size = 32:更新模型之前处理的样本数量。epochs 是完整通过训练数据集的


次数。batch_size必须大于等于1并且小于或等于训练数据集中的样本数。

任务 2 :
现在让我们开始加载模型,这里使用的数据集是fer2013,该数据集是由kaggle托管
的开源数据集。数据集共包含7类,分别是愤怒、厌恶、恐惧、快乐、悲伤、惊奇、无
表情,训练集共有28,709个示例。该数据集已从网站上删除,但我们在以下链接中可
以找到相关代码和数据集。https://github.com/karansjc1/emotion-detection

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490278&idx=1&sn=feeed6d9e4a622a60907bfa7dd7557e5&chksm=fb56f80… 2/16
2020/9/30 基于OpencvCV的情绪检测

数据集的存储库中
我们将数据储存在特定文件夹中。例如,“愤怒”文件夹包含带有愤怒面孔等的图片。
在这里,我们使用5类,包括“愤怒”,“快乐”,“悲伤”,“惊奇”和“无表情”。使用24256
张图像作为训练数据,3006张图像作为检测数据。

现在让我们将数据加载到一些变量中。

1 train_data_dir='fer2013/train'
2 validation_data_dir='fer2013/validation'

以上两行导入了检测和训练数据。该模型是在训练数据集上进行训练的;在检测数据
集上检测该模型性能,检测数据集是原始数据集的一部分,从原始数据集上分离开来
的。

任务 3 :

现在,我们对这些数据集进行图像增强。图像数据增强可以扩展训练数据集大小,改
善图像质量。Keras深度学习神经网络库中的ImageDataGenerator类通过图像增强来
拟合模型。

1 train_datagen = ImageDataGenerator(
2 rescale=1./255,
3 rotation_range=30,
4 shear_range=0.3,
5 zoom_range=0.3,
6 width_shift_range=0.4,
7 height_shift_range=0.4,
8 horizontal_flip=True,
9 fill_mode='nearest')
10 validation_datagen = ImageDataGenerator(rescale=1./255)

train_datagen变量以下方法人为地扩展数据集:

• rotation_range:随机旋转,在这里我们使用30度。

• shear_range:剪切强度(逆时针方向的剪切角,以度为单位)。在这里我们使用
0.3作为剪切范围。

• zoom_range:随机缩放的范围,这里我们使用0.3作为缩放范围。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490278&idx=1&sn=feeed6d9e4a622a60907bfa7dd7557e5&chksm=fb56f80… 3/16
2020/9/30 基于OpencvCV的情绪检测

• width_shift_range:在图像的整个宽度上移动一个值。

• height_shift_range:这会在整个图像高度上移动一个值。

• horizontal_flip:水平翻转图像。

• fill_mode:通过上述使用的方法更改图像的方向后填充像素,使用“最近”作为填充
模式,即用附近的像素填充图像中丢失的像素。

在这里,我只是重新保存验证数据,而没有执行任何其他扩充操作,因为我想使用与
训练模型中数据不同的原始数据来检查模型。

1 train_generator = train_datagen.flow_from_directory(
2 train_data_dir,
3 color_mode='grayscale',
4 target_size=(img_rows,img_cols),
5 batch_size=batch_size,
6 class_mode='categorical',
7 shuffle=True)
8 validation_generator = validation_datagen.flow_from_directory(
9 validation_data_dir,
10 color_mode='grayscale',
11 target_size=(img_rows,img_cols),
12 batch_size=batch_size,
13 class_mode='categorical',
14 shuffle=True)

上面代码的输出将是:

1 Found 24256 images belonging to 5 classes.


2 Found 3006 images belonging to 5 classes.

在上面的代码中,我正在使用flow_from_directory()方法从目录中加载我们的数据
集 , 该 目 录 已 扩 充 并 存 储 在 train_generator 和 validation_generator 变 量 中 。
flow_from_directory()采用目录的路径并生成一批扩充数据。因此,在这里,我们
为该方法提供了一些选项,以自动更改尺寸并将其划分为类,以便更轻松地输入模
型。

给出的选项是:

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490278&idx=1&sn=feeed6d9e4a622a60907bfa7dd7557e5&chksm=fb56f80… 4/16
2020/9/30 基于OpencvCV的情绪检测

• directory:数据集的目录。

• color_mode:在这里,我将图像转换为灰度,因为我对图像的颜色不感兴趣,而仅
对表达式感兴趣。

• target_size:将图像转换为统一大小。

• batch_size:制作大量数据以进行训练。

• class_mode:在这里,我将“类别”用作类模式,因为我将图像分为5类。

• shuffle:随机播放数据集以进行更好的训练。

任务 4 :

数据集的修改已完成,现在是该模型的大脑即CNN网络。

因此,首先,我将定义将要使用的模型的类型。在这里,我使用的是Sequential模
型,该模型定义网络中的所有层将依次相继并将其存储在变量模型中。

1 model = Sequential()

该网络由7个块组成:(后面我们将逐层解释)

1 #Block-1
2 model.add(Conv2D(32,(3,3),padding='same',kernel_initializer='he_normal
3 input_shape=(img_rows,img_cols,1)))
4 model.add(Activation('elu'))
5 model.add(BatchNormalization())
6 model.add(Conv2D(32,(3,3),padding='same',kernel_initializer='he_normal
7 input_shape=(img_rows,img_cols,1)))
8 model.add(Activation('elu'))
9 model.add(BatchNormalization())
10 model.add(MaxPooling2D(pool_size=(2,2)))
11 model.add(Dropout(0.2))
12 #Block-2
13 model.add(Conv2D(64,(3,3),padding='same',kernel_initializer='he_normal
14 model.add(Activation('elu'))
15 model.add(BatchNormalization())
16 model.add(Conv2D(64,(3,3),padding='same',kernel_initializer='he_normal
17 model.add(Activation('elu'))
https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490278&idx=1&sn=feeed6d9e4a622a60907bfa7dd7557e5&chksm=fb56f80… 5/16
2020/9/30 基于OpencvCV的情绪检测

18 model.add(BatchNormalization())
19 model.add(MaxPooling2D(pool_size=(2,2)))
20 model.add(Dropout(0.2))
21 #Block-3
22 model.add(Conv2D(128,(3,3),padding='same',kernel_initializer='he_norma
23 model.add(Activation('elu'))
24 model.add(BatchNormalization())
25 model.add(Conv2D(128,(3,3),padding='same',kernel_initializer='he_norma
26 model.add(Activation('elu'))
27 model.add(BatchNormalization())
28 model.add(MaxPooling2D(pool_size=(2,2)))
29 model.add(Dropout(0.2))
30 #Block-4
31 model.add(Conv2D(256,(3,3),padding='same',kernel_initializer='he_norma
32 model.add(Activation('elu'))
33 model.add(BatchNormalization())
34 model.add(Conv2D(256,(3,3),padding='same',kernel_initializer='he_norma
35 model.add(Activation('elu'))
36 model.add(BatchNormalization())
37 model.add(MaxPooling2D(pool_size=(2,2)))
38 model.add(Dropout(0.2))
39 #Block-5
40 model.add(Flatten())
41 model.add(Dense(64,kernel_initializer='he_normal'))
42 model.add(Activation('elu'))
43 model.add(BatchNormalization())
44 model.add(Dropout(0.5))
45 #Block-6
46 model.add(Dense(64,kernel_initializer='he_normal'))
47 model.add(Activation('elu'))
48 model.add(BatchNormalization())
49 model.add(Dropout(0.5))
50 #Block-7
51 model.add(Dense(num_classes,kernel_initializer='he_normal'))
52 model.add(Activation('softmax'))

运行以上代码,如果使用的是旧版本的tensorflow,则会收到一些警告。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490278&idx=1&sn=feeed6d9e4a622a60907bfa7dd7557e5&chksm=fb56f80… 6/16
2020/9/30 基于OpencvCV的情绪检测

在这里,我使用了存在于keras.layers中的7种类型的层。

这些层是:

•Conv2D(
filters, kernel_size, strides=(1, 1), padding=’valid’, data_format=None,
dilation_rate=(1, 1), activation=None, use_bias=True,
kernel_initializer=’glorot_uniform’, bias_initializer=’zeros’,
kernel_regularizer=None, bias_regularizer=None, activity_regularizer=None,
kernel_constraint=None, bias_constraint=None, **kwargs)

• Activation(activation_type)

• BatchNormalization()

• MaxPooling2D(pool_size, strides,padding, data_format, **kwargs)

• Dropout(dropout_value)

• Flatten()

• Dense(
units,
activation=None,
use_bias=True,
kernel_initializer=”glorot_uniform”,
bias_initializer=”zeros”,
kernel_regularizer=None,
bias_regularizer=None,
activity_regularizer=None,
kernel_constraint=None,
bias_constraint=None,
**kwargs)

Block-1层的出现顺序如下:
• Conv2D层-此层为网络创建卷积层。我们创建的该层包含32个大小为(3,3)滤波
器,其中使用padding ='same'填充图像并使用内核初始化程序he_normal。添加了2
个卷积层,每个层都有一个激活层和批处理归一化层。

• 激活层-使用elu激活。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490278&idx=1&sn=feeed6d9e4a622a60907bfa7dd7557e5&chksm=fb56f80… 7/16
2020/9/30 基于OpencvCV的情绪检测

• BatchNormalization(批处理归一化)-归一化每一层的激活,即将平均激活值保持
在接近0并将激活标准偏差保持在接近1。

• MaxPooling2D层-通过沿pool_size定义的沿特征轴的每个尺寸的窗口上的最大值,
对输入表示进行下采样。在此, pool_size大小为(2,2)。

• Dropout : 是 一 种 在 训 练 过 程 中 忽 略 随 机 选 择 的 神 经 元 的 技 术 。 在 这 里 , 我 将
dropout设为0.5,这意味着它将忽略一半的神经元。

Block-2层的出现顺序如下:
• 与block-1相同的层,但是卷积层具有64个滤波器。

Block-3层的出现顺序如下:
• 与block-1相同的层,但是卷积层具有128个滤波器。

Block-4层的出现顺序如下:
• 与block-1相同的层,但是卷积层具有256个滤波器。

Block-5层的出现顺序如下:
• 展平层-将前一层的输出展平,即转换为矢量形式。

• 密集层-该层中每个神经元都与其他每个神经元相连。在这里,我使用带有内核的程
序初始化64个单元或64个神经元-he_normal。

• 这些层之后使用elu激活,批处理归一化,最后以dropout为50%选择忽略。

块6 层的出现顺序如下:
• 与模块5相同的层,但没有展平层,因为该模块的输入已展平。

块7 层的出现顺序如下:
• 密 集 层 - 网 络 的 最 后 一 个 块 中 , 我 使 用 num_classes 创 建 一 个 密 集 层 , 该 层 具 有
he_normal初始值设定项,其unit =类数。

• 激活层-在这里,我使用softmax,该层多用于分类。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490278&idx=1&sn=feeed6d9e4a622a60907bfa7dd7557e5&chksm=fb56f80… 8/16
2020/9/30 基于OpencvCV的情绪检测

现在检查模型的整体结构:

1 print(model.summary())

输出将是:

1 Model: "sequential_1"
2 _________________________________________________________________
3 Layer (type) Output Shape Param #
4 =================================================================
5 conv2d_1 (Conv2D) (None, 48, 48, 32) 320
6 _________________________________________________________________
7 activation_1 (Activation) (None, 48, 48, 32) 0
8 _________________________________________________________________
9 batch_normalization_1 (Batch (None, 48, 48, 32) 128
10 _________________________________________________________________
11 conv2d_2 (Conv2D) (None, 48, 48, 32) 9248
12 _________________________________________________________________
13 activation_2 (Activation) (None, 48, 48, 32) 0
14 _________________________________________________________________
15 batch_normalization_2 (Batch (None, 48, 48, 32) 128
16 _________________________________________________________________
17 max_pooling2d_1 (MaxPooling2 (None, 24, 24, 32) 0
18 _________________________________________________________________
19 dropout_1 (Dropout) (None, 24, 24, 32) 0
20 _________________________________________________________________
21 conv2d_3 (Conv2D) (None, 24, 24, 64) 18496
22 _________________________________________________________________
23 activation_3 (Activation) (None, 24, 24, 64) 0
24 _________________________________________________________________
25 batch_normalization_3 (Batch (None, 24, 24, 64) 256
26 _________________________________________________________________
27 conv2d_4 (Conv2D) (None, 24, 24, 64) 36928
28 _________________________________________________________________
29 activation_4 (Activation) (None, 24, 24, 64) 0
30 _________________________________________________________________
31 batch_normalization_4 (Batch (None, 24, 24, 64) 256
32 _________________________________________________________________
33 max_pooling2d_2 (MaxPooling2 (None, 12, 12, 64) 0

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490278&idx=1&sn=feeed6d9e4a622a60907bfa7dd7557e5&chksm=fb56f80… 9/16
2020/9/30 基于OpencvCV的情绪检测

34 _________________________________________________________________
35 dropout_2 (Dropout) (None, 12, 12, 64) 0
36 _________________________________________________________________
37 conv2d_5 (Conv2D) (None, 12, 12, 128) 73856
38 _________________________________________________________________
39 activation_5 (Activation) (None, 12, 12, 128) 0
40 _________________________________________________________________
41 batch_normalization_5 (Batch (None, 12, 12, 128) 512
42 _________________________________________________________________
43 conv2d_6 (Conv2D) (None, 12, 12, 128) 147584
44 _________________________________________________________________
45 activation_6 (Activation) (None, 12, 12, 128) 0
46 _________________________________________________________________
47 batch_normalization_6 (Batch (None, 12, 12, 128) 512
48 _________________________________________________________________
49 max_pooling2d_3 (MaxPooling2 (None, 6, 6, 128) 0
50 _________________________________________________________________
51 dropout_3 (Dropout) (None, 6, 6, 128) 0
52 _________________________________________________________________
53 conv2d_7 (Conv2D) (None, 6, 6, 256) 295168
54 _________________________________________________________________
55 activation_7 (Activation) (None, 6, 6, 256) 0
56 _________________________________________________________________
57 batch_normalization_7 (Batch (None, 6, 6, 256) 1024
58 _________________________________________________________________
59 conv2d_8 (Conv2D) (None, 6, 6, 256) 590080
60 _________________________________________________________________
61 activation_8 (Activation) (None, 6, 6, 256) 0
62 _________________________________________________________________
63 batch_normalization_8 (Batch (None, 6, 6, 256) 1024
64 _________________________________________________________________
65 max_pooling2d_4 (MaxPooling2 (None, 3, 3, 256) 0
66 _________________________________________________________________
67 dropout_4 (Dropout) (None, 3, 3, 256) 0
68 _________________________________________________________________
69 flatten_1 (Flatten) (None, 2304) 0
70 _________________________________________________________________
71 dense_1 (Dense) (None, 64) 147520
72 _________________________________________________________________
73 activation_9 (Activation) (None, 64) 0

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490278&idx=1&sn=feeed6d9e4a622a60907bfa7dd7557e5&chksm=fb56f8… 10/16
2020/9/30 基于OpencvCV的情绪检测

74 _________________________________________________________________
75 batch_normalization_9 (Batch (None, 64) 256
76 _________________________________________________________________
77 dropout_5 (Dropout) (None, 64) 0
78 _________________________________________________________________
79 dense_2 (Dense) (None, 64) 4160
80 _________________________________________________________________
81 activation_10 (Activation) (None, 64) 0
82 _________________________________________________________________
83 batch_normalization_10 (Batc (None, 64) 256
84 _________________________________________________________________
85 dropout_6 (Dropout) (None, 64) 0
86 _________________________________________________________________
87 dense_3 (Dense) (None, 5) 325
88 _________________________________________________________________
89 activation_11 (Activation) (None, 5) 0
90 =================================================================
91 Total params: 1,328,037
92 Trainable params: 1,325,861
93 Non-trainable params: 2,176
94 _________________________________________________________________
95 None

上面的输出显示了该网络中使用的所有层。这是一个大型网络,包含1,328,037个 参
数。

任务 5 :

最后一步:编译和训练

现在剩下的事情就是编译和训练模型。但是首先让我们导入更多的依赖。

1 from keras.optimizers import RMSprop,SGD,Adam


2 from keras.callbacks import ModelCheckpoint, EarlyStopping, ReduceLROnP

在编译之前,我将使用keras.callbacks类创建以下3个东西:

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490278&idx=1&sn=feeed6d9e4a622a60907bfa7dd7557e5&chksm=fb56f8… 11/16
2020/9/30 基于OpencvCV的情绪检测

Checkpoint(函数— ModelCheckpoint())
它将监视验证损失,并使用mode ='min'属性尝试将损失降至最低。到达检查点时,它
将保存训练有素的最佳大小。Verbose = 1仅用于代码创建检查点时的可视化。这里我
使用以下参数:

• file-path : 保 存 模 型 文 件 的 路 径 , 这 里 我 保 存 的 模 型 文 件 名 为
EmotionDetectionModel.h5

• monitor:要监视的数量。在这里,我正在监视验证损失。

• mode:{自动,最小,最大}之一。如果save_best_only = True,则基于监视数量的
最大化或最小化来决定覆盖当前保存文件。

• save_best_only:如果save_best_only = True,则根据监视数量的最新最佳模型将
不会被覆盖。

• verbose:1:更新数据,0:不变。

提前停止(功能— EarlyStopping())
通过检查以下属性,以提前结束运行。

• monitor:要监视的数量。在这里,我正在监视验证损失。

• min_delta:被监视的数量的最小变化有资格作为改进,即绝对变化小于min_delta
将被视为没有任何改进。在这里我给了0。

• patience:没有改善的时期数,此后将停止训练。我在这里给了它3。

• restore_best_weights:是否从时期以受监视数量的最佳值恢复模型权重。如果为
False,则使用在训练的最后一步获得的模型权重。

• verbose:1:更新数据,0:不变。

降低学习率(函数— ReduceLROnPlateau())
一旦学习停滞,模型通常会受益于将学习率降低2-10倍。回调监视数量,并且如果没
有发现patience的改善,则学习率会降低,为此使用了以下属性。

• monitor:监视特定损失。在这里,我正在监视验证损失。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490278&idx=1&sn=feeed6d9e4a622a60907bfa7dd7557e5&chksm=fb56f8… 12/16
2020/9/30 基于OpencvCV的情绪检测

• factor:降低学习率的因素。new_lr = lr *因子。在这里我使用0.2作为系数。

• patience:没有改善的时期数,之后学习率将降低。我在这里使用3。

• min_delta:测量新的最佳阈值,仅关注重大变化。

• verbose:1:更新数据,0:不变。

现 在 是 时 候 到 最 后 使 用 编 译 模 型 model.compile ( ) 和 适 合 训 练 数 据 集 的 模 型
model.fit_generator()

model.compile()
具有以下参数:

• loss:此值将确定要在代码中使用的损失函数的类型。在这里,我们有5个类别或类
别的分类数据,因此使用了“ categorical_crossentropy”损失。

• optimizer:此值将确定要在代码中使用的优化器功能的类型。这里我使用的学习率
是0.001的Adam优化器,因为它是分类数据的最佳优化器。

• metrics:metrics参数应该是一个列表,模型可以有任意数量的metrics。它是模型
在训练和测试过程中要评估的metrics列表。这里我们使用了精度作为度量标准。

model.fit_generator()
使模型适合Python逐批生成的数据。

它具有以下参数:
• generator:我们之前创建的train_generator对象。
• steps_per_epochs:在一个纪元内接受训练数据的步骤。
• epoch:一次通过整个数据集。
• callbacks:包含我们之前创建的所有回调的列表。
• validation_data:我们之前创建的validation_generator对象。
• validation_steps:在一个时期内采取验证数据的步骤。

1 model.compile(loss='categorical_crossentropy',
2 optimizer = Adam(lr=0.001),
3 metrics=['accuracy'])
4 nb_train_samples = 24176

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490278&idx=1&sn=feeed6d9e4a622a60907bfa7dd7557e5&chksm=fb56f8… 13/16
2020/9/30 基于OpencvCV的情绪检测

5 nb_validation_samples = 3006
6 epochs=25
7 history=model.fit_generator(
8 train_generator,
9 steps_per_epoch=nb_train_samples//batch_size,
10 epochs=epochs,
11 callbacks=callbacks,
12 validation_data=validation_generator,
13 validation_steps=nb_validation_samples//batch_size)

完成!
现在,可以使用此模型创建情绪检测器,从而完成模型生成。

驱动程式码
现在,我们将使用在上一节中创建的模型来说明用于情感检测的代码。

首先,让我们再次导入一些运行代码所需的模块。

1 from keras.models import load_model


2 from keras.preprocessing.image import img_to_array
3 from keras.preprocessing import image
4 import cv2
5 import numpy as np

现在,让我们加载模型,并加载我用来检测摄像头前方人脸的分类器。使用
haarcascade_frontalface_default分类器。Haar Cascade是一种机器学习对象检测
算法,用于识别图像或视频中的对象,并基于Paul Viola和Michael Jones在其论文
《 使 用 简 单 特 征 的 增 强 级 联 进 行 快 速 对 象 检 测 》 中 提 出 的 特 征 概 念 。 2001 。
haarcascade_frontalface_default分类器可检测图像或连续视频源中人的正面。

1 face_classifier=cv2.CascadeClassifier('/haarcascade_frontalface_default
2 classifier = load_model('/EmotionDetectionModel.h5')

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490278&idx=1&sn=feeed6d9e4a622a60907bfa7dd7557e5&chksm=fb56f8… 14/16
2020/9/30 基于OpencvCV的情绪检测

现在,我将定义一个变量class_labels来存储类的名称或我们要预测的情绪类型,还定
义一个变量cap来存储cv2.VideoCapture方法返回的值。在此,VideoCapture中的值0
用于指示该方法使用便携式计算机的主要网络摄像头。

1 class_labels=['Angry','Happy','Neutral','Sad','Surprise']
2 cap=cv2.VideoCapture(0)

结论
因此,在这里我已经解释了使用OpenCV和Keras创建情绪检测的过程。通过以下链接
可以查看完整的代码以及数据集。

https://github.com/karansjc1/emotion-detection

实验结果如下:

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490278&idx=1&sn=feeed6d9e4a622a60907bfa7dd7557e5&chksm=fb56f8… 15/16
2020/9/30 基于OpencvCV的情绪检测

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490278&idx=1&sn=feeed6d9e4a622a60907bfa7dd7557e5&chksm=fb56f8… 16/16
2020/9/30 基于OpenCV的表格文本内容提取

基于OpenCV的表格文本内容提取
原创 努比 小白学视觉 9月5日

点击上方“小白学视觉”,选择加"星标"或“置顶”
重磅干货,第一时间送达

小伙伴们可能会觉得从图像中提取文本是一件很麻烦的事情,尤其是需要提取大量文
本时。PyTesseract是一种光学字符识别(OCR),该库提了供文本图像。

PyTesseract确实有一定的效果,用PyTesseract来检测短文本时,结果相当不错。但
是,当我们用它来检测表格中的文本时,算法执行失败。

图1.直接使用PyTesseract检测表中的文本

图1描绘了文本检测结果,绿色框包围了检测到的单词。可以看出算法对于大部分文本
都无法检测,尤其是数字。而这些数字却是展示了每日COVID-19病例的相关信息。那
么,如何提取这些信息?

简介

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490880&idx=1&sn=857abb328299af950b54abd3dfd7374d&chksm=fb56ffac… 1/15
2020/9/30 基于OpenCV的表格文本内容提取

在编写算法时,我们通常应该以我们人类理解问题的方式来编写算法。这样,我们可
以轻松地将想法转化为算法。

当我们阅读表格时,首先注意到的就是单元格。一个单元格使用边框(线)与另一个
单元格分开,边框可以是垂直的也可以是水平的。识别单元格后,我们继续阅读其中
的信息。将其转换为算法,您可以将过程分为三个过程,即单元格检测、区域
(ROI)选择和文本提取。

在执行每个任务之前,让我们先导入必要内容

1 import cv2 as cv
2 import numpy as np
3 filename = 'filename.png'
4 img = cv.imread(cv.samples.findFile(filename))
5 cImage = np.copy(img) #image to draw lines
6 cv.imshow("image", img) #name the window as "image"
7 cv.waitKey(0)
8 cv.destroyWindow("image") #close the window

单元格检测
查找表格中的水平线和垂直线可能是最容易开始的。有多种检测线的方法,这里我们
采用OpenCV库中的Hough Line Transform。

在应用霍夫线变换之前,需要进行一些预处理。第一是将存在的RGB图像转换为灰度
图像。因为灰度图像对于Canny边缘检测而言非常重要。

1 gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)


2 cv.imshow("gray", gray)
3 cv.waitKey(0)
4 cv.destroyWindow("gray")
5 canny = cv.Canny(gray, 50, 150)
6 cv.imshow("canny", canny)
7 cv.waitKey(0)
8 cv.destroyWindow("canny")

下面的两幅图分别显示了灰度图像和Canny图像。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490880&idx=1&sn=857abb328299af950b54abd3dfd7374d&chksm=fb56ffac… 2/15
2020/9/30 基于OpenCV的表格文本内容提取

图2.灰度和Canny图像

霍夫线变换

在OpenCV中,此算法有两种类型,即标准霍夫线变换和概率霍夫线变换。标准变换为
我们提供直线方程,因此我们无法得知直线的起点和终点。概率变换将为我们提供线
列表,即直线起点与终点的坐标值列表。我们优先选用的是概率变化。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490880&idx=1&sn=857abb328299af950b54abd3dfd7374d&chksm=fb56ffac… 3/15
2020/9/30 基于OpenCV的表格文本内容提取

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490880&idx=1&sn=857abb328299af950b54abd3dfd7374d&chksm=fb56ffac… 4/15
2020/9/30 基于OpenCV的表格文本内容提取

图3.霍夫线变换结果示例(来源:OpenCV)

对于HoughLinesP函数,有如下几个输入参数:

image -8位单通道二进制源图像。该图像可以通过该功能进行修改。

rho —累加器的距离分辨率,以像素为单位。

theta —弧度的累加器角度分辨率。

threshold-累加器阈值参数。仅返回那些获得足够投票的行

line — 线的输出向量。这里设置为无,该值保存到linesP

minLineLength —最小行长。短于此的线段将被拒绝。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490880&idx=1&sn=857abb328299af950b54abd3dfd7374d&chksm=fb56ffac… 5/15
2020/9/30 基于OpenCV的表格文本内容提取

maxLineGap —同一线上的点之间允许链接的最大间隙。

1 # cv.HoughLinesP(image, rho, theta, threshold[, lines[, minLineLength[,


2 rho = 1
3 theta = np.pi/180
4 threshold = 50
5 minLinLength = 350
6 maxLineGap = 6
7 linesP = cv.HoughLinesP(canny, rho , theta, threshold, None, minLinLengt

为了区分水平线和垂直线,我们定义了一个函数并根据该函数的返回值添加列表。

1 def is_vertical(line):
2 return line[0]==line[2]
3 def is_horizontal(line):
4 return line[1]==line[3]
5 horizontal_lines = []
6 vertical_lines = []
7
8 if linesP is not None:
9 for i in range(0, len(linesP)):
10 l = linesP[i][0]
11 if (is_vertical(l)):
12 vertical_lines.append(l)
13
14 elif (is_horizontal(l)):
15 horizontal_lines.append(l)
16 for i, line in enumerate(horizontal_lines):
17 cv.line(cImage, (line[0], line[1]), (line[2], line[3]), (0,255,0),
18
19 for i, line in enumerate(vertical_lines):
20 cv.line(cImage, (line[0], line[1]), (line[2], line[3]), (0,0,255),
21
22 cv.imshow("with_line", cImage)
23 cv.waitKey(0)
24 cv.destroyWindow("with_line") #close the window
25

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490880&idx=1&sn=857abb328299af950b54abd3dfd7374d&chksm=fb56ffac… 6/15
2020/9/30 基于OpenCV的表格文本内容提取

图4.霍夫线变换结果—没有重叠滤波器

重叠滤波器

检测到的线如上图所示。但是,霍夫线变换结果中有一些重叠的线。较粗的线由多个
相同位置,长度不同的线组成。为了消除此重叠线,我们定义了一个重叠过滤器。

最初,基于分类索引对线进行分类,水平线的y₁和垂直线的x₁。如果下一行的间隔小
于一定距离,则将其视为与上一行相同的行。

1 def overlapping_filter(lines, sorting_index):


2 filtered_lines = []
3
4 lines = sorted(lines, key=lambda lines: lines[sorting_index])
5 separation = 5
6 for i in range(len(lines)):
7 l_curr = lines[i]
8 if(i>0):
9 l_prev = lines[i-1]
10 if ( (l_curr[sorting_index] - l_prev[sorting_index]) >
11 filtered_lines.append(l_curr)
12 else:
13 filtered_lines.append(l_curr)
14

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490880&idx=1&sn=857abb328299af950b54abd3dfd7374d&chksm=fb56ffac… 7/15
2020/9/30 基于OpenCV的表格文本内容提取

15 return filtered_lines

实现重叠滤镜并在图像上添加文本,现在代码应如下所示:

1 horizontal_lines = []
2 vertical_lines = []
3
4 if linesP is not None:
5 for i in range(0, len(linesP)):
6 l = linesP[i][0]
7 if (is_vertical(l)):
8 vertical_lines.append(l)
9
10 elif (is_horizontal(l)):
11 horizontal_lines.append(l)
12 horizontal_lines = overlapping_filter(horizontal_lines, 1)
13 vertical_lines = overlapping_filter(vertical_lines, 0)
14 for i, line in enumerate(horizontal_lines):
15 cv.line(cImage, (line[0], line[1]), (line[2], line[3]), (0,255,0),
16 cv.putText(cImage, str(i) + "h", (line[0] + 5, line[1]), cv.FONT_H
17 for i, line in enumerate(vertical_lines):
18 cv.line(cImage, (line[0], line[1]), (line[2], line[3]), (0,0,255),
19 cv.putText(cImage, str(i) + "v", (line[0], line[1] + 5), cv.FONT_H
20
21 cv.imshow("with_line", cImage)
22 cv.waitKey(0)
23 cv.destroyWindow("with_line") #close the window
24

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490880&idx=1&sn=857abb328299af950b54abd3dfd7374d&chksm=fb56ffac… 8/15
2020/9/30 基于OpenCV的表格文本内容提取

图5.霍夫线变换结果—带重叠滤波器

有了这个代码,就不会提取出重叠的行了。此外,我们还将在图像中写入水平和垂直
线的索引,这将有利于ROI的选择。

ROI选择
首先,我们需要定义列数和行数。这里我们只对第二行第十四行以及所有列中的数据
感兴趣。对于列,我们定义了一个名为关键字的列表,将其用于字典关键字。

1 ## set keywords
2 keywords = ['no', 'kabupaten', 'kb_otg', 'kl_otg', 'sm_otg', 'ks_otg',
3 'kb_odp', 'kl_odp', 'sm_odp', 'ks_odp', 'not_cvd_odp', 'de
4 'kb_pdp', 'kl_pdp', 'sm_pdp', 'ks_pdp', 'not_cvd_pdp', 'de
5 'positif', 'sembuh', 'meninggal']
6
7 dict_kabupaten = {}
8 for keyword in keywords:
9 dict_kabupaten[keyword] = []
10
11 ## set counter for image indexing
12 counter = 0
13
14 ## set line index
https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490880&idx=1&sn=857abb328299af950b54abd3dfd7374d&chksm=fb56ffac… 9/15
2020/9/30 基于OpenCV的表格文本内容提取

15 first_line_index = 1
16 last_line_index = 14
17

然后,要选择ROI,我们定义了一个函数,该函数将图像(水平线和垂直线都作为输
入)以及线索引作为边框。此函数返回裁剪的图像及其在图像全局坐标中的位置和大

1 def get_cropped_image(image, x, y, w, h):


2 cropped_image = image[ y:y+h , x:x+w ]
3 return cropped_image
4 def get_ROI(image, horizontal, vertical, left_line_index, right_line_i
5 x1 = vertical[left_line_index][2] + offset
6 y1 = horizontal[top_line_index][3] + offset
7 x2 = vertical[right_line_index][2] - offset
8 y2 = horizontal[bottom_line_index][3] - offset
9
10 w = x2 - x1
11 h = y2 - y1
12
13 cropped_image = get_cropped_image(image, x1, y1, w, h)
14
15 return cropped_image, (x1, y1, w, h)

裁剪的图像将用于下一个任务,即文本提取。返回的第二个参数将用于绘制ROI的边
界框

文字提取

现在,我们定义了ROI功能。我们可以继续提取结果。我们可以通过遍历单元格来读
取列中的所有数据。列数由关键字的长度指定,而行数则由定义。

首先,让我们定义一个函数来绘制文本和周围的框,并定义另一个函数来提取文本。

1 import pytesseract
2 pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files (x86)\Tesse
3 def draw_text(src, x, y, w, h, text):

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490880&idx=1&sn=857abb328299af950b54abd3dfd7374d&chksm=fb56ffa… 10/15
2020/9/30 基于OpenCV的表格文本内容提取

4 cFrame = np.copy(src)
5 cv.rectangle(cFrame, (x, y), (x+w, y+h), (255, 0, 0), 2)
6 cv.putText(cFrame, "text: " + text, (50, 50), cv.FONT_HERSHEY_SIMP
7
8 return cFrame
9 def detect(cropped_frame, is_number = False):
10 if (is_number):
11 text = pytesseract.image_to_string(cropped_frame,
12 config ='-c tessedit_char_w
13 else:
14 text = pytesseract.image_to_string(cropped_frame, config='--ps
15
16 return text

将图像转换为黑白以获得更好的效果,让我们开始迭代!

1 counter = 0
2 print("Start detecting text...")
3 (thresh, bw) = cv.threshold(gray, 100, 255, cv.THRESH_BINARY)
4 for i in range(first_line_index, last_line_index):
5 for j, keyword in enumerate(keywords):
6 counter += 1
7
8 left_line_index = j
9 right_line_index = j+1
10 top_line_index = i
11 bottom_line_index = i+1
12
13 cropped_image, (x,y,w,h) = get_ROI(bw, horizontal, vertical, l
14
15 if (keywords[j]=='kabupaten'):
16 text = detect(cropped_image)
17 dict_kabupaten[keyword].append(text)
18
19 else:
20 text = detect(cropped_image, is_number=True)
21 dict_kabupaten[keyword].append(text)
22 image_with_text = draw_text(img, x, y, w, h, text)

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490880&idx=1&sn=857abb328299af950b54abd3dfd7374d&chksm=fb56ffa… 11/15
2020/9/30 基于OpenCV的表格文本内容提取

问题解决
这是文本提取的结果!我们只选择了最后三列,因为它对某些文本给出了奇怪的结
果,其余的很好,所以我不显示它。

图6.检测到的文本—版本1

一些数字被检测为随机文本,即39个数据中的5个。这是由于最后三列与其余列不同。
文本为白色时背景为黑色,会以某种方式影响文本提取的性能。

图7.二进制图像
https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490880&idx=1&sn=857abb328299af950b54abd3dfd7374d&chksm=fb56ffa… 12/15
2020/9/30 基于OpenCV的表格文本内容提取

为了解决这个问题,让我们倒数最后三列。

1 def invert_area(image, x, y, w, h, display=False):


2 ones = np.copy(image)
3 ones = 1
4
5 image[ y:y+h , x:x+w ] = ones*255 - image[ y:y+h , x:x+w ]
6
7 if (display):
8 cv.imshow("inverted", image)
9 cv.waitKey(0)
10 cv.destroyAllWindows()
11 return image
12 left_line_index = 17
13 right_line_index = 20
14 top_line_index = 0
15 bottom_line_index = -1
16
17 cropped_image, (x, y, w, h) = get_ROI(img, horizontal, vertical, left_
18 gray = get_grayscale(img)
19 bw = get_binary(gray)
20 bw = invert_area(bw, x, y, w, h, display=True)

结果如下所示。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490880&idx=1&sn=857abb328299af950b54abd3dfd7374d&chksm=fb56ffa… 13/15
2020/9/30 基于OpenCV的表格文本内容提取

图8.处理后的二进制图像

结果
反转图像后,重新执行步骤,这是最终结果!

算法成功检测到文本后,现在可以将其保存到Python对象(例如Dictionary或List)
中 。 由 于 Tesseract 训 练 数 据 中 未 包 含 某 些 地 区 名 称 ( “ Kabupaten / Kota” 中 的 名
称),因此无法准确检测到。但是,由于可以精确检测到地区的索引,因此这不会成
为问题。文本提取可能无法检测到其他字体的文本,具体取决于所使用的字体,如果
出现误解,例如将“ 5”检测为“ 8”,则可以进行诸如腐蚀膨胀之类的图像处理。

源代码:https://github.com/fazlurnu/Text-Extraction-Table-Image

交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计
算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),
请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交
大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入
相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490880&idx=1&sn=857abb328299af950b54abd3dfd7374d&chksm=fb56ffa… 14/15
2020/9/30 基于OpenCV的表格文本内容提取

小白学视觉 发起了一个读者讨论
小伙伴有什么想说的嘛

精选讨论内容

我不要無聊的風格
Hi,有個疑問:
重疊濾波部分,不需要判斷下相鄰線段的長度,把長的留下來,短的濾掉嗎?基
於文中的這種方式,有可能會把長的去掉,而留下雜小的線段把?

兔哥哥儿
小伙伴有啥想了解的都可以留言哦

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490880&idx=1&sn=857abb328299af950b54abd3dfd7374d&chksm=fb56ffa… 15/15
2020/9/30 基于OpenCV的实时面部识别

基于OpenCV的实时面部识别
原创 努比 小白学视觉 9月12日

点击上方“小白学视觉”,选择加"星标"或“置顶”
重磅干货,第一时间送达

我们将使用一些简单的代码来实现实时面部识别代码,我们可以对个人的面部进行预
测。

现在,面部识别已成为生活中的一部分。因此,在介绍主题之前我们先看看实时面部
识别示例。我们在手机、平板电脑等设备中使用人脸信息进行解锁的时候,这时就要
求获取我们的实时面部图像,并将其储存在数据库中以进一步表明我们的身份。

通过对输入图像进行迭代和预测可以完成这个过程。同样,实时人脸识别可与OpenCV
框架python的实现配合使用。再将它们组合在一个组合级别中,以实现用于实时目的
的模型。

人脸识别
“ 面部识别 ” 名称本身就是一个非常全面的定义,面部识别是通过数字媒体作为输入来
识别或检测人脸的技术执行过程。

人脸识别的准确性可以提供高质量的输出,而不是忽略影响其的问题因素。在这里,
要确保运行我们的模型,必须确保在本地系统中安装了库。
pip install face_recognition

如果在 face_recognition 库的安装过程中遇到一些问题或错误,可以点击以下链接:


https://www.youtube.com/watch?v=xaDJ5xnc8dc

人脸识别本身无法提供清晰的输出,因此出现了OpenCV实现的概念。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490991&idx=1&sn=38990c4bafc18134466dd4e9fb029f71&chksm=fb56ff43c… 1/8
2020/9/30 基于OpenCV的实时面部识别

预先录制的视频中的人脸识别输出示例。

OpenCV
OpenCV 是 python 中一个著名的库,用于实时应用程序。 OpenCV 在计算机世界中
就像树的根一样非常重要。

face_recognition中的OpenCV对我们训练为输入的面部图像进行聚类和特征提取。它
以图像中的地标为目标,以迭代方式在计算机视觉的深度学习方法中训练它们。

在本地系统中安装 OpenCV
pip install opencv-python

使用深度学习算法,OpenCV检测可作为聚类,相似性检测和图像分类的表示。

为什么我们使用OpenCV 作为实时Face_Recognition 中
的关键工具?
人类可以轻松检测到面部,但是我们如何训练机器识别面部?OpenCV在这里填补了人
与计算机之间的空白,并充当了计算机的愿景。

以一个实时的例子为例,当一个人遇到新朋友时,他会记住这些人的脸,以备将来识
别。一个人的大脑反复训练后端的人脸。因此,当他看到那个人的脸时,他说:“嗨,
约翰!你好吗?”。

对面部的识别和可以为计算机提供与人类相同的思维方式。
https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490991&idx=1&sn=38990c4bafc18134466dd4e9fb029f71&chksm=fb56ff43c… 2/8
2020/9/30 基于OpenCV的实时面部识别

OpenCV是计算机视觉中的重要工具。如果我们使用OpenCV,则遵循以下步骤:

• 通过输入提取数据。

• 识别图像中的面部。

• 提取独特的特征,以建立预测思想。

• 该特定人的性格特征,如鼻子,嘴巴,耳朵,眼睛和面部主要特征。

• 实时人脸识别中人脸的比较。

• 识别出的人脸的最终输出。

使用OpenCV python的Face_Recognition:
代码下载:https://github.com/eazyciphers/deep-machine-learning-
tutors/tree/master/Real-Time Face RecognitionGitHub

导入所有软件包:

1 import face_recognition
2 import cv2
3 import numpy as np

加载并训练图像:

1 # Load a sample picture and learn how to recognize it.


2 Jithendra_image = face_recognition.load_image_file("jithendra.jpg")
3 Jithendra_face_encoding = face_recognition.face_encodings(Jithendra_imag
4 # Load a sample picture and learn how to recognize it.
5 Modi_image = face_recognition.load_image_file("Modi.jpg")
6 Modi_face_encoding = face_recognition.face_encodings(Modi_image)[0]

人脸编码:

1 # Create arrays of known face encodings and their names


2 known_face_encodings = [
3 Jithendra_face_encoding,

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490991&idx=1&sn=38990c4bafc18134466dd4e9fb029f71&chksm=fb56ff43c… 3/8
2020/9/30 基于OpenCV的实时面部识别

4 Modi_face_encoding,
5 ]
6 known_face_names = [
7 "Jithendra",
8 "Modi"
9 ]

主要方法:

当实时人脸识别为true时,它将检测到人脸并按照代码中的以下步骤操作:

• 抓取实时视频中的一帧。

• 将图像从BGR颜色(OpenCV使用的颜色)转换为RGB颜色(face_recognition使用
的颜色)

• 在实时视频的帧中找到所有面部和面部编码。

• 循环浏览此视频帧中的每个面孔,并检查该面孔是否与现有面孔匹配。

• 如果一个人脸无法识别现有人脸,则将输出视为未知或未知。

• 识别后,否则在识别出的脸部周围画一个方框。

• 用其名称标记识别的面部。

• 识别后显示结果图像。

退出:
# Hit 'q' on the keyboard to quit!
if cv2.waitKey(1) & 0xFF == ord('q'):
break

释放摄像头的手柄:
# Release handle to the webcam
video_capture.release()
cv2.destroyAllWindows()

输入和输出
在训练过程中提供给模型的样本输入…。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490991&idx=1&sn=38990c4bafc18134466dd4e9fb029f71&chksm=fb56ff43c… 4/8
2020/9/30 基于OpenCV的实时面部识别

输入

用于训练代码的样本图像

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490991&idx=1&sn=38990c4bafc18134466dd4e9fb029f71&chksm=fb56ff43c… 5/8
2020/9/30 基于OpenCV的实时面部识别

样本输入图像进行训练

输出:

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490991&idx=1&sn=38990c4bafc18134466dd4e9fb029f71&chksm=fb56ff43c… 6/8
2020/9/30 基于OpenCV的实时面部识别

记录输出

代码参考:https : //github.com/eazyciphers/deep-machine-learning-
tutors

参考文献:

https://www.pyimagesearch.com/2018/09/24/opencv-face-recognition/

https://www.superdatascience.com/blogs/opencv-face-recognition

https://zh.wikipedia.org/wiki/Facial_recognition_system

交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计
算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),
请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交
大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入
相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490991&idx=1&sn=38990c4bafc18134466dd4e9fb029f71&chksm=fb56ff43c… 7/8
2020/9/30 基于OpenCV的实时面部识别

小白学视觉 发起了一个读者讨论
小伙伴有什么想说的嘛

精选讨论内容

囫笏
可以的话,讲一讲图像分割的历史 ,CPVR,ICCV,ECCV最好的几个例子,让
萌新了解了解图像分割,我就知道这几个月的目标了

作者
安排上

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490991&idx=1&sn=38990c4bafc18134466dd4e9fb029f71&chksm=fb56ff43c… 8/8
2020/9/30 基于OpenCV的图像卡通化

基于OpenCV的图像卡通化
原创 努比 小白学视觉 8月10日

点击上方“小白学视觉”,选择加"星标"或“置顶”
重磅干货,第一时间送达

本期将创建一个类似于Adobe Lightroom的Web应用程序,使用OpenCV和
Streamlit实现图像的卡通化

作为一个狂热的街头摄影爱好者,几乎每个周末都要在城市中拍摄一些照片,因此
Adobe Lightroom始终是我们的首选软件,通过它可以编辑原始照片以使其更具“
Instagram风格”。

我们想能否创建一个自己的图像编辑软件?

开源计算机视觉库(如OpenCV)和开源应用程序框架(如Streamlit)的出现使这一
想法得以实现。使用不到100行代码,我们就可以构建一个简单的图像卡通化Web应用
程序,模仿Adobe Lightroom的功能。

在本文中,我们将展示如何使用OpenCV和Streamlit,根据滤波器,构建一个简单的
Web应用程序,以将图像转换为卡通图像。

如何使图像成为卡通图?
我们通常需要执行两个主要步骤将图像转换为卡通图像:边缘检测和区域平滑。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490152&idx=2&sn=852db92d636b04481b3680d6750d5ce3&chksm=fb56f8… 1/16
2020/9/30 基于OpenCV的图像卡通化

边缘检测的主要目的显然是为了强调图像的边缘,因为卡通图像通常具有良好的边
缘。同时,区域平滑的主要目的是消除颜色边界并减少图像的噪点,使图像像素化程
度降低。

根据不同滤波器,我们可以获得不同的图像卡通化结果。在本文中,将有四个不同的
过滤器:

1. 铅笔素描
2. 细节增强
3. 双边过滤器
4. 铅笔边缘

接下来,我们将展示如何应用每个过滤器,以及从每个过滤器中获得什么样的结果。

铅笔素描滤波器
使用“铅笔素描”滤波器,您的图像将被转换为素描,就像使用铅笔绘制图像一样。下
面是使用OpenCV将图像转换为铅笔素描的完整代码。

1 # Convert the image into grayscale image


2 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
3
4 # Blur the image using Gaussian Blur
5 gray_blur = cv2.GaussianBlur(gray, (25, 25), 0)
6
7 # Convert the image into pencil sketch
8 cartoon = cv2.divide(gray, gray_blur, scale=250.0)

令人惊讶的是,使用OpenCV,我们只需三行代码就可以将图像转换成铅笔素描状的图
片。现在让我逐行解释一下该图像发生了哪些变化。

在第一行中,我们使用OpenCV的cvtColor()功能将图像从彩色通道转换为灰度通
道。这很简单,处理的结果是我们将图像变成了灰度图。

接下来,我们使用高斯模糊对图像进行模糊处理。模糊灰度图像,实际上是在平滑图
像,减少图像的噪点。另外,模糊也是我们检测图像边缘的必要步骤。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490152&idx=2&sn=852db92d636b04481b3680d6750d5ce3&chksm=fb56f8… 2/16
2020/9/30 基于OpenCV的图像卡通化

模糊图像,可以使用OpenCV中的GaussianBlur()功能。我在GaussianBlur()
函数中输入的(25,25)是内核的大小。

由于我们使用高斯模糊,因此内核中像素值的分布遵循正态分布。核数越大,标准偏
差将越大,因此模糊效果越强。下面是内核大小不同时的模糊结果示例。

基于不同内核大小的模糊效果

最后一步是将原始灰度图像除以模糊后的灰度图像。这样可以得出两个图像中每个像
素之间的变化率。模糊效果越强,每个像素的值相对于其原点的变化就越大,因此,
它使我们的铅笔素描更加清晰。

以下是使用铅笔素描过滤器的结果。

铅笔素描过滤器实现示例

细节增强滤波器

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490152&idx=2&sn=852db92d636b04481b3680d6750d5ce3&chksm=fb56f8… 3/16
2020/9/30 基于OpenCV的图像卡通化

简而言之,“细节增强”滤镜通过锐化图像,平滑颜色以及增强边缘效果为我们提供了
卡通效果。以下是使用此滤镜将您的图像转换成卡通的完整代码。

1 #convert the image into grayscale image


2 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
3
4 #Blur the grayscale image with median blur
5 gray_blur = cv2.medianBlur(gray, 3)
6
7 #Apply adaptive thresholding to detect edges
8 edges = cv2.adaptiveThreshold(gray_blur, 255, cv2.ADAPTIVE_THRESH_MEAN_
9
10 #Sharpen the image
11 color = cv2.detailEnhance(img, sigma_s=5, sigma_r=0.5)
12
13 #Merge the colors of same images using "edges" as a mask
14 cartoon = cv2.bitwise_and(color, color, mask=edges)

第一步与之前相同,我们需要将图像转换为灰度图像。

接下来,不使用高斯模糊,而是应用中值模糊。为此,我们使用OpenCV中的
medianBlur() 函数。中值模糊通过计算与内核重叠的像素值的中值,然后将其中
心像素替换为中值。但是,我们可以根据需要先使用高斯模糊。

接下来,我们需要检测图像的边缘。为此,将自适应阈值与OpenCV中的
adaptiveThreshold() 函数一起应用。自适应阈值的主要目标是根据内核重叠的
像素的平均值,将图像每个区域中的每个像素值转换为黑色或白色。

以下是自适应阈值对模糊图像的影响的可视化结果。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490152&idx=2&sn=852db92d636b04481b3680d6750d5ce3&chksm=fb56f8… 4/16
2020/9/30 基于OpenCV的图像卡通化

左:自适应阈值之前—右:自适应阈值之后

为了使图像看起来更清晰,我们可以使用OpenCV中的detailEnhance()函数。我
们需要指定两个参数:

• sigma_s:控制着邻域的大小,该邻域的大小将被加权以替换图像中的像素值。值
越高,邻域越大。这样可以使图像更平滑。

• sigma_r:如果要在平滑图像时保留边缘,这很重要。较小的值只会产生非常相似
的颜色进行平均(即平滑),而相差很大的颜色将保持不变。

最后,我们使用自适应阈值的结果作为掩码。然后,根据蒙版的值合并细节增强的结
果,以创建具有清晰边缘的清晰效果。

以下是“细节增强”过滤器的示例结果。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490152&idx=2&sn=852db92d636b04481b3680d6750d5ce3&chksm=fb56f8… 5/16
2020/9/30 基于OpenCV的图像卡通化

细节增强过滤器实现示例

双边过滤器
使用双边滤镜的一大优势是,我们可以在保留边缘的同时使图像和颜色平滑。以下是
通过双边过滤将您的图像转换为卡通图像的完整代码。

1 #convert the image into grayscale image


2 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
3
4 #Blur the grayscale image with median blur
5 gray_blur = cv2.medianBlur(gray, 3)
6
7 #Apply adaptive thresholding to detect edges
8 edges = cv2.adaptiveThreshold(gray_blur, 255, cv2.ADAPTIVE_THRESH_MEAN_
9
10 #Sharpen the image
11 color = cv2.detailEnhance(img, sigma_s=5, sigma_r=0.5)
12
13 #Merge the colors of same images using "edges" as a mask
14 cartoon = cv2.bitwise_and(color, color, mask=edges)

如果仔细看,所有步骤都与“细节增强”过滤器中的步骤相似,但是这次不是使用
detailEnhance() 函数,而是使用openCV中的bilateralFilter()函数。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490152&idx=2&sn=852db92d636b04481b3680d6750d5ce3&chksm=fb56f8… 6/16
2020/9/30 基于OpenCV的图像卡通化

调用此函数时需要传递的参数与detailEnhance()相同,只多一个附加参数,即内
核大小d。首先,我们指定图像源,然后是d,sigma_s和sigma_r值控制平滑效
果,并保持边缘。

以下是使用双边过滤器的结果示例。

双边过滤器实施示例

铅笔边缘滤波器
铅笔边缘滤镜可创建仅包含重要边缘和白色背景的新图像。要应用此滤波器,下面是
完整的代码。

1 #Convert the image into grayscale image


2 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
3
4 #Blur the grayscale image using median blur
5 gray = cv2.medianBlur(gray, 25)
6
7 #Detect edges with Laplacian
8 edges = cv2.Laplacian(gray, -1, ksize=3)
9
10 #Invert the edges
11 edges_inv = 255-edges
12
13 #Create a pencil edge sketch
14 dummy, cartoon = cv2.threshold(edges_inv, 150, 255, cv2.THRESH_BINARY)

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490152&idx=2&sn=852db92d636b04481b3680d6750d5ce3&chksm=fb56f8… 7/16
2020/9/30 基于OpenCV的图像卡通化

前两个步骤与其他过滤器相同。首先,我们将图像转换为灰度图像。接下来,我们使
用大小为25的内核对图像进行模糊处理。

接下来,我们应用拉普拉斯滤波器来检测边缘。根据内核的大小,拉普拉斯滤波器中
的值可以不同。

Laplacian滤波器的工作是,将通过对象内部的灰度级和图像背景强度来突出对象的边
缘。以下是拉普拉斯滤波器应用结果。

左:拉普拉斯滤波器应用之前—右:拉普拉斯滤波器应用之后

接下来,我们将Laplacian滤波器的结果求反。最后,通过应用openCV中的
threshold()函数,根据指定的阈值将灰度图像转换为全黑或全白。

以下是“铅笔边缘”过滤器的结果示例。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490152&idx=2&sn=852db92d636b04481b3680d6750d5ce3&chksm=fb56f8… 8/16
2020/9/30 基于OpenCV的图像卡通化

Pencil Edges滤镜实现示例

使用Streamlit 构建图像卡通化Web 应用程序


在创建了图像卡通化滤波器的代码之后,现在就可以创建图像卡通化Web应用程序
了。

第一步,需要将创建图像卡通化滤波器的所有代码放入一个函数中,以便于访问。到
目前为止,我们已经对每个参数值进行了硬编码,例如内核的大小等等。

现在,我们可以让用户使用滑块根据自己的喜好指定一个值,而不是对每个参数值进
行硬编码。为此,我们可以使用Streamlit中的streamlit.slider()函数。下面是
其实现的示例。

1 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)


2 scale_val = st.slider('Tune the brightness of your sketch (the higher th
3 kernel = st.slider('Tune the boldness of the edges of your sketch (the h
4 gray_blur = cv2.GaussianBlur(gray, (kernel, kernel), 0)
5 cartoon = cv2.divide(gray, gray_blur, scale= scale_val)

使用此滑块,可以创建一个交互式图像卡通化Web应用程序,就像Adobe Lightroom
一样。每次调整内核的值和其他参数时,图像卡通化的结果都会实时更改和更新。

我们可以将其应用到streamlit.slider()上,创建的每个图像卡通化滤波器,以
替换硬编码的参数值。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490152&idx=2&sn=852db92d636b04481b3680d6750d5ce3&chksm=fb56f8… 9/16
2020/9/30 基于OpenCV的图像卡通化

接下来,我们需要添加一个小插件,以便用户可以上传自己想要转换为卡通的图像。
为此,我们可以使用Streamlit中的streamlit.file_uploader()函数。要添加某
些文本到Web应用程序中,我们可以使用Streamlit 中的streamlit.text()或
streamlit.write()。

用户上传图像后,现在我们需要显示图像,使用图像卡通化滤波器之一编辑图像,并
显示卡通化图像,以便用户知道他们是否要进一步调整滑块。要显示图像,我们可以
使用Streamlit中的streamlit.image()函数。

以下是在不到100行代码的情况下如何构建图像卡通化Web应用程序的实现。

1 import cv2
2 import streamlit as st
3 import numpy as np
4 from PIL import Image
5
6
7 def cartoonization (img, cartoon):
8
9 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
10
11 if cartoon == "Pencil Sketch":
12
13 value = st.sidebar.slider('Tune the brightness of your sketch
14 kernel = st.sidebar.slider('Tune the boldness of the edges of y
15
16
17 gray_blur = cv2.GaussianBlur(gray, (kernel, kernel), 0)
18
19 cartoon = cv2.divide(gray, gray_blur, scale=value)
20
21 if cartoon == "Detail Enhancement":
22
23
24 smooth = st.sidebar.slider('Tune the smoothness level of the i
25 kernel = st.sidebar.slider('Tune the sharpness of the image (t
26 edge_preserve = st.sidebar.slider('Tune the color averaging eff
27
28 gray = cv2.medianBlur(gray, kernel)

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490152&idx=2&sn=852db92d636b04481b3680d6750d5ce3&chksm=fb56f… 10/16
2020/9/30 基于OpenCV的图像卡通化

29 edges = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_M


30 cv2.THRESH_BINARY, 9, 9)
31
32 color = cv2.detailEnhance(img, sigma_s=smooth, sigma_r=edge_pr
33 cartoon = cv2.bitwise_and(color, color, mask=edges)
34
35 if cartoon == "Pencil Edges":
36
37 kernel = st.sidebar.slider('Tune the sharpness of the sketch (t
38 laplacian_filter = st.sidebar.slider('Tune the edge detection
39 noise_reduction = st.sidebar.slider('Tune the noise effects of
40
41 gray = cv2.medianBlur(gray, kernel)
42 edges = cv2.Laplacian(gray, -1, ksize=laplacian_filter)
43
44
45 edges_inv = 255-edges
46
47 dummy, cartoon = cv2.threshold(edges_inv, noise_reduction, 255
48
49
50 if cartoon == "Bilateral Filter":
51
52
53
54 smooth = st.sidebar.slider('Tune the smoothness level of the i
55 kernel = st.sidebar.slider('Tune the sharpness of the image (t
56 edge_preserve = st.sidebar.slider('Tune the color averaging eff
57
58 gray = cv2.medianBlur(gray, kernel)
59 edges = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_M
60 cv2.THRESH_BINARY, 9, 9)
61
62 color = cv2.bilateralFilter(img, smooth, edge_preserve, smooth
63 cartoon = cv2.bitwise_and(color, color, mask=edges)
64
65 return cartoon
66
67 #######################################################################
68

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490152&idx=2&sn=852db92d636b04481b3680d6750d5ce3&chksm=fb56f… 11/16
2020/9/30 基于OpenCV的图像卡通化

69 st.write("""
70 # Cartoonize Your Image!
71
72 """
73 )
74
75 st.write("This is an app to turn your photos into cartoon")
76
77 file = st.sidebar.file_uploader("Please upload an image file", type=["
78
79 if file is None:
80 st.text("You haven't uploaded an image file")
81 else:
82 image = Image.open(file)
83 img = np.array(image)
84
85 option = st.sidebar.selectbox(
86 'Which cartoon filters would you like to apply?',
87 ('Pencil Sketch', 'Detail Enhancement', 'Pencil Edges', 'Bilateral
88
89 st.text("Your original image")
90 st.image(image, use_column_width=True)
91
92 st.text("Your cartoonized image")
93 cartoon = cartoonization(img, option)
94
95 st.image(cartoon, use_column_width=True)

现在,可以打开提示符,然后转到包含上面代码的Python文件的工作目录。接下来,
您需要使用以下命令运行代码。

1 streamlit run your_app_name.py

最后,您可以在本地计算机上使用图像卡通化Web应用程序!以下是该网络应用程序
的示例。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490152&idx=2&sn=852db92d636b04481b3680d6750d5ce3&chksm=fb56f… 12/16
2020/9/30 基于OpenCV的图像卡通化

该网络应用程序示例

部署Web 应用
本节是可选的,但是如果小伙伴需要部署Web应用程序以便其他人也可以访问您的
Web应用程序,则可以使用Heroku部署Web应用程序。

要将Web应用程序部署到Heroku,首先要免费创建一个Heroku帐户,然后下载
Heroku CLI。

接下来需要在与Python文件相同的目录中创建四个其他文件,它们是:

• requirements.txt:这是文本文件,用于告诉Heroku构建Web应用程序需要哪些
依赖项。因为在我们的web应用程序,我们使用四种不同的库:opencv,numpy,
Pillow,和streamlit,那么我们就可以写所有这些库及其版本为
requirements.txt的

1 opencv-python==4.3.0.36
2 streamlit==0.63.0
3 Pillow==7.0.0
4 numpy==1.18.1

• setup.sh:这是用于在Heroku上设置配置的文件。为此setup.sh文件编写以下内
容。

1 mkdir -p ~/.streamlit/
https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490152&idx=2&sn=852db92d636b04481b3680d6750d5ce3&chksm=fb56f… 13/16
2020/9/30 基于OpenCV的图像卡通化

2 echo "\
3 [server]\n\
4 headless = true\n\
5 port = $PORT\n\
6 enableCORS = false\n\
7 \n\
8 " > ~/.streamlit/config.toml

• Procfile:这是告诉Heroku哪些文件以及应如何执行文件的文件。为Procfile编写
以下内容。

1 web: sh setup.sh && streamlit run cartoon_app.py

• Aptfile:这是Heroku 构建包的文件,以使OpenCV能够在Heroku中运行。为
Aptfile编写以下内容。

1 libsm6
2 libxrender1
3 libfontconfig1
4 libice6

接下来,打开命令提示符,然后转到Python文件和这四个其他文件的工作目录。在其
中键入heroku login命令,以便您登录到Heroku帐户。

现在,您可以通过键入来创建新应用heroku create your-app-name。为确保您


位于新创建的应用程序内部,请键入以下内容:

1 heroku git:remote -a your-app-name

接下来,我们需要在新创建的应用程序中添加一个buildpack,以使OpenCV能够在
Heroku上运行。要添加必要的buildpack,请在Heroku CLI上键入以下内容。

1 heroku create --buildpack https://github.com/heroku/heroku-buildpack-apt

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490152&idx=2&sn=852db92d636b04481b3680d6750d5ce3&chksm=fb56f… 14/16
2020/9/30 基于OpenCV的图像卡通化

现在设置好了。接下来,您需要通过打字来初始化一个空的git git init其次是git


add .,git commit和git push heroku master命令。

1 git init
2 git add .
3 git commit -m "Add your messages"
4 git push heroku master

之后,部署过程就开始了,并且可能需要一些时间来等待此部署过程。最后,Heroku
将生成新部署的Web应用程序的URL。

就是这样!现在,我们已经构建了自己的图像卡通化Web应用程序,该应用程序模仿
了Adobe Lightroom的功能。

交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计
算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),
请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交
大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入
相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490152&idx=2&sn=852db92d636b04481b3680d6750d5ce3&chksm=fb56f… 15/16
2020/9/30 基于OpenCV的图像卡通化

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490152&idx=2&sn=852db92d636b04481b3680d6750d5ce3&chksm=fb56f… 16/16
2020/9/30 基于python和OpenCV构建智能停车系统

基于python和OpenCV构建智能停车系统
原创 努比 小白学视觉 8月8日

点击上方“小白学视觉”,选择加"星标"或“置顶”
重磅干货,第一时间送达

当今时代最令人头疼的事情就是找不到停车位,尤其是找20分钟还没有找到停车位。

根据复杂性和效率的不同,任何问题都具有一个或多个解决方案。目前智能停车系统
的解决方案,主要包括基于深度学习实现,以及基于重量传感器、光传感器实现等。

本期我们将一起通过使用摄像头和少量代码来实现最简单的智能停车系统。该解决方
案所使用的概念非常简单。它由具有以下两个脚本组成:

1. 选择停车位的坐标并将其保存到文件中。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490083&idx=1&sn=a572e2dd8f1cc444936b93613d6a6f7e&chksm=fb56f8cf… 1/10
2020/9/30 基于python和OpenCV构建智能停车系统

2. 从文件中获取坐标,并确定该点是否可用。

将该解决方案分成两个脚本的原因是,避免在每次确定是否有可用停车位的时候,就
进行停车位的选择。

为 了 使 这 一 过 程 尽 可 能 简 单 , 从 现 在 开 始 , 我 们 将 这 两 个 脚 本 称 为 selector 和
detector。

相关依赖

在本文中,我们使用python 3.7.6,但其他版本(例如3.6或3.8)当然也可以使用。首
先我们要检查python的版本,我们通过在控制台中编写python –version,即可返
回已安装的python版本。

1 C:\Users\Razvan>python --version
2 Python 3.7.6

在开始构建该系统依赖项之前,我们可以设置一个虚拟环境。通过以下链接我们可以
了解更多有关虚拟环境的信息https://docs.python.org/3.7/tutorial/venv.html。

也 可 以 使 用 conda 创 建 和 管 理 环 境 。 有 关 更 多 信 息 见
https://docs.anaconda.com/anaconda/。

在python中设置完所有内容后, 最重要的依赖关系将是OpenCV库。通过pip将其添
加到虚拟环境中,可以运行pip install opencv-python。

要检查所有设置是否正确,我们可以使用以下cv2.__version__命令打印环境中可
用的当前OpenCV版本。

1 (OpenCV) C:\Users\Razvan>python
2 Python 3.7.6 (default, Jan 8 2020, 20:23:39) [MSC v.1916 64 bit (AMD64
3 Type "help", "copyright", "credits" or "license" for more information.
4 >>> import cv2
5 >>> print(cv2.__version__)
6 4.2.0
7 >>>

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490083&idx=1&sn=a572e2dd8f1cc444936b93613d6a6f7e&chksm=fb56f8cf… 2/10
2020/9/30 基于python和OpenCV构建智能停车系统

在第一行中,我们可以看到在该项目中使用了名为OpenCV的虚拟环境。

步骤

首先,我们需要安装一个停车场摄像头。由于我们并没有一个窗户可以看到的任何停
车场,因此我们选择使用旧汽车玩具和印刷纸。另外,我在停车场上方设置了一个网
络摄像头,以获取良好的图像,因此我们正在处理的图像如下所示:

selector选择器

接下来,我们来介绍编码部分。首先,我们需要构建选择器。我们从导入所需模块开

1 import cv2
2 import csv

之后,我们开始获取图像,在该图像上选择停车位。为此,我们可以选择摄网络摄像
头提供的第一帧,保存并使用该图像选择停车位。下面的代码是这样的:

1. 打开image变量中的视频流;suc确定流是否成功打开。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490083&idx=1&sn=a572e2dd8f1cc444936b93613d6a6f7e&chksm=fb56f8cf… 3/10
2020/9/30 基于python和OpenCV构建智能停车系统

2. 将第一帧写入frame0.jpg。

3. 流被释放,所有窗口都关闭。

4. 新保存的图片将以img变量形式读取。

1 VIDEO_SOURCE = 1
2
3 cap = cv2.VideoCapture(VIDEO_SOURCE)
4 suc, image = cap.read()
5 cv2.imwrite("frame0.jpg", image)
6 cap.release()
7 cv2.destroyAllWindows()
8 img = cv2.imread("frame0.jpg")

现在,我们已经保存了第一帧并在img变量中将其打开,可以使用selectROIs函数标
记停车位。ROI被定义为感兴趣的区域,代表图像的一部分,我们将在其上应用不同
的函数以及滤波器来获取结果。

返回到selectROIs函数,这将返回一个列表(类型:numpy.ndarray),其中包
含我们组装图像所需的数字及其边界ROI。

1 r = cv2.selectROIs('Selector', img, showCrosshair = False, fromCenter =

我们的列表将保存在变量r中。赋予cv2.selectROIs函数的参数如下:

1. “选择器”是允许我们选择投资回报率的窗口的名称。

2. img是包含我们要选择图像的变量。

3. showCrosshair = Flase删除选区内部的中心线。可以将其设置为True,因为对
结果没有影响。

4. fromCenter = False是一个非常重要的参数,因为如果将其设置为True,则正
确的选择会困难得多。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490083&idx=1&sn=a572e2dd8f1cc444936b93613d6a6f7e&chksm=fb56f8cf… 4/10
2020/9/30 基于python和OpenCV构建智能停车系统

选择所有停车位之后,是时候将它们写入.csv文件了。为此,我们需要将r变量转换为
python列表,可以使用rlist = r.tolist()命令实现。

拥有适当的数据后,我们将其保存到.csv文件中,以备将来使用。

1 with open('data/rois.csv', 'w', newline='') as outf:


2 csvw = csv.writer(outf)
3 csvw.writerows(rlist)

detector探测器

现在我们已经选择了停车位,是时候进行一些图像处理了。解决这个问题的方法如
下:

1. 从.csv文件获取坐标。

2. 从中构建新图像。

3. 应用OpenCV中可用的Canny函数。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490083&idx=1&sn=a572e2dd8f1cc444936b93613d6a6f7e&chksm=fb56f8cf… 5/10
2020/9/30 基于python和OpenCV构建智能停车系统

4. 计算新图像内的白色像素。

5. 建立一个点内的像素范围将被占用。

6. 在实时供稿上绘制一个红色或绿色矩形。

对于所有这些操作,我们需要定义一个要应用于每个位置的函数。该函数如下所示:

1 def drawRectangle(img, a, b, c, d):


2 sub_img = img[b:b + d, a:a + c]
3 edges = cv2.Canny(sub_img, lowThreshold, highThreshold)
4 pix = cv2.countNonZero(edges)
5
6 if pix in range(min, max):
7 cv2.rectangle(img, (a, b), (a + c, b + d), (0, 255, 0), 3)
8 spots.loc += 1
9 else:
10 cv2.rectangle(img, (a, b), (a + c, b + d), (0, 0, 255), 3)

现在我们已经实现了所需的功能,如果我们直接将其应用于.csv文件中的每组坐标效果
可能并不好。因此我们做如下处理

首先,我们的有一些参数如果可以在脚本运行时(也可以在通过GUI)实时调整它
们,那就更好了。为此,我们需要构建一些轨迹栏。OpenCV为我们提供这项功能。

我们需要一个回调函数,该函数不执行任何操作,但作为使用OpenCV创建轨迹栏的参
数是必需的。实际上,回调参数具有明确定义的用途,但我们在此不使用它。要了解
有关此内容的更多信息,查阅OpenCV文档。

1 def callback(foo):
2 pass

现 在 我 们 需 要 创 建 轨 迹 栏 。 我 们 将 使 用 cv2.namedWindow 和
cv2.createTrackbar功能。

1 cv2.namedWindow('parameters')
2 cv2.createTrackbar('Threshold1', 'parameters', 186, 700, callback)
3 cv2.createTrackbar('Threshold2', 'parameters', 122, 700, callback)
https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490083&idx=1&sn=a572e2dd8f1cc444936b93613d6a6f7e&chksm=fb56f8cf… 6/10
2020/9/30 基于python和OpenCV构建智能停车系统

4 cv2.createTrackbar('Min pixels', 'parameters', 100, 1500, callback)


5 cv2.createTrackbar('Max pixels', 'parameters', 323, 1500, callback)

现在,我们已经创建了用于操作参数的GUI,只剩下一件事了。这就是图像中可用斑
点的数量。在drawRectangle中定义为spot.loc。这是一个静态变量,必须在程序
开始时进行定义。该变量为静态变量的原因是,我们希望调用的每个
drawRectangle函数都将其写入相同的全局变量,而不是每个函数都使用一个单独
的变量。这样可以防止返回的可用空间数量大于实际的可用空间数量。

为了实现这一点,我们只需要使用它的loc静态变量创建spots类。

1 class spots:
2 loc = 0

现在我们已经准备就绪,只需要从.csv文件中获取数据,将其所有数据转换为整数,然
后在无限循环中应用构建的函数即可。

1 with open('data/rois.csv', 'r', newline='') as inf:


2 csvr = csv.reader(inf)
3 rois = list(csvr)
4
5 rois = [[int(float(j)) for j in i] for i in rois]
6 VIDEO_SOURCE = 1
7 cap = cv2.VideoCapture(VIDEO_SOURCE)
8
9 while True:
10 spots.loc = 0
11
12 ret, frame = cap.read()
13 ret2, frame2 = cap.read()
14 min = cv2.getTrackbarPos('Min pixels', 'parameters')
15 max = cv2.getTrackbarPos('Max pixels', 'parameters')
16 lowThreshold = cv2.getTrackbarPos('Threshold1', 'parameters')
17 highThreshold = cv2.getTrackbarPos('Threshold2', 'parameters')
18
19 for i in range(len(rois)):
20 drawRectangle(frame, rois[i][0], rois[i][1], rois[i][2], rois[
21
https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490083&idx=1&sn=a572e2dd8f1cc444936b93613d6a6f7e&chksm=fb56f8cf… 7/10
2020/9/30 基于python和OpenCV构建智能停车系统

22 font = cv2.FONT_HERSHEY_SIMPLEX
23 cv2.putText(frame, 'Available spots: ' + str(spots.loc), (10, 30),
24 cv2.imshow('Detector', frame)
25
26 canny = cv2.Canny(frame2, lowThreshold, highThreshold)
27 cv2.imshow('canny', canny)
28
29 if cv2.waitKey(1) & 0xFF == ord('q'):
30 break
31
32 cap.release()
33 cv2.destroyAllWindows()

拓展

在我们的循环中实际上只是调用的构建函数要复杂一点。

首先,我们将空间的数量初始化为0,以防止每帧添加数字。

其次,我们进入两个处理流以显示真实图像和已处理的图像。这有助于更好地了解此
脚本的工作方式以及图像的处理方式。

然 后 , 我 们 需 要 在 每 次 迭 代 中 获 取 我 们 创 建 的 参 数 GUI 中 的 参 数 值 。 这 是 通 过
cv2.getTrackbarPos功能完成的。

接下来最重要的部分,将drawRectangle函数应用到Selector脚本获取的所有坐标
上。

最后,在结果图像上写下可用斑点的数量,显示Canny函数的结果,显然,这是一种
众所周知的停止循环的方法。

我们现在便完成了一个智能停车项目!

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490083&idx=1&sn=a572e2dd8f1cc444936b93613d6a6f7e&chksm=fb56f8cf… 8/10
2020/9/30 基于python和OpenCV构建智能停车系统

总结

如今,智能停车已成为热门话题之一,并且有许多实现方式可以导致良好的功能系
统。我们这处理方法并不是完美的,有许多方法可以更好地优化结果,并且可以在更
多情况下使用。但是,即使这不能解决停车场危机,也可能是导致危机 的主要原因。

交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计
算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),
请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交
大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入
相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490083&idx=1&sn=a572e2dd8f1cc444936b93613d6a6f7e&chksm=fb56f8cf… 9/10
2020/9/30 基于python和OpenCV构建智能停车系统

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490083&idx=1&sn=a572e2dd8f1cc444936b93613d6a6f7e&chksm=fb56f8… 10/10
2020/9/30 基于深度学习OpenCV与python进行字符识别

基于深度学习OpenCV与python进行字符识别
原创 努比 小白学视觉 1周前

点击上方“小白学视觉”,选择加"星标"或“置顶”
重磅干货,第一时间送达

当我们在处理图像数据集时,总是会想有没有什么办法以简单的文本格式检索图像中
的这些字符呢?

今天我们就一起看看有没有什么简单的办法可以实现这一功能~
对于字符识别,我们找到了一些在线工具可以使用,他们将获取用户的输入并提供输
出信息。

字符识别:
字符识别程序有助于准确的从文本中识别出每个文本元素。

通过应用深度学习算法,可以准确的识别图像中字符或文本元素的并对其进行分类。
这些字符一般有很大区别。

当我们使用普通技术来识别字符时,可能会在特定点上出现一些错误。如果我们使用
基于深度学习的OpenCV算法将给出有效的输出。

对于运行模型的必须安装由Google作为光学字符识别引擎开发的tesseract。
pip install pytesseract

OpenCV :
OpenCV是一种一项基本技术,我们主要利用他来消除的噪声以便进一步执行数据操
作。

Open CV是深度学习技术领域中使用最广泛的算法。

它极大地依赖于受过训练的数据,并有助于识别图像中存在的文本。开放式简历使算
法丢失的准确性变成一幅图画。灰度等级在字符识别领域提供了有效的分类。因此,
我们特此导入所有必要的软件包,以使我们的模型可以正常使用。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247491080&idx=2&sn=768798062fb6af418fa2e1426a89846a&chksm=fb56fce4c… 1/6
2020/9/30 基于深度学习OpenCV与python进行字符识别

为了在您的本地计算机上安装OpenCV,请使用以下命令...
pip install opencv-python

图像可以直接读取到代码中吗?
• 让我们讨论影响识别字符的因素:

• 图像中的噪声会导致许多错误识别字符的因素。为了确保无噪音,我们在代码中消除
了识别。

• 当图像不是高分辨率时,识别将失败。因此,为了获得准确的结果,最好拍摄高分辨
率的图像。

• 有时图像的角度也会出现缺陷。

• 在假定文本时,图像的反射会导致错误。如此多的字母“ F”被识别为“ P”。

• 如果代码无法训练字体,字体也会改变结果。

• 各种图像具有本领域的各种表示风格,因此,当存在更多肤色或多种颜色时,会对图
像中的识别文本做出不正确的假设。

考虑到以上所有因素,必须在所有测试用例通过的地方相应地构建代码。

使用OpenCV 识别字符的Python 代码:


导入所有软件包:

1 #import all the packages


2 import cv2
3 import numpy as np
4 import pytesseract
5 from PIL import Image

使用软件包安装后,将其导入代码。
声明或初始化路径:
tesseract是一种开源工具,可以从网上下载。下载后,请提及其路径,如下所示。

1 # path of pytesseract execution folder


2 pytesseract.pytesseract.tesseract_cmd = 'C:\Program Files\Tesseract-OCR\
3 main_path = r'qu12.png'

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247491080&idx=2&sn=768798062fb6af418fa2e1426a89846a&chksm=fb56fce4c… 2/6
2020/9/30 基于深度学习OpenCV与python进行字符识别

主要方法:
在这部分代码中,我们正在实现
• 读取图像
• 灰度图像
• 进行膨胀和腐蚀以消除图像中不需要的噪声。
• 去除噪点后写图像。
• 应用阈值以获取唯一的黑白图片。
• 写入相同的图像以进行进一步的识别过程。
• 使用Tesseract进行字符识别。

1 def get_string(pic_path):
2 # Reading picture with opencv
3 pic = cv2.imread(pic_path)# grey-scale the picture
4 pic = cv2.cvtColor(pic, cv2.COLOR_BGR2GRAY)# Do dilation and erosio
5 kernel = np.ones((1, 1), np.uint8)
6 pic = cv2.dilate(pic, kernel, iterations=20)
7 pic = cv2.erode(pic, kernel, iterations=20)# Write image after remov
8 cv2.imwrite(main_path + "no_noise.png", pic)# threshold applying t
9 pic = cv2.adaptiveThreshold(pic, 300, cv2.ADAPTIVE_THRESH_GAUSSIAN_
10 cv2.imwrite(main_path + "threshold.png", pic)# Character recognitio
11 final = pytesseract.image_to_string(Image.open(main_path + "thresho

显示最终输出:
print(get_string(src_path))

输入和输出:

在这里,我们给出输入到代码中的输入和输出,以便稍后执行代码。

输入:

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247491080&idx=2&sn=768798062fb6af418fa2e1426a89846a&chksm=fb56fce4c… 3/6
2020/9/30 基于深度学习OpenCV与python进行字符识别

从互联网上获取样本识别

输出:

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247491080&idx=2&sn=768798062fb6af418fa2e1426a89846a&chksm=fb56fce4c… 4/6
2020/9/30 基于深度学习OpenCV与python进行字符识别

执行代码后的屏幕截图

代码链接 : https : //github.com/eazyciphers/deep-learning-tutors

参考文献:
https://www.researchgate.net/profile/Andrew_Agbemenu/publication/325223548_An_
Automatic_Number_Plate_Recognition_System_using_OpenCV_and_Tesseract_OCR_En
gine/links/5c87e7ea299bf14e7e781750/An-Automatic-Number-Plate-Recognition-
System-using-OpenCV-and-Tesseract-OCR-Engine.pdf
https://en.wikipedia.org/wiki/Tesseract_(software)

交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计
算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),
请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交
大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入
相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247491080&idx=2&sn=768798062fb6af418fa2e1426a89846a&chksm=fb56fce4c… 5/6
2020/9/30 基于深度学习OpenCV与python进行字符识别

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247491080&idx=2&sn=768798062fb6af418fa2e1426a89846a&chksm=fb56fce4c… 6/6
2020/7/29 基于自适应显着性的图像分割(源码开放)

基于自适应显着性的图像分割(源码开放)
小白 小白学视觉 4月13日

点击上方“小白学视觉”,选择“星标”公众号
重磅干货,第一时间送达

本文介绍算法的源码在github上给出
https://github.com/TimChinenov/GraspPicture

前言

成产品及系统平台的现场演示,编写技术应用服务方案等,编写投标类方案文件及标书的制作;
通常,当我们看到一张图片时,会在图片中聚焦一个焦点。这个可能是一个人,一座建筑物甚至是一个桶。
其他没有聚焦区域虽然很清晰,但是却由于颜色单调或者纹理较为平滑而很少引起关注。当遇到此类图象
时,我们希望从图像中分割感兴趣的对象。下面给出了显着图像的示例,本文探讨了此类显着图像的分割方
法,也称为显着性的图像分割。

显着图像的示例。桶(左)和人(右)是感兴趣的对象

这中分割方式最开始起源于希望能够自主寻找图像中的Trimap。Trimap是图像掩码(mask),当与掩码
算法配合使用时,可用于分割图像,同时能够提示前景和背景之间的细节。Trimap通常包含定义前景的白
色区域,定义背景的黑色区域以及代表不确定区域的灰色区域。具体形式如下图所示。

Trimap示例

大部分抠图算法问题在于,他们希望Trimap由用户提供,这是一项非常耗时的任务。这里面介绍两个试图
解决自主trimap生成问题的相关论文,这两篇论文在文末给出。在第一篇论文中使用了一种相当简单且易于
实现的方法。不幸的是,他们的方法并不是完全自主的,因为它要求用户为Grabcut算法提供一个矩形区

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247487350&idx=1&sn=c9e8440347e3f28ed6d4b4a92c3e4c60&chksm=fb56ed9… 1/6
2020/7/29 基于自适应显着性的图像分割(源码开放)

域。第二篇论文中,使用显着性方法预测感兴趣的区域。但是,它们的显着性方法非常复杂,将三种不同的
显着性算法的结果结合在一起。这三种算法中有一种利用卷积神经网络,为了易于实现,应该尽量避免这种
技术。

如果忽略需要人为给出矩形区域,第一篇论文中能够产生较好的分割结果。通过第二篇论文的原理去自动给
出一个Grabcut算法的矩形区域,那么将完美的解决自主分割的问题。

方法

对于大多数形式的图像分割,目标都是将图像二值化为感兴趣的区域。这个本文介绍方法的目标也是这样
的。首先,大致确定感兴趣的对象在哪里。将高斯模糊应用于图像,之后在模糊图像中生成平均15像素大小
的超像素。超像素算法旨在根据像素区域中值的颜色和距离来分解图像。具体来说,使用了简单的线性迭代
聚类(SLIC)算法。具体形式如下图所示。

一个桶和一个人的超像素划分结果

超像素将图像分解为大致相同的区域。这样的一个优点是,超像素允许区域的泛化。我们可以假设超像素内
的大多数像素具有相似的属性。

在确定图像中的超像素的同时,计算图像的显着性图。使用了两种不同的显着性技术。第一种方法使用
OpenCV内置的方法,即所谓的细颗粒显着性。第二种方法涉及获取细颗粒显着性图像的平均值,然后从图
像的高斯模糊版本中减去平均值,然后是新图像的绝对值。

下方的图像均突出显示了感兴趣的区域。细颗粒显着性产生的图像较为柔和。此外,细颗粒显着性图像主要
勾勒出突出图像的边界。而另一种方法虽然也捕获了突出图像的内部,但是与细颗粒方法相比,该方法会产
生更多的噪音。之后需要对噪声进行去除。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247487350&idx=1&sn=c9e8440347e3f28ed6d4b4a92c3e4c60&chksm=fb56ed9… 2/6
2020/7/29 基于自适应显着性的图像分割(源码开放)

第一种显着性结果

第二种显着性结果

为了将图像二值化,对从彩色图像生成的每个超级像素进行迭代。如果显着图像内该超像素区域的中值像素
值大于阈值T1,则整个超像素将被二值化为白色。否则,整个超像素将保留为黑色。T1由用户选择,一般情
况下,将T1设置为显着图像中最大像素值的25%-30%。

在对图像进行二值化之后,基于所使用的显着性技术对图像进行扩张。在第一种方法中,将图像放大为平均
超像素尺寸的两倍。在第二种方法中没有进行扩大,因为图像中存在的较大噪声使扩张风险增大。处理的结
果在下面给出。

最后一步操作取决于使用的是哪种显着性。在这两种方法的结果中,都提取最大的白色像素区域。通过查找
图像中的轮廓并选择面积最大的轮廓来执行此操作,之后将边界框拟合到所选区域。

根据一般性结果,第一种显着性方法通常会导致区域碎片化。生成边界框后,将落入该框的不属于最大区域
的所有其他白色区域添加到该框。框的边界增加到包括这些区域。第二种显着性方法不需要这样做。通常,

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247487350&idx=1&sn=c9e8440347e3f28ed6d4b4a92c3e4c60&chksm=fb56ed9… 3/6
2020/7/29 基于自适应显着性的图像分割(源码开放)

最大获取的区域会超出期望的数量。

最后一步是将最终找到的边界框提供给Grabcut算法。Grabcut是用于分割图像的常用方法,该方法会将绝
对是背景和前景的内容分开。这里面我们直接使用OpenCV的内置Grabcut函数。处理的结果如下所示。

结果

两种显着性计算方法对于结果会有一些影响。第一种显着性方法更加适用于含有噪声的图像中,在含有噪声
的图像中不会像第二种显着性方法造成分割结果的溢出。,但是如果图像太长或有卷须,则这些部分通常会
与图像的其余部分断开连接。

下面是这两种方法分割更多图像的示例结果。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247487350&idx=1&sn=c9e8440347e3f28ed6d4b4a92c3e4c60&chksm=fb56ed9… 4/6
2020/7/29 基于自适应显着性的图像分割(源码开放)

参考文献:

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247487350&idx=1&sn=c9e8440347e3f28ed6d4b4a92c3e4c60&chksm=fb56ed9… 5/6
2020/7/29 基于自适应显着性的图像分割(源码开放)

[1] C. Hsieh and M. Lee, “Automatic trimap generation for digital image matting,” 2013 Asia-Pacific Signal and

Information Processing Association Annual Summit and Conference, Kaohsiung, 2013, pp. 1–5.

[2] Gupta, Vikas & Raman, Shanmuganathan. (2017). Automatic Trimap Generation for Image Matting.

原文链接:https://towardsdatascience.com/saliency-based-image-segmentation-473b4cb31774
作者:Tim Chin

//
//

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247487350&idx=1&sn=c9e8440347e3f28ed6d4b4a92c3e4c60&chksm=fb56ed9… 6/6
2020/9/30 使用OpenCV对运动员的姿势进行检测

使用OpenCV对运动员的姿势进行检测
原创 小白 小白学视觉 8月30日

点击上方“小白学视觉”,选择加"星标"或“置顶”
重磅干货,第一时间送达

如今,体育运动的热潮日益流行。同样,以不正确的方式进行运动的风险也在增加。
有时可能会导致严重的伤害。考虑到这些原因,提出一种以分析运动员的关节运动,
来帮助运动员纠正姿势的解决方案。

人体姿势估计是计算机视觉领域的重要问题。它的算法有助于定位手腕,脚踝,膝盖
等部位。这样做是为了使用深度学习和卷积神经网络的概念提供个性化的运动训练体
验。特别是对于体育活动而言,训练质量在很大程度上取决于图像或视频序列中人体
姿势的正确性。

从图像或视频序列中检测运动员的姿势

数据集
正确选择数据集以对结果产生适当影响也是非常必要的。在此姿势检测中,模型在两
个不同的数据集即COCO关键点数据集和MPII人类姿势数据集上进行了预训练。

1. COCO:COCO关键点数据集是一个多人2D姿势估计数据集,其中包含从Flickr收
集的图像。迄今为止,COCO是最大的2D姿势估计数据集,并被视为测试2D姿势估计
算法的基准。COCO模型有18种分类。COCO输出格式:鼻子— 0,脖子—1,右肩
—2,右肘—3,右手腕—4,左肩—5,左手肘—6,左手腕—7,右臀部—8,右膝盖
—9,右脚踝—10,左臀部—11,左膝—12,左脚踝—13,右眼—14,左眼—15,
右耳—16,左耳—17,背景—18

2. MPII:MPII人体姿势数据集是一个多人2D姿势估计数据集,包含从Youtube视频
中收集的近500种不同的人类活动。MPII是第一个包含各种姿势范围的数据集,也是
第一个在2014年发起2D姿势估计挑战的数据集。MPII模型输出15分。MPII输出格
式:头—0,脖子—1,右肩—2,右肘—3,右腕—4,左肩—5,左肘—6,左腕—
https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490621&idx=1&sn=d369e6052cd41f31a45e593dbd6b05ba&chksm=fb56fed1… 1/7
2020/9/30 使用OpenCV对运动员的姿势进行检测

7,右臀部—8,右膝盖—9,右脚踝—10,左臀部—11,左膝盖—12 , 左 脚 踝 —
13,胸部—14,背景—15

这些点是在对数据集进行处理并通过卷积神经网络(CNN)进行全面训练时生成的。

具体步骤
步骤1:需求收集(模型权重)和负载网络
训练有素的模型需要加载到OpenCV中。这些模型在Caffe深度学习框架上进行了训
练。Caffe模型包含两个文件,即.prototxt文件和.caffemodel文件。
1. .prototxt文件指定了神经网络的体系结构。
2. .caffemodel文件存储训练后的模型的权重。
然后我们将这两个文件加载到网络中。

1 # Specify the paths for the 2 files


2 protoFile = "pose/mpi/pose_deploy_linevec_faster_4_stages.prototxt"
3 weightsFile = "pose/mpi/pose_iter_160000.caffemodel"
4 # Read the network into Memory
5 net = cv2.dnn.readNetFromCaffe(protoFile, weightsFile)

步骤2:读取图像并准备输入网络
首先,我们需要使用blobFromImage函数将图像从OpenCV格式转换为Caffe blob
格式,以便可以将其作为输入输入到网络。这些参数将在blobFromImage函数中提
供。由于OpenCV和Caffe都使用BGR格式,因此无需交换R和B通道。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490621&idx=1&sn=d369e6052cd41f31a45e593dbd6b05ba&chksm=fb56fed1… 2/7
2020/9/30 使用OpenCV对运动员的姿势进行检测

1 # Read image
2 frame = cv2.imread("image.jpg")
3 # Specify the input image dimensions
4 inWidth = 368
5 inHeight = 368
6 # Prepare the frame to be fed to the network
7 inpBlob = cv2.dnn.blobFromImage(frame, 1.0 / 255, (inWidth, inHeight),
8 # Set the prepared object as the input blob of the network
9 net.setInput(inpBlob)

步骤3:做出预测并解析关键点
一旦将图像传递到模型,就可以使用OpenCV中DNN类的正向方法进行预测,该方法
通过网络进行正向传递,这只是说它正在进行预测的另一种方式。

1 output = net.forward()

输出为4D矩阵:
1. 第一个维度是图片ID(如果您将多个图片传递到网络)。
2. 第二个维度指示关键点的索引。该模型会生成置信度图(在图像上的概率分布,
表示每个像素处关节位置的置信度)和所有已连接的零件亲和度图。对于COCO
模型,它由57个部分组成-18个关键点置信度图+ 1个背景+ 19 * 2个部分亲和度
图。同样,对于MPI,它会产生44点。我们将仅使用与关键点相对应的前几个
点。
3. 第三维是输出图的高度。
4. 第四个维度是输出图的宽度。
然后,我们检查图像中是否存在每个关键点。我们通过找到关键点的置信度图的最大
值来获得关键点的位置。我们还使用阈值来减少错误检测。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490621&idx=1&sn=d369e6052cd41f31a45e593dbd6b05ba&chksm=fb56fed1… 3/7
2020/9/30 使用OpenCV对运动员的姿势进行检测

置信度图
一旦检测到关键点,我们便将其绘制在图像上。

1 H = out.shape[2]
2 W = out.shape[3]
3 # Empty list to store the detected keypoints
4 points = []
5 for i in range(len()):
6 # confidence map of corresponding body's part.
7 probMap = output[0, i, :, :]
8 # Find global maxima of the probMap.
9 minVal, prob, minLoc, point = cv2.minMaxLoc(probMap)
10 # Scale the point to fit on the original image
11 x = (frameWidth * point[0]) / W
12 y = (frameHeight * point[1]) / H
13 if prob &amp;amp;gt; threshold :
14 cv2.circle(frame, (int(x), int(y)), 15, (0, 255, 255), thickne
15 cv2.putText(frame, "{}".format(i), (int(x), int(y)), cv2.FONT_
16 # Add the point to the list if the probability is greater than the thr
17 points.append((int(x), int(y)))
18 else :
19 points.append(None)

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490621&idx=1&sn=d369e6052cd41f31a45e593dbd6b05ba&chksm=fb56fed1… 4/7
2020/9/30 使用OpenCV对运动员的姿势进行检测

20 cv2.imshow("Output-Keypoints",frame)
21 cv2.waitKey(0)
22 cv2.destroyAllWindows()

步骤4:绘制骨架
由于我们已经绘制了关键点,因此我们现在只需将两对连接即可绘制骨架。

1 for pair in POSE_PAIRS:


2 partA = pair[0]
3 partB = pair[1]
4 if points[partA] and points[partB]:
5 cv2.line(frameCopy, points[partA], points[partB], (0, 255, 0), 3

结果

上面显示的输出向我们显示了运动员在特定时刻的准确姿势。下面是视频的检测结
果。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490621&idx=1&sn=d369e6052cd41f31a45e593dbd6b05ba&chksm=fb56fed1… 5/7
2020/9/30 使用OpenCV对运动员的姿势进行检测

项目源码:https://github.com/ManaliSeth/Athlete-Pose-Detection

交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计
算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),
请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交
大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入
相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490621&idx=1&sn=d369e6052cd41f31a45e593dbd6b05ba&chksm=fb56fed1… 6/7
2020/9/30 使用OpenCV对运动员的姿势进行检测

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490621&idx=1&sn=d369e6052cd41f31a45e593dbd6b05ba&chksm=fb56fed1… 7/7
2020/7/29 使用OpenCV实现道路车辆计数

使用OpenCV实现道路车辆计数
原创 努比 小白学视觉 7月10日

来自专辑

OpenCV应用

点击上方“小白学视觉”,选择加"星标"或“置顶”

重磅干货,第一时间送达

今天,我们将一起探讨如何基于计算机视觉实现道路交通计数。
在本教程中,我们将仅使用Python和OpenCV,并借助背景减除算法非常简单地进行运动检测。

我们将从以下四个方面进行介绍:
1. 用于物体检测的背景减法算法主要思想。
2. OpenCV图像过滤器。
3. 利用轮廓检测物体。
4. 建立进一步数据处理的结构。

背景扣除算法

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488820&idx=2&sn=4769e0a1fc3bdd1a4abe498e2dfa0e16&chksm=fb56f7d… 1/18
2020/7/29 使用OpenCV实现道路车辆计数

有许多不同的背景扣除算法,但是它们的主要思想都很简单。
假设有一个房间的视频,在某些帧上没有人和宠物,那么此时的视频基本为静态的,我们将其称为背
景(background_layer)。因此要获取在视频上移动的对象,我们只需要:用当前帧减去背景即
可。

由于光照变化,人为移动物体,或者始终存在移动的人和宠物,我们将无法获得静态帧。在这种情况
下,我们从视频中选出一些图像帧,如果绝大多数图像帧中都具有某个相同的像素点,则此将像素作
为background_layer中的一部分。

我们将使用MOG算法进行背景扣除

原始帧

代码如下所示:

1 import os
2 import logging
3 import logging.handlers
4 import random
5
6 import numpy as np
7 import skvideo.io
8 import cv2
9 import matplotlib.pyplot as plt
10
11 import utils
12 # without this some strange errors happen

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488820&idx=2&sn=4769e0a1fc3bdd1a4abe498e2dfa0e16&chksm=fb56f7d… 2/18
2020/7/29 使用OpenCV实现道路车辆计数

13 cv2.ocl.setUseOpenCL(False)
14 random.seed(123)
15
16 # ============================================================================
17 IMAGE_DIR = "./out"
18 VIDEO_SOURCE = "input.mp4"
19 SHAPE = (720, 1280) # HxW
20 # ============================================================================
21
22 def train_bg_subtractor(inst, cap, num=500):
23 '''
24 BG substractor need process some amount of frames to start giving result
25 '''
26 print ('Training BG Subtractor...')
27 i = 0
28 for frame in cap:
29 inst.apply(frame, None, 0.001)
30 i += 1
31 if i >= num:
32 return cap
33
34 def main():
35 log = logging.getLogger("main")
36
37 # creting MOG bg subtractor with 500 frames in cache
38 # and shadow detction
39 bg_subtractor = cv2.createBackgroundSubtractorMOG2(
40 history=500, detectShadows=True)
41
42 # Set up image source
43 # You can use also CV2, for some reason it not working for me
44 cap = skvideo.io.vreader(VIDEO_SOURCE)
45
46 # skipping 500 frames to train bg subtractor
47 train_bg_subtractor(bg_subtractor, cap, num=500)
48
49 frame_number = -1
50 for frame in cap:
51 if not frame.any():
52 log.error("Frame capture failed, stopping...")
53 break
54

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488820&idx=2&sn=4769e0a1fc3bdd1a4abe498e2dfa0e16&chksm=fb56f7d… 3/18
2020/7/29 使用OpenCV实现道路车辆计数

55 frame_number += 1
56 utils.save_frame(frame, "./out/frame_%04d.png" % frame_number)
57 fg_mask = bg_subtractor.apply(frame, None, 0.001)
58 utils.save_frame(frame, "./out/fg_mask_%04d.png" % frame_number)
59 # ============================================================================
60
61 if __name__ == "__main__":
62 log = utils.init_logging()
63
64 if not os.path.exists(IMAGE_DIR):
65 log.debug("Creating image directory `%s`...", IMAGE_DIR)
66 os.makedirs(IMAGE_DIR)
67
68 main()

处理后得到下面的前景图像

去除背景后的前景图像

我们可以看出前景图像上有一些噪音,可以通过标准滤波技术可以将其消除。

滤波

针 对 我 们 现 在 的 情 况 , 我 们 将 需 要 以 下 滤 波 函 数 : Threshold 、 Erode 、 Dilate 、 Opening 、


Closing。

首先,我们使用“Closing”来移除区域中的间隙,然后使用“Opening”来移除个别独立的像素点,然
后使用“Dilate”进行扩张以使对象变粗。代码如下:

1 def filter_mask(img):
2 kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (2, 2))
3 # Fill any small holes
4 closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
5 # Remove noise

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488820&idx=2&sn=4769e0a1fc3bdd1a4abe498e2dfa0e16&chksm=fb56f7d… 4/18
2020/7/29 使用OpenCV实现道路车辆计数

6 opening = cv2.morphologyEx(closing, cv2.MORPH_OPEN, kernel)


7 # Dilate to merge adjacent blobs
8 dilation = cv2.dilate(opening, kernel, iterations=2)
9 # threshold
10 th = dilation[dilation < 240] = 0
11 return th

处理后的前景如下 :

利用轮廓进行物体检测
我们将使用cv2.findContours函数对轮廓进行检测。我们在使用的时候可以选择的参数为:
cv2.CV_RETR_EXTERNAL------仅获取外部轮廓。
cv2.CV_CHAIN_APPROX_TC89_L1------使用Teh-Chin链逼近算法(更快)

代码如下:

1 def get_centroid(x, y, w, h):


2 x1 = int(w / 2)
3 y1 = int(h / 2)
4 cx = x + x1
5 cy = y + y1
6 return (cx, cy)
7
8 def detect_vehicles(fg_mask, min_contour_width=35, min_contour_height=35):
9 matches = []
10 # finding external contours
11 im, contours, hierarchy = cv2.findContours(
12 fg_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_TC89_L1)
13 # filtering by with, height
14 for (i, contour) in enumerate(contours):
15 (x, y, w, h) = cv2.boundingRect(contour)
16 contour_valid = (w >= min_contour_width) and (

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488820&idx=2&sn=4769e0a1fc3bdd1a4abe498e2dfa0e16&chksm=fb56f7d… 5/18
2020/7/29 使用OpenCV实现道路车辆计数

17 h >= min_contour_height)
18 if not contour_valid:
19 continue
20 # getting center of the bounding box
21 centroid = get_centroid(x, y, w, h)
22 matches.append(((x, y, w, h), centroid))
23 return matches

建立数据处理框架
我们都知道在ML和CV中,没有一个算法可以处理所有问题。即使存在这种算法,我们也不会使用
它,因为它很难大规模有效。例如几年前Netflix公司用300万美元的奖金悬赏最佳电影推荐算法。有
一个团队完成这个任务,但是他们的推荐算法无法大规模运行,因此其实对公司毫无用处。但是,
Netflix公司仍奖励了他们100万美元。

接下来我们来建立解决当前问题的框架,这样可以使数据的处理更加方便

1 class PipelineRunner(object):
2 '''
3 Very simple pipline.
4 Just run passed processors in order with passing context from one to
5 another.
6 You can also set log level for processors.
7 '''
8 def __init__(self, pipeline=None, log_level=logging.DEBUG):
9 self.pipeline = pipeline or []
10 self.context = {}
11 self.log = logging.getLogger(self.__class__.__name__)
12 self.log.setLevel(log_level)
13 self.log_level = log_level
14 self.set_log_level()
15 def set_context(self, data):
16 self.context = data
17 def add(self, processor):
18 if not isinstance(processor, PipelineProcessor):
19 raise Exception(
20 'Processor should be an isinstance of PipelineProcessor.')
21 processor.log.setLevel(self.log_level)
22 self.pipeline.append(processor)
23
24 def remove(self, name):

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488820&idx=2&sn=4769e0a1fc3bdd1a4abe498e2dfa0e16&chksm=fb56f7d… 6/18
2020/7/29 使用OpenCV实现道路车辆计数

25 for i, p in enumerate(self.pipeline):
26 if p.__class__.__name__ == name:
27 del self.pipeline[i]
28 return True
29 return False
30
31 def set_log_level(self):
32 for p in self.pipeline:
33 p.log.setLevel(self.log_level)
34
35 def run(self):
36 for p in self.pipeline:
37 self.context = p(self.context)
38 self.log.debug("Frame #%d processed.", self.context['frame_number'])
39 return self.context
40
41 class PipelineProcessor(object):
42 '''
43 Base class for processors.
44 '''
45 def __init__(self):
46 self.log = logging.getLogger(self.__class__.__name__)

首先我们获取一张处理器运行顺序的列表,让每个处理器完成一部分工作,在案顺序完成执行以获得
最终结果。

我们首先创建轮廓检测处理器。轮廓检测处理器只需将前面的背景扣除,滤波和轮廓检测部分合并在
一起即可,代码如下所示:

1 class ContourDetection(PipelineProcessor):
2 '''
3 Detecting moving objects.
4 Purpose of this processor is to subtrac background, get moving objects
5 and detect them with a cv2.findContours method, and then filter off-by
6 width and height.
7 bg_subtractor - background subtractor isinstance.
8 min_contour_width - min bounding rectangle width.
9 min_contour_height - min bounding rectangle height.
10 save_image - if True will save detected objects mask to file.
11 image_dir - where to save images(must exist).
12 '''

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488820&idx=2&sn=4769e0a1fc3bdd1a4abe498e2dfa0e16&chksm=fb56f7d… 7/18
2020/7/29 使用OpenCV实现道路车辆计数

13
14 def __init__(self, bg_subtractor, min_contour_width=35, min_contour_height=35,
15 super(ContourDetection, self).__init__()
16 self.bg_subtractor = bg_subtractor
17 self.min_contour_width = min_contour_width
18 self.min_contour_height = min_contour_height
19 self.save_image = save_image
20 self.image_dir = image_dir
21
22 def filter_mask(self, img, a=None):
23 '''
24 This filters are hand-picked just based on visual tests
25 '''
26 kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (2, 2))
27 # Fill any small holes
28 closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
29 # Remove noise
30 opening = cv2.morphologyEx(closing, cv2.MORPH_OPEN, kernel)
31 # Dilate to merge adjacent blobs
32 dilation = cv2.dilate(opening, kernel, iterations=2)
33 return dilation
34
35 def detect_vehicles(self, fg_mask, context):
36 matches = []
37 # finding external contours
38 im2, contours, hierarchy = cv2.findContours(
39 fg_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_TC89_L1)
40 for (i, contour) in enumerate(contours):
41 (x, y, w, h) = cv2.boundingRect(contour)
42 contour_valid = (w >= self.min_contour_width) and (
43 h >= self.min_contour_height)
44 if not contour_valid:
45 continue
46 centroid = utils.get_centroid(x, y, w, h)
47 matches.append(((x, y, w, h), centroid))
48 return matches
49
50 def __call__(self, context):
51 frame = context['frame'].copy()
52 frame_number = context['frame_number']
53 fg_mask = self.bg_subtractor.apply(frame, None, 0.001)
54 # just thresholding values

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488820&idx=2&sn=4769e0a1fc3bdd1a4abe498e2dfa0e16&chksm=fb56f7d… 8/18
2020/7/29 使用OpenCV实现道路车辆计数

55 fg_mask[fg_mask < 240] = 0


56 fg_mask = self.filter_mask(fg_mask, frame_number)
57 if self.save_image:
58 utils.save_frame(fg_mask, self.image_dir +
59 "/mask_%04d.png" % frame_number, flip=False)
60 context['objects'] = self.detect_vehicles(fg_mask, context)
61 context['fg_mask'] = fg_mask
62 return contex

现在,让我们创建一个处理器,该处理器将找出不同的帧上检测到的相同对象,创建路径,并对到达
出口区域的车辆进行计数。代码如下所示:

1 '''
2 Counting vehicles that entered in exit zone.
3
4 Purpose of this class based on detected object and local cache create
5 objects pathes and count that entered in exit zone defined by exit masks.
6
7 exit_masks - list of the exit masks.
8 path_size - max number of points in a path.
9 max_dst - max distance between two points.
10 '''
11
12 def __init__(self, exit_masks=[], path_size=10, max_dst=30, x_weight=1.0, y_wei
13 super(VehicleCounter, self).__init__()
14
15 self.exit_masks = exit_masks
16
17 self.vehicle_count = 0
18 self.path_size = path_size
19 self.pathes = []
20 self.max_dst = max_dst
21 self.x_weight = x_weight
22 self.y_weight = y_weight
23
24 def check_exit(self, point):
25 for exit_mask in self.exit_masks:
26 try:
27 if exit_mask[point[1]][point[0]] == 255:
28 return True
29 except:
https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488820&idx=2&sn=4769e0a1fc3bdd1a4abe498e2dfa0e16&chksm=fb56f7d… 9/18
2020/7/29 使用OpenCV实现道路车辆计数

30 return True
31 return False
32
33 def __call__(self, context):
34 objects = context['objects']
35 context['exit_masks'] = self.exit_masks
36 context['pathes'] = self.pathes
37 context['vehicle_count'] = self.vehicle_count
38 if not objects:
39 return context
40
41 points = np.array(objects)[:, 0:2]
42 points = points.tolist()
43
44 # add new points if pathes is empty
45 if not self.pathes:
46 for match in points:
47 self.pathes.append([match])
48
49 else:
50 # link new points with old pathes based on minimum distance between
51 # points
52 new_pathes = []
53
54 for path in self.pathes:
55 _min = 999999
56 _match = None
57 for p in points:
58 if len(path) == 1:
59 # distance from last point to current
60 d = utils.distance(p[0], path[-1][0])
61 else:
62 # based on 2 prev points predict next point and calculate
63 # distance from predicted next point to current
64 xn = 2 * path[-1][0][0] - path[-2][0][0]
65 yn = 2 * path[-1][0][1] - path[-2][0][1]
66 d = utils.distance(
67 p[0], (xn, yn),
68 x_weight=self.x_weight,
69 y_weight=self.y_weight
70 )
71

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488820&idx=2&sn=4769e0a1fc3bdd1a4abe498e2dfa0e16&chksm=fb56f7… 10/18
2020/7/29 使用OpenCV实现道路车辆计数

72 if d < _min:
73 _min = d
74 _match = p
75
76 if _match and _min <= self.max_dst:
77 points.remove(_match)
78 path.append(_match)
79 new_pathes.append(path)
80
81 # do not drop path if current frame has no matches
82 if _match is None:
83 new_pathes.append(path)
84
85 self.pathes = new_pathes
86
87 # add new pathes
88 if len(points):
89 for p in points:
90 # do not add points that already should be counted
91 if self.check_exit(p[1]):
92 continue
93 self.pathes.append([p])
94
95 # save only last N points in path
96 for i, _ in enumerate(self.pathes):
97 self.pathes[i] = self.pathes[i][self.path_size * -1:]
98
99 # count vehicles and drop counted pathes:
100 new_pathes = []
101 for i, path in enumerate(self.pathes):
102 d = path[-2:]
103
104 if (
105 # need at list two points to count
106 len(d) >= 2 and
107 # prev point not in exit zone
108 not self.check_exit(d[0][1]) and
109 # current point in exit zone
110 self.check_exit(d[1][1]) and
111 # path len is bigger then min
112 self.path_size <= len(path)
113 ):

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488820&idx=2&sn=4769e0a1fc3bdd1a4abe498e2dfa0e16&chksm=fb56f7… 11/18
2020/7/29 使用OpenCV实现道路车辆计数

114 self.vehicle_count += 1
115 else:
116 # prevent linking with path that already in exit zone
117 add = True
118 for p in path:
119 if self.check_exit(p[1]):
120 add = False
121 break
122 if add:
123 new_pathes.append(path)
124
125 self.pathes = new_pathes
126
127 context['pathes'] = self.pathes
128 context['objects'] = objects
129 context['vehicle_count'] = self.vehicle_count
130
131 self.log.debug('#VEHICLES FOUND: %s' % self.vehicle_count)
132
133 return context

上面的代码有点复杂,因此让我们一个部分一个部分的介绍一下。

上面的图像中绿色的部分是出口区域。我们在这里对车辆进行计数,只有当车辆移动的长度超过3个
点我们才进行计算

我们使用掩码来解决这个问题,因为它比使用矢量算法有效且简单得多。只需使用“二进制和”即可选
出车辆区域中点。设置方式如下:

1 EXIT_PTS = np.array([
2 [[732, 720], [732, 590], [1280, 500], [1280, 720]],
3 [[0, 400], [645, 400], [645, 0], [0, 0]]

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488820&idx=2&sn=4769e0a1fc3bdd1a4abe498e2dfa0e16&chksm=fb56f7… 12/18
2020/7/29 使用OpenCV实现道路车辆计数

4 ])
5
6 base = np.zeros(SHAPE + (3,), dtype='uint8')
7 exit_mask = cv2.fillPoly(base, EXIT_PTS, (255, 255, 255))[:, :, 0]

现在我们将检测到的点链接起来。

对于第一帧图像,我们将所有点均添加为新路径。
接下来,如果len(path)== 1,我们在新检测到的对象中找到与每条路径最后一点距离最近的对
象。
如果len(path)> 1,则使用路径中的最后两个点,即在同一条线上预测新点,并找到该点与当前点
之间的最小距离。
具有最小距离的点将添加到当前路径的末端并从列表中删除。如果在此之后还剩下一些点,我们会将
其添加为新路径。这个过程中我们还会限制路径中的点数。

1 new_pathes = []
2 for path in self.pathes:
3 _min = 999999
4 _match = None
5 for p in points:
6 if len(path) == 1:
7 # distance from last point to current
8 d = utils.distance(p[0], path[-1][0])
9 else:
10 # based on 2 prev points predict next point and calculate
11 # distance from predicted next point to current
12 xn = 2 * path[-1][0][0] - path[-2][0][0]
13 yn = 2 * path[-1][0][1] - path[-2][0][1]
14 d = utils.distance(
15 p[0], (xn, yn),
16 x_weight=self.x_weight,
17 y_weight=self.y_weight
18 )
19
20 if d < _min:
21 _min = d
22 _match = p
23
24 if _match and _min <= self.max_dst:
25 points.remove(_match)
26 path.append(_match)

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488820&idx=2&sn=4769e0a1fc3bdd1a4abe498e2dfa0e16&chksm=fb56f7… 13/18
2020/7/29 使用OpenCV实现道路车辆计数

27 new_pathes.append(path)
28
29 # do not drop path if current frame has no matches
30 if _match is None:
31 new_pathes.append(path)
32
33 self.pathes = new_pathes
34
35 # add new pathes
36 if len(points):
37 for p in points:
38 # do not add points that already should be counted
39 if self.check_exit(p[1]):
40 continue
41 self.pathes.append([p])
42
43 # save only last N points in path
44 for i, _ in enumerate(self.pathes):
45 self.pathes[i] = self.pathes[i][self.path_size * -1:]

现在,我们将尝试计算进入出口区域的车辆。为此,我们需获取路径中的最后2个点,并检查
len(path)是否应大于限制。

1 # count vehicles and drop counted pathes:


2 new_pathes = []
3 for i, path in enumerate(self.pathes):
4 d = path[-2:]
5 if (
6 # need at list two points to count
7 len(d) >= 2 and
8 # prev point not in exit zone
9 not self.check_exit(d[0][1]) and
10 # current point in exit zone
11 self.check_exit(d[1][1]) and
12 # path len is bigger then min
13 self.path_size <= len(path)
14 ):
15 self.vehicle_count += 1
16 else:
17 # prevent linking with path that already in exit zone
18 add = True

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488820&idx=2&sn=4769e0a1fc3bdd1a4abe498e2dfa0e16&chksm=fb56f7… 14/18
2020/7/29 使用OpenCV实现道路车辆计数

19 for p in path:
20 if self.check_exit(p[1]):
21 add = False
22 break
23 if add:
24 new_pathes.append(path)
25 self.pathes = new_pathes
26
27 context['pathes'] = self.pathes
28 context['objects'] = objects
29 context['vehicle_count'] = self.vehicle_count
30 self.log.debug('#VEHICLES FOUND: %s' % self.vehicle_count)
31 return context

最后两个处理器是CSV编写器,用于创建报告CSV文件,以及用于调试和精美图片的可视化。

1 class CsvWriter(PipelineProcessor):
2 def __init__(self, path, name, start_time=0, fps=15):
3 super(CsvWriter, self).__init__()
4 self.fp = open(os.path.join(path, name), 'w')
5 self.writer = csv.DictWriter(self.fp, fieldnames=['time', 'vehicles'])
6 self.writer.writeheader()
7 self.start_time = start_time
8 self.fps = fps
9 self.path = path
10 self.name = name
11 self.prev = None
12 def __call__(self, context):
13 frame_number = context['frame_number']
14 count = _count = context['vehicle_count']
15 if self.prev:
16 _count = count - self.prev
17 time = ((self.start_time + int(frame_number / self.fps)) * 100
18 + int(100.0 / self.fps) * (frame_number % self.fps))
19 self.writer.writerow({'time': time, 'vehicles': _count})
20 self.prev = count
21 return context
22 class Visualizer(PipelineProcessor):
23 def __init__(self, save_image=True, image_dir='images'):
24 super(Visualizer, self).__init__()
25 self.save_image = save_image

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488820&idx=2&sn=4769e0a1fc3bdd1a4abe498e2dfa0e16&chksm=fb56f7… 15/18
2020/7/29 使用OpenCV实现道路车辆计数

26 self.image_dir = image_dir
27 def check_exit(self, point, exit_masks=[]):
28 for exit_mask in exit_masks:
29 if exit_mask[point[1]][point[0]] == 255:
30 return True
31 return False
32 def draw_pathes(self, img, pathes):
33 if not img.any():
34 return
35 for i, path in enumerate(pathes):
36 path = np.array(path)[:, 1].tolist()
37 for point in path:
38 cv2.circle(img, point, 2, CAR_COLOURS[0], -1)
39 cv2.polylines(img, [np.int32(path)], False, CAR_COLOURS[0], 1)
40 return img
41 def draw_boxes(self, img, pathes, exit_masks=[]):
42 for (i, match) in enumerate(pathes):
43 contour, centroid = match[-1][:2]
44 if self.check_exit(centroid, exit_masks):
45 continue
46 x, y, w, h = contour
47 cv2.rectangle(img, (x, y), (x + w - 1, y + h - 1),
48 BOUNDING_BOX_COLOUR, 1)
49 cv2.circle(img, centroid, 2, CENTROID_COLOUR, -1)
50 return img
51 def draw_ui(self, img, vehicle_count, exit_masks=[]):
52 # this just add green mask with opacity to the image
53 for exit_mask in exit_masks:
54 _img = np.zeros(img.shape, img.dtype)
55 _img[:, :] = EXIT_COLOR
56 mask = cv2.bitwise_and(_img, _img, mask=exit_mask)
57 cv2.addWeighted(mask, 1, img, 1, 0, img)
58 # drawing top block with counts
59 cv2.rectangle(img, (0, 0), (img.shape[1], 50), (0, 0, 0), cv2.FILLED)
60 cv2.putText(img, ("Vehicles passed: {total} ".format(total=vehicle_count
61 cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 1)
62 return img
63 def __call__(self, context):
64 frame = context['frame'].copy()
65 frame_number = context['frame_number']
66 pathes = context['pathes']
67 exit_masks = context['exit_masks']

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488820&idx=2&sn=4769e0a1fc3bdd1a4abe498e2dfa0e16&chksm=fb56f7… 16/18
2020/7/29 使用OpenCV实现道路车辆计数

68 vehicle_count = context['vehicle_count']
69 frame = self.draw_ui(frame, vehicle_count, exit_masks)
70 frame = self.draw_pathes(frame, pathes)
71 frame = self.draw_boxes(frame, pathes, exit_masks)
72 utils.save_frame(frame, self.image_dir +
73 "/processed_%04d.png" % frame_number)
74 return context

结论

正如我们看到的那样,它并不像许多人想象的那么难。但是,如果小伙伴运行脚本,小伙伴会发
现此解决方案并不理想,存在前景对象存在重叠的问题,并且它也没有按类型对车辆进行分类。
但是,当相机有较好位置,例如位于道路正上方时,该算法具有很好的准确性。

如果本文对小伙伴有帮助,希望可以在文末来个“一键三连”。

交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、检测、
分 割 、 识 别 、 医 学 影 像 、 GAN 、 算 法 竞 赛 等 微 信 群 ( 以 后 会 逐 渐 细 分 ) , 请 扫 描 下 面 微 信 号 加 群 , 备
注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通
过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488820&idx=2&sn=4769e0a1fc3bdd1a4abe498e2dfa0e16&chksm=fb56f7… 17/18
2020/7/29 使用OpenCV实现道路车辆计数

//
//

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488820&idx=2&sn=4769e0a1fc3bdd1a4abe498e2dfa0e16&chksm=fb56f7… 18/18
2020/9/30 使用OpenCV实现哈哈镜效果

使用OpenCV实现哈哈镜效果
原创 小白 小白学视觉 9月6日

点击上方“小白学视觉”,选择加"星标"或“置顶”
重磅干货,第一时间送达

我们都记得那些曾经去游乐园或县集市的童年时代。这些游乐园中我最喜欢的是哈哈
镜室。

有趣的镜子不是平面镜子,而是凸/凹反射表面的组合,它们会产生扭曲效果,当我们
在这些镜子前面移动时,这些效果看起来很有趣。

在本文中,我们将学习使用OpenCV创建属于自己的哈哈镜。

视频

我们可以看到上面视频中的孩子如何享受不同的有趣镜子。我们都想拥有这样的乐
趣,但是为此,我们必须在现实中找到一面哈哈镜。

现在,我们无需去哈哈镜室即可在家中享受这种有趣的效果。在本文中,我们将学习
如何使用OpenCV制作这些有趣的镜子的数字版本。我们先来看一下具体的效果。

视频

图像形成理
我们首先需要了解如何将世界上的3D点投影到相机的图像坐标系中,这部分内容我们
默认小伙伴们已经了解,如果不了解,可以简单搜索一下,会有很多讲解的文章。这
里我们只做一个简单的介绍。

世界坐标中的3D点和图像中的像素点具有以下等式映射关系。其中P是相机投影矩
阵。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490886&idx=1&sn=117a8cf1367ab228fcb3a6e1e2b2a8f3&chksm=fb56ffaac… 1/11
2020/9/30 使用OpenCV实现哈哈镜效果

项目的主要内容
整个项目可以分为三个主要步骤:
1. 创建一个虚拟相机。
2. 定义3D表面(镜面),并使用合适的投影矩阵值将其投影到虚拟相机中。
3. 使用3D曲面的投影点的图像坐标来应用基于网格的变形以获得有趣的镜子的所需
效果。
下图可能会帮助我们更好地理解步骤。

图1:创建数字滑稽镜像所涉及的步骤。创建一个3D表面,即镜子(左),在虚拟相机中捕获平面以获取相应的

2D点,使用获得的2D点将基于网格的变形应用于图像,从而产生类似于滑稽镜子的效果。

接下来我们将详细的介绍每一个步骤

创建一个虚拟相机
基于上述理论,我们清楚地知道3D点如何与其对应的图像坐标相关。现在让我们了解
虚拟相机的含义以及如何使用该虚拟相机捕获图像。

虚拟相机本质上是矩阵P,因为它告诉我们3D世界坐标与相应图像像素坐标之间的关
系。让我们看看如何使用python创建虚拟相机。

我们将首先创建外部参数矩阵(M1)和内部参数矩阵(K),然后使用它们创建相机
投影矩阵(P)。

1 import numpy as np
2
3 # Defining the translation matrix
4 # Tx,Ty,Tz represent the position of our virtual camera in the world c
5 T = np.array([[1,0,0,-Tx],[0,1,0,-Ty],[0,0,1,-Tz]])

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490886&idx=1&sn=117a8cf1367ab228fcb3a6e1e2b2a8f3&chksm=fb56ffaac… 2/11
2020/9/30 使用OpenCV实现哈哈镜效果

6
7 # Defining the rotation matrix
8 # alpha,beta,gamma define the orientation of the virtual camera
9
10 Rx = np.array([[1, 0, 0], [0, math.cos(alpha), -math.sin(alpha)], [0,
11
12 Ry = np.array([[math.cos(beta), 0, -math.sin(beta)],[0, 1, 0],[math.si
13
14 Rz = np.array([[math.cos(gamma), -math.sin(gamma), 0],[math.sin(gamma)
15
16 R = np.matmul(Rx, np.matmul(Ry, Rz))
17
18 # Calculating the extrinsic camera parameter matrix M1
19 M1 = np.matmul(R,T)
20
21 # Calculating the intrinsic camera parameter matrix K
22 # sx and sy are apparent pixel length in x and y direction
23 # ox and oy are the coordinates of the optical center in the image pla
24 K = np.array([[-focus/sx,sh,ox],[0,focus/sy,oy],[0,0,1]])
25
26 P = np.matmul(K,RT)

请注意,我们必须为上面矩阵中的所有参数设置合适的值,例如focus,sx,sy,
ox,oy等。

那么,我们如何用这个虚拟相机捕捉图像呢?

首先,我们假设原始图像或视频帧是3D平面。当然,我们知道场景实际上不是3D平
面,但是我们没有图像中每个像素的深度信息。因此,我们仅假设场景为平面。请记
住,我们的目标不是为了科学目的而准确地为滑稽的镜子建模。我们只是想将其近似
用于娱乐。

其次,我们将图像定义为3D平面,我们可以简单地将矩阵P与世界坐标相乘并获得像
素坐标(u,v)。应用此转换与使用我们的虚拟相机捕获3D点的图像相同!

我们如何确定捕获图像中像素的颜色?场景中物体的材质属性如何?

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490886&idx=1&sn=117a8cf1367ab228fcb3a6e1e2b2a8f3&chksm=fb56ffaac… 3/11
2020/9/30 使用OpenCV实现哈哈镜效果

在渲染逼真的3D场景时,以上所有这些点绝对重要,但是我们不必渲染逼真的场景。
我们只是想做一些看起来很有趣的事情。

我们需要做的就是捕获(投影),首先将原始图像(或视频帧)表示为虚拟相机中的
3D平面,然后使用投影矩阵将该平面上的每个点投影到虚拟相机的图像平面上。

我们将3D坐标存储为numpy数组(W),将相机矩阵存储为numpy数组(P),然
后执行矩阵乘法P * W捕获3D点。

但是,在编写代码以使用虚拟相机捕获3D表面之前,我们首先需要定义3D表面。

定义3D表面(镜子)
为了定义3D曲面,我们形成X和Y坐标的网格,然后针对每个点计算Z坐标作为X和Y的
函数。因此,对于平面镜,我们将定义Z = K,其中K为任何常数。下图显示了可以生
成的镜面的一些示例。

3D表面的一些示例可用于创建哈哈镜镜子

现在,由于我们对如何定义3D曲面并将其捕获到虚拟相机中有了清晰的思路,让我们
看看如何在python中进行程序书写。

1 # Determine height and width of input image


2 H,W = image.shape[:2]
3 # Define x and y coordinate values in range (-W/2 to W/2) and (-H/2 to
4
5 x = np.linspace(-W/2, W/2, W)
6 y = np.linspace(-H/2, H/2, H)
7
8 # Creating a mesh grid using the x and y coordinate range defined abov
9 xv,yv = np.meshgrid(x,y)
10
11 # Generating the X,Y and Z coordinates of the plane
12 # Here we define Z = 1 plane

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490886&idx=1&sn=117a8cf1367ab228fcb3a6e1e2b2a8f3&chksm=fb56ffaac… 4/11
2020/9/30 使用OpenCV实现哈哈镜效果

13 X = xv.reshape(-1,1)
14 Y = yv.reshape(-1,1)
15 Z = X*0+1 # The mesh will be located on Z = 1 plane
16
17 pts3d = np.concatenate(([X],[Y],[Z],[X*0+1]))[:,:,0]
18
19 pts2d = np.matmul(P,pts3d)
20 u = pts2d[0,:]/(pts2d[2,:]+0.00000001)
21 v = pts2d[1,:]/(pts2d[2,:]+0.00000001)

VCAM:虚拟摄像机
我们是否需要每次编写以上代码?如果我们想动态更改摄像机的某些参数怎么办?为
了简化创建此类3D曲面,定义虚拟相机,设置所有参数并查找其投影的任务,我们可
以使用一个名为vcam的python库。我们可以在其文档中找到使用此库的不同方式的
各种插图。它减少了我们每次创建虚拟相机,定义3D点和查找2D投影的工作。此外,
该库还负责设置适当的内在和外在参数值,并处理各种异常,从而使其易于使用。存
储库中还提供了安装库的说明。
我们可以使用pip安装该库。

1 pip3 install vcam

下面是我们可以使用该库编写代码的方式,该代码的工作方式与我们到目前为止编写
的代码类似,但只有几行。

1 import cv2
2 import numpy as np
3 import math
4 from vcam import vcam,meshGen
5
6 # Create a virtual camera object. Here H,W correspond to height and wi
7 c1 = vcam(H=H,W=W)
8
9 # Create surface object
10 plane = meshGen(H,W)
11
12 # Change the Z coordinate. By default Z is set to 1
13 # We generate a mirror where for each 3D point, its Z coordinate is def
14 plane.Z = 10*np.sin((plane.X/plane.W)*2*np.pi*10)
https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490886&idx=1&sn=117a8cf1367ab228fcb3a6e1e2b2a8f3&chksm=fb56ffaac… 5/11
2020/9/30 使用OpenCV实现哈哈镜效果

15
16 # Get modified 3D points of the surface
17 pts3d = plane.getPlane()
18
19 # Project the 3D points and get corresponding 2D image coordinates usi
20 pts2d = c1.project(pts3d)
21

可以很容易地看到vcam库如何使定义虚拟摄像机,创建3D平面以及将其投影到虚拟
摄像机中变得容易。

现在可以将投影的2D点用于基于网格的重新映射。这是创建哈哈镜镜面效果的最后一
步。

图像重映射
重映射基本上是通过将输入图像的每个像素从其原始位置移动到由重映射功能定义的
新位置来生成新图像。因此,在数学上可以这样写:

上面的方法称为前向重映射或前向扭曲,其中map_x和map_y函数为我们提供了像素
的新位置,该位置最初位于(x,y)。

现在,如果map_x和map_y没有为我们给定的(x,y)对提供整数值怎么办?我们基于最
接近的整数值将(x,y)处的像素强度扩展到相邻像素。这会在重新映射或生成的图像中
创建孔,这些像素的强度未知且设置为0。如何避免这些孔?

我们使用反翘曲。这意味着现在map_x和map_y将为我们提供源图像中目标图像中给
定像素位置(x,y)的旧像素位置。它可以用数学方式表示如下:

我们现在知道如何执行重新映射。为了产生有趣的镜像效果,我们将对原始输入帧应
用重新映射。但是为此,我们需要map_x和map_y。在这种情况下,我们如何定义
map_x和map_y?

相当于我们理论解释中的(u,v)的2D投影点(pts2d)是可以传递给remap函数的所需
地图。现在,让我们来看一下从投影的2D点提取地图并应用remap函数(基于网格的
变形)以生成有趣的镜像效果的代码。

1 # Get mapx and mapy from the 2d projected points

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490886&idx=1&sn=117a8cf1367ab228fcb3a6e1e2b2a8f3&chksm=fb56ffaac… 6/11
2020/9/30 使用OpenCV实现哈哈镜效果

2 map_x,map_y = c1.getMaps(pts2d)
3
4 # Applying remap function to input image (img) to generate the funny mi
5 output = cv2.remap(img,map_x,map_y,interpolation=cv2.INTER_LINEAR)
6
7 cv2.imshow("Funny mirror",output)
8 cv2.waitKey(0)

输入和相应的输出图像,显示了基于正弦函数的滑稽镜的效果

太棒了!让我们尝试再创建一个有趣的镜像,以获得更好的效果。之后,我们将可以
制作自己的有趣的镜子。

1 import cv2
2 import numpy as np
3 import math
4 from vcam import vcam,meshGen
5
6 # Reading the input image. Pass the path of image you would like to us
7 img = cv2.imread("chess.png")
8 H,W = img.shape[:2]
9
10 # Creating the virtual camera object
11 c1 = vcam(H=H,W=W)
12
13 # Creating the surface object
14 plane = meshGen(H,W)
15
16 # We generate a mirror where for each 3D point, its Z coordinate is def

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490886&idx=1&sn=117a8cf1367ab228fcb3a6e1e2b2a8f3&chksm=fb56ffaac… 7/11
2020/9/30 使用OpenCV实现哈哈镜效果

17
18 plane.Z += 20*np.exp(-0.5*((plane.X*1.0/plane.W)/0.1)**2)/(0.1*np.sqrt
19 pts3d = plane.getPlane()
20
21 pts2d = c1.project(pts3d)
22 map_x,map_y = c1.getMaps(pts2d)
23
24 output = cv2.remap(img,map_x,map_y,interpolation=cv2.INTER_LINEAR)
25
26 cv2.imshow("Funny Mirror",output)
27 cv2.imshow("Input and output",np.hstack((img,output)))
28 cv2.waitKey(0)

现在我们知道,通过将Z定义为X和Y的函数,我们可以创建不同类型的失真效果。让
我们使用上面的代码创建更多的效果。我们只需要更改将Z定义为X和Y的函数的行即
可。这将进一步帮助您创建自己的效果。

1 # We generate a mirror where for each 3D point, its Z coordinate is def


2
3 plane.Z += 20*np.exp(-0.5*((plane.Y*1.0/plane.H)/0.1)**2)/(0.1*np.sqrt(2

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490886&idx=1&sn=117a8cf1367ab228fcb3a6e1e2b2a8f3&chksm=fb56ffaac… 8/11
2020/9/30 使用OpenCV实现哈哈镜效果

1 # We generate a mirror where for each 3D point, its Z coordinate is def


2
3 plane.Z += 20*np.sin(2*np.pi*((plane.X-plane.W/4.0)/plane.W)) + 20*np.s

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490886&idx=1&sn=117a8cf1367ab228fcb3a6e1e2b2a8f3&chksm=fb56ffaac… 9/11
2020/9/30 使用OpenCV实现哈哈镜效果

1 # We generate a mirror where for each 3D point, its Z coordinate is def


2
3 plane.Z -= 100*np.sqrt((plane.X*1.0/plane.W)**2+(plane.Y*1.0/plane.H)**2

项目源码:https://github.com/spmallick/learnopencv/tree/master/FunnyMirrors

小白学视觉 发起了一个读者讨论
小伙伴有什么想说的嘛

精选讨论内容

Ellen
cv2能在python3上运行吗

作者
可以的

交流群

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490886&idx=1&sn=117a8cf1367ab228fcb3a6e1e2b2a8f3&chksm=fb56ffaa… 10/11
2020/9/30 使用OpenCV实现哈哈镜效果

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计
算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),
请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交
大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入
相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247490886&idx=1&sn=117a8cf1367ab228fcb3a6e1e2b2a8f3&chksm=fb56ffaa… 11/11
2020/9/30 使用OpenCV为视频中美女加上眼线

使用OpenCV为视频中美女加上眼线
原创 小白 小白学视觉 8月2日

收录于话题
16个
#OpenCV应用

点击上方“小白学视觉”,选择加"星标"或“置顶”

重磅干货,第一时间送达

计算机视觉是最令人兴奋的领域之一,其应用范围非常广泛。从医学成像到创建最有
趣的面部滤镜等各个领域都充分见证了计算机视觉技术的强大。在本文中,我们将尝
试创建一个人造眼线笔来模仿Snapchat或Instagram滤波器,为视频中的美女添加上
美丽的眼线。最终的结果可以通过下面的动图观察到。

采集

本文介绍的内容适合想要通过计算机视觉来实现一个具有一定展示性功能的计算机视
觉初学者。因此,在本文重我们会尽量简化说明,如果您对完整的程序感兴趣,可以
在Github上找到完整的代码。Github的链接在本文的文末给出。

在实现本文功能之前,我们需要设置一个新的虚拟环境并安装所有必需的依赖项。这
个过程比较简单,我们也在Github里面给出了如何配置环境的具体过程。在本项目
中,我们需要使用的工具有OpenCV,NumPy,imutils,SciPy和Dlib。有些小伙伴
可能对这些工具和库比较陌生,接下来我们简单介绍一下每个模块的作用。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489920&idx=1&sn=ddd05888a2d1cec51b9d36f2b7fdb6a1&chksm=fb56fb6c… 1/7
2020/9/30 使用OpenCV为视频中美女加上眼线

OpenCV:用于图像处理的最受欢迎的模块之一。我们将使用OpenCV读取,写入和
绘制图像。
NumPy:在处理OpenCV项目时经常使用NumPy。图像本质上是一个像素数组,
OpenCV使用以NumPy数组形式存储的这些数组,并对图像执行操作。
Imutils:Imutils附带了自定义功能,使我们的计算机视觉工作变得更加轻松。在这
里,我们将使用它来将dlib对象转换为非常灵活且广泛接受的numpy数组。
Scipy:顾名思义,SciPy用于python上的科学计算。我们将使用它来创建插值(如
果现在没有意义,可以的)。
Dlib:Dlib是一个包含各种ML算法的C ++库。我们将使用dlib提取面部界标点。

项目简要介绍

该程序首先从每个面孔中提取68个界标点。在这68个点中,点37–42属于左眼,点43
–48属于右眼,具体形式如下图所示。

因为我们的目标是给面部添加眼线,所以我们只对37-48点感兴趣,因此我们提取了
这些点。我们将对这些提取的点进行插值。插值意味着我们尝试在两个给定点之间插
入点。我们可以使用的插值方式如下图所示。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489920&idx=1&sn=ddd05888a2d1cec51b9d36f2b7fdb6a1&chksm=fb56fb6c… 2/7
2020/9/30 使用OpenCV为视频中美女加上眼线

眼线算法的流程图如下所示

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489920&idx=1&sn=ddd05888a2d1cec51b9d36f2b7fdb6a1&chksm=fb56fb6c… 3/7
2020/9/30 使用OpenCV为视频中美女加上眼线

接下来,我们将进一步详细描述该算法。如果小伙伴只对运行代码感兴趣,可以跳至
最后一部分。

算法介绍
https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489920&idx=1&sn=ddd05888a2d1cec51b9d36f2b7fdb6a1&chksm=fb56fb6c… 4/7
2020/9/30 使用OpenCV为视频中美女加上眼线

我们首先需要提取脸部周围边界框的坐标。

OpenCV将图像转换为NumPy数组。numpy.array(即图像的矩阵表示形式)存储在
名为的变量中frame。我们使用一个名为face_detector()的函数,该函数返回围绕框
架中所有脸部的包围框的坐标。这些边界框坐标存储在一个名为bounding_boxes的
变 量 中 。 遍 历 循 环 bounding_boxes 以 将 眼 线 应 用 于 帧 中 检 测 到 的 每 个 脸 部 。
face_landmark_points 存 储 68 个 坐 标 点 。 eye_landmark_points 是 从
getEyeLandmarkPts()函数中得到。

getEyeLandmarkPts()函数使用68个坐标点作为输入并返回具有左上眼睑的坐标4个
矩 阵 , 左 上 眼 线 ( L_eye_top ) , 左 下 眼 线 ( L_eye_bottom ) 和 相 同 的 右 眼
(R_eye_top & R_eye_bottom)。这可以通过简单的NumPy索引完成的。我们将
端点(pt号37、40、43和46。请参见68个界标点图)向外移动5px,以使外观更逼
真。

现在,我们需要对这些点进行插值以获得平滑的曲线,进而可以画出眼线。我们需要
对 每 个 曲 线 进 行 不 同 的 处 理 ( 即 L_eye_top , L_eye_bottom , R_eye_top ,
R_eye_bottom ) 。 因 此 , 我 们 为 每 个 曲 线 使 用 单 独 的 变 量 名 称 。
interpolateCoordinates()用于在每条曲线上生成插值。重复使用该函数,为每个曲
线生成插值坐标。这个函数为每个曲线返回一个插值点数组。

drawEyeLiner()函数将生成的插值点作为参数,并在两个连续点之间画一条线。在两
个循环中为每个曲线完成此操作,一个循环用于左眼,另一个循环用于右眼。

调用项目
该项目的用发非常简单,首先从Github上克隆到本地

1 git clone https://github.com/kaushil24/Artificial-Eyeliner/

接下来,打开命令提示符并键入以下代码以运行示例测试

1 python3 eyeliner.py -v "Media/Sample Video.mp4"

我们也可以通过将视频路径放在参数中来使用自己的视频。完整的CLI命令如下:

1 python eyeliner.py [-i image] [-v video] [-d dat] [-t thickness] [-c col

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489920&idx=1&sn=ddd05888a2d1cec51b9d36f2b7fdb6a1&chksm=fb56fb6c… 5/7
2020/9/30 使用OpenCV为视频中美女加上眼线

每个参数的具体含义如下:

i :要在其上绘制眼线的图像的路径
v :要在其上绘制眼线的视频的路径。
v :也可以通过网络摄像头获取视频。例如:python3 -v webcam -s "Webcam
output"
t :整数(整数)以设置眼线的厚度。默认值= 2。推荐的数值介于1-5之间
d : shape_predictor_68_face_landmarks.dat 文 件 的 路 径 。 默 认 路 径 在 根 目 录
中。除非将shape_predictor_68_face_landmarks.dat文件存储在其他位置,否则
不需要使用此参数。
c :更改眼线的颜色。语法-c 255 255 255。默认值= 0 0 0。其中每个数字代表
其RGB值。
s :要将输出保存到的位置和文件名。注意程序在保存文件时会自动添加扩展
名。如果已经存在同名文件,它将覆盖该文件。

好了,对这个项目感兴趣的小伙伴可以按照上面的说明来进行尝试,可以通过对程序
的修改以达到自己的需求。如果小伙伴觉得这个项目比较有趣,文末给小白六留个“好
看”哦。

交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计
算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),
请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交
大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入
相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489920&idx=1&sn=ddd05888a2d1cec51b9d36f2b7fdb6a1&chksm=fb56fb6c… 6/7
2020/9/30 使用OpenCV为视频中美女加上眼线

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489920&idx=1&sn=ddd05888a2d1cec51b9d36f2b7fdb6a1&chksm=fb56fb6c… 7/7
2020/7/31 使用Python,Keras和OpenCV进行实时面部检测

使用Python,Keras和OpenCV进行实时面部检测
原创 努比 小白学视觉 今天

来自专辑

OpenCV应用

点击上方“小白学视觉”,选择加"星标"或“置顶”

重磅干货,第一时间送达

目前我们在互联网和论文中看到的大多数面部识别算法都是以图像为基础进行处理。这些方法在检测
和识别来自摄像头的图像、或视频流各帧中的人脸时效果很好。但是,他们无法区分现实生活中的人
脸和照片上的人脸,因为这些算法处理的是2D帧。

现在,让我们想象一下,如果我们想要实现一个面部识别开门器。该系统可以很好地区分已知面孔和
未知面孔,保证只有特定人员才能访问。尽管如此,任意一个陌生人只要拥有他们的照片就很容易进
入该区域,这时3D检测器(类似于Apple的FaceID)就被纳入考虑范围。但是,如果我们没有3D探
测器怎么办?

https://mp.weixin.qq.com/s/PQUESErIgl8jIoV6Jsb8NQ 1/9
2020/7/31 使用Python,Keras和OpenCV进行实时面部检测

奥巴马脸部照片识别案例❌

本文旨在实现一种基于眨眼检测的面部活动检测算法来阻止照片的使用。该算法通过网络摄像头实时
工作,并且仅在眨眼时才显示该人的姓名。程序流程如下:

1. 对网络摄像头生成的每一帧图像,进行面部检测。

2. 对于每个检测到的脸部区域,进行眼睛检测。

3. 对于检测到的每只眼睛,进行眨眼检测。

4. 如果在某个时刻检测到眼睛合上后又睁开了,则认为该人眨了眨眼,程序将显示他的名字(对
于面部识别开门器,我们将授权该人进入)。

为了检测和识别面部,我们需要安装face_recognition库,该库提供了非常棒的深度学习算法来查找
和识别图像中的人脸。特别是face_locations , face_encodings和compare_faces函数是3个最常用
的函数 。 face_locations函数有两种可使用两种方法进行人脸检测 :梯度方向的
Histrogram ( HOG ) 和C onvolutional 神经网络( CNN )。 由于时间限制 ,选择了HOG方法。
face_encodings函数是一个预训练的卷积神经网络,能够将图像编码为128个特征的向量。这些向量
的信息足够以区分两个不同的人。最后,使用compare_faces计算两个嵌入向量之间的距离 。 它将允
许算法识别从摄像头帧中提取的面部,并将其嵌入矢量与我们数据集中的所有编码面部进行比较。最
接近的向量对应于同一个人。

1.已知的人脸数据集编码

https://mp.weixin.qq.com/s/PQUESErIgl8jIoV6Jsb8NQ 2/9
2020/7/31 使用Python,Keras和OpenCV进行实时面部检测

就我们的算法而言,它能够识别我们自己和巴拉克·奥巴马。分别选择了约10张图片。以下是用于处理
和编码已知面孔数据库的代码。

1 def process_and_encode(images):
2 known_encodings = []
3 known_names = []
4 print("[LOG] Encoding dataset ...")
5
6 for image_path in tqdm(images):
7 # Load image
8 image = cv2.imread(image_path)
9 # Convert it from BGR to RGB
10 image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
11
12 # detect face in the image and get its location (square boxes coordinates)
13 boxes = face_recognition.face_locations(image, model='hog')
14
15 # Encode the face into a 128-d embeddings vector
16 encoding = face_recognition.face_encodings(image, boxes)
17
18 # the person's name is the name of the folder where the image comes from
19 name = image_path.split(os.path.sep)[-2]
20
21 if len(encoding) > 0 :
22 known_encodings.append(encoding[0])
23 known_names.append(name)
24
25 return {"encodings": known_encodings, "names": known_names}

现在我们知道了要识别的每个人的编码,我们可以尝试通过网络摄像头识别和识别面部。但是,在进
行此部分操作之前,我们需要区分面部照片和活人的面部。

2.面部活跃度检测

提醒一下,目标是在某个点检测“睁开-闭合-睁开”的眼图。我训练了卷积神经网络来对眼睛是闭合还
是睁开进行分类。选择的模型是LeNet-5,该模型已在 Closed Eyes In The Wild (CEW) 数据集中进
行了训练。它由大小约为24x24的4800眼图像组成。

1 from keras.models import Sequential


2 from keras.layers import Conv2D
3 from keras.layers import AveragePooling2D

https://mp.weixin.qq.com/s/PQUESErIgl8jIoV6Jsb8NQ 3/9
2020/7/31 使用Python,Keras和OpenCV进行实时面部检测

4 from keras.layers import Flatten


5 from keras.layers import Dense
6 from keras.preprocessing.image import ImageDataGenerator
7
8 IMG_SIZE = 24
9 def train(train_generator, val_generator):
10 STEP_SIZE_TRAIN=train_generator.n//train_generator.batch_size
11 STEP_SIZE_VALID=val_generator.n//val_generator.batch_size
12
13 model = Sequential()
14
15 model.add(Conv2D(filters=6, kernel_size=(3, 3), activation='relu', input_shape=(IM
16 model.add(AveragePooling2D())
17
18 model.add(Conv2D(filters=16, kernel_size=(3, 3), activation='relu'))
19 model.add(AveragePooling2D())
20
21 model.add(Flatten())
22
23 model.add(Dense(units=120, activation='relu'))
24
25 model.add(Dense(units=84, activation='relu'))
26
27 model.add(Dense(units=1, activation = 'sigmoid'))
28
29
30 model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
31
32 print('[LOG] Training CNN')
33
34 model.fit_generator(generator=train_generator,
35 steps_per_epoch=STEP_SIZE_TRAIN,
36 validation_data=val_generator,
37 validation_steps=STEP_SIZE_VALID,
38 epochs=20
39 )
40 return model

在评估模型时,准确率达到94%。

https://mp.weixin.qq.com/s/PQUESErIgl8jIoV6Jsb8NQ 4/9
2020/7/31 使用Python,Keras和OpenCV进行实时面部检测

每次检测到眼睛时,我们都会使用模型预测其状态,并跟踪每个人的眼睛状态。因此,借助以下功
能,可使检测眨眼变得很容易,该功能尝试在眼睛状态历史记录中查找闭合-闭合-闭合模式。

1 def isBlinking(history, maxFrames):


2 """ @history: A string containing the history of eyes status
3 where a '1' means that the eyes were closed and '0' open.
4 @maxFrames: The maximal number of successive frames where an eye is closed ""
5 for i in range(maxFrames):
6 pattern = '1' + '0'*(i+1) + '1'
7 if pattern in history:
8 return True
9 return False

3.活人的面部识别

我们拥有构建“真实”面部识别算法的所有要素,只需要一种实时检测面部和眼睛的方法即可。我们选
择使用OpenCV预训练的Haar级联分类器执行这些任务。

1 def detect_and_display(model, video_capture, face_detector, open_eyes_detector, lef


2 frame = video_capture.read()
3 # resize the frame
4 frame = cv2.resize(frame, (0, 0), fx=0.6, fy=0.6)
5
6 gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
7 rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
8
9 # Detect faces
10 faces = face_detector.detectMultiScale(
11 gray,
12 scaleFactor=1.2,
13 minNeighbors=5,
14 minSize=(50, 50),
15 flags=cv2.CASCADE_SCALE_IMAGE
16 )
17
18 # for each detected face
19 for (x,y,w,h) in faces:
20 # Encode the face into a 128-d embeddings vector
21 encoding = face_recognition.face_encodings(rgb, [(y, x+w, y+h, x)])[0]
22
23 # Compare the vector with all known faces encodings

https://mp.weixin.qq.com/s/PQUESErIgl8jIoV6Jsb8NQ 5/9
2020/7/31 使用Python,Keras和OpenCV进行实时面部检测

24 matches = face_recognition.compare_faces(data["encodings"], encoding)


25
26 # For now we don't know the person name
27 name = "Unknown"
28
29 # If there is at least one match:
30 if True in matches:
31 matchedIdxs = [i for (i, b) in enumerate(matches) if b]
32 counts = {}
33 for i in matchedIdxs:
34 name = data["names"][i]
35 counts[name] = counts.get(name, 0) + 1
36
37 # The known encoding with the most number of matches corresponds to
38 name = max(counts, key=counts.get)
39
40 face = frame[y:y+h,x:x+w]
41 gray_face = gray[y:y+h,x:x+w]
42
43 eyes = []
44
45 # Eyes detection
46 # check first if eyes are open (with glasses taking into account)
47 open_eyes_glasses = open_eyes_detector.detectMultiScale(
48 gray_face,
49 scaleFactor=1.1,
50 minNeighbors=5,
51 minSize=(30, 30),
52 flags = cv2.CASCADE_SCALE_IMAGE
53 )
54 # if open_eyes_glasses detect eyes then they are open
55 if len(open_eyes_glasses) == 2:
56 eyes_detected[name]+='1'
57 for (ex,ey,ew,eh) in open_eyes_glasses:
58 cv2.rectangle(face,(ex,ey),(ex+ew,ey+eh),(0,255,0),2)
59
60 # otherwise try detecting eyes using left and right_eye_detector
61 # which can detect open and closed eyes
62 else:
63 # separate the face into left and right sides
64 left_face = frame[y:y+h, x+int(w/2):x+w]
65 left_face_gray = gray[y:y+h, x+int(w/2):x+w]

https://mp.weixin.qq.com/s/PQUESErIgl8jIoV6Jsb8NQ 6/9
2020/7/31 使用Python,Keras和OpenCV进行实时面部检测

66
67 right_face = frame[y:y+h, x:x+int(w/2)]
68 right_face_gray = gray[y:y+h, x:x+int(w/2)]
69
70 # Detect the left eye
71 left_eye = left_eye_detector.detectMultiScale(
72 left_face_gray,
73 scaleFactor=1.1,
74 minNeighbors=5,
75 minSize=(30, 30),
76 flags = cv2.CASCADE_SCALE_IMAGE
77 )
78
79 # Detect the right eye
80 right_eye = right_eye_detector.detectMultiScale(
81 right_face_gray,
82 scaleFactor=1.1,
83 minNeighbors=5,
84 minSize=(30, 30),
85 flags = cv2.CASCADE_SCALE_IMAGE
86 )
87
88 eye_status = '1' # we suppose the eyes are open
89
90 # For each eye check wether the eye is closed.
91 # If one is closed we conclude the eyes are closed
92 for (ex,ey,ew,eh) in right_eye:
93 color = (0,255,0)
94 pred = predict(right_face[ey:ey+eh,ex:ex+ew],model)
95 if pred == 'closed':
96 eye_status='0'
97 color = (0,0,255)
98 cv2.rectangle(right_face,(ex,ey),(ex+ew,ey+eh),color,2)
99 for (ex,ey,ew,eh) in left_eye:
100 color = (0,255,0)
101 pred = predict(left_face[ey:ey+eh,ex:ex+ew],model)
102 if pred == 'closed':
103 eye_status='0'
104 color = (0,0,255)
105 cv2.rectangle(left_face,(ex,ey),(ex+ew,ey+eh),color,2)
106 eyes_detected[name] += eye_status
107

https://mp.weixin.qq.com/s/PQUESErIgl8jIoV6Jsb8NQ 7/9
2020/7/31 使用Python,Keras和OpenCV进行实时面部检测

108 # Each time, we check if the person has blinked


109 # If yes, we display its name
110 if isBlinking(eyes_detected[name],3):
111 cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
112 # Display name
113 y = y - 15 if y - 15 > 15 else y + 15
114 cv2.putText(frame, name, (x, y), cv2.FONT_HERSHEY_SIMPLEX,0.75, (0,
115
116 return frame

上面的功能是用于检测和识别真实面部的代码。它所需的输入参数:

• 型号:睁眼/闭眼分类器

• video_capture:流视频

• face_detector:Haar级联的人脸分类器。我们选择了haarcascade_frontalface_alt.xml

• open_eyes_detector:Haar级联睁眼分类器。我选择了
haarcascade_eye_tree_eyeglasses.xml

• left_eye_detector:Haar级联的左眼分类器。我选择了haarcascade_lefteye_2splits.xml,它
可以检测睁眼或闭眼。

• right_eye_detector:Haar级联的右眼分类器。我们选择了
haarcascade_righteye_2splits.xml,它可以检测睁眼或闭眼。

• 数据:已知编码和已知名称的字典

• eyes_detected:包含每个名称的眼睛状态历史记录的字典。

在第2至4行,我们从网络摄像头流中抓取一帧,然后调整其大小以加快计算速度。在第10 行,我们
从帧中检测人脸,然后在第21行,将其编码为128-d向量。在第23-38行中,我们将此向量与已知的
面部编码进行比较,然后通过计算匹配次数确定该人的姓名。匹配次数最多的一个被选中。从第45行
开始,我们在脸部范围内检测眼睛是否存在。首先,我们尝试使用open_eye_detector检测 睁眼 。如
果检测器成功,则在第54行,将 ''1''添加到眼睛状态历史记录。如果第一个分类器失败了(可能是因
为闭眼或仅仅是因为它不识别眼睛),这意味着open_eye_detector无法检测到闭合的眼睛,则使用
left_eye和right_eye检测器。该面部分为左侧和右侧,以便对各个检测器进行分类。从第92行开
始,提取眼睛部分,经过训练的模型预测眼睛是否闭合。如果检测到一只闭合的眼睛,则预测两只眼

https://mp.weixin.qq.com/s/PQUESErIgl8jIoV6Jsb8NQ 8/9
2020/7/31 使用Python,Keras和OpenCV进行实时面部检测

睛都闭合,并且将''0''添加到眼睛状态历史记录中。否则,可以得出结论,眼睛睁开了。最后在第110
行,isBlinking () 功能用于检测眨眼以及是否眨眼的人。

参考资料

• https://docs.opencv.org/3.4.3/d7/d8b/tutorial_py_face_detection.html

• https://www.pyimagesearch.com/2018/06/18/face-recognition-with-opencv-python-and-
deep-learning/

交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、检测、
分 割 、 识 别 、 医 学 影 像 、 GAN 、 算 法 竞 赛 等 微 信 群 ( 以 后 会 逐 渐 细 分 ) , 请 扫 描 下 面 微 信 号 加 群 , 备
注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通
过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~

https://mp.weixin.qq.com/s/PQUESErIgl8jIoV6Jsb8NQ 9/9
2020/7/29 使用TensorFlow和OpenCV实现口罩检测

使用TensorFlow和OpenCV实现口罩检测
原创 花生 小白学视觉 6月8日

来自专辑

OpenCV应用

点击上方“小白学视觉”,选择“星标”公众号
重磅干货,第一时间送达

在 这 段 艰 难 的 疫 情 期 间 , 我 们 决 定 建 立 一 个 非 常 简 单 和 基 本 的 卷 积 神 经 网 络 (CNN) 模 型 , 使 用
TensorFlow与Keras库和OpenCV来检测人们是否佩戴口罩。

图片来源于澳门图片社
为了建立这个模型,我们将使用由Prajna Bhandary 提供的口罩数据集。这个数据集包括大约1,376
幅图像,其中690幅图像包含戴口罩的人,686幅图像包含没有戴口罩的人。

我们将使用这些图像悬链一个基于TensorFlow框架的CNN模型,之后通过电脑端的网络摄像头来检
测人们是否戴着口罩。此外,我们也可以使用手机相机做同样的事情。

数据可视化
首先,我们需要标记数据集中两个类别的全部图像。我们可以看到这里有690张图像在‘yes’类里,也
就是戴口罩的一类;有686张图像在‘no’类中,也就是没有带口罩的一类。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488108&idx=1&sn=9dd54acec574d08fcfa33a784e6b6556&chksm=fb56f080… 1/5
2020/7/29 使用TensorFlow和OpenCV实现口罩检测

1 The number of images with facemask labelled 'yes': 690


2 The number of images with facemask labelled 'no': 686

数据增强
这里,我们需要增强我们的数据集,为训练提供更多数量的图像。在数据增强时,我们旋转并翻转数
据集中的每幅图像。在数据增强后,我们总共有2751幅图像,其中‘yes’类中有1380幅图像,‘no’类
中有1371幅图像。

1 Number of examples: 2751


2 Percentage of positive examples: 50.163576881134134%, number of pos examples: 1380
3 Percentage of negative examples: 49.836423118865866%, number of neg examples: 1371

数据分割
我们将我们的数据分割成训练集和测试集,训练集中包含将要被CNN模型训练的图像,测试集中包含
将要被我们模型测试的图像。

在此,我们取split_size=0.8,这意味着80%的图像将进入训练集,其余20%的图像将进入测试集。

1 The number of images with facemask in the training set labelled 'yes': 1104
2 The number of images with facemask in the test set labelled 'yes': 276
3 The number of images without facemask in the training set labelled 'no': 1096
4 The number of images without facemask in the test set labelled 'no': 275

在分割后,我们看到图像已经按照分割的百分比分配给训练集和测试集。

建立模型
在这一步中,我们将使用Conv2D,MaxPooling2D,Flatten,Dropout和Dense等各种层构建顺
序CNN模型。在最后一个Dense层中,我们使用‘softmax’函数输出一个向量,给出两个类中每个类
的概率。

1 model = tf.keras.models.Sequential([
2 tf.keras.layers.Conv2D(100, (3,3), activation='relu', input_shape=(150, 150, 3))
3 tf.keras.layers.MaxPooling2D(2,2),
4

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488108&idx=1&sn=9dd54acec574d08fcfa33a784e6b6556&chksm=fb56f080… 2/5
2020/7/29 使用TensorFlow和OpenCV实现口罩检测

5 tf.keras.layers.Conv2D(100, (3,3), activation='relu'),


6 tf.keras.layers.MaxPooling2D(2,2),
7
8 tf.keras.layers.Flatten(),
9 tf.keras.layers.Dropout(0.5),
10 tf.keras.layers.Dense(50, activation='relu'),
11 tf.keras.layers.Dense(2, activation='softmax')
12 ])
13 model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['acc'])

在这里,我们使用 ‘adam’ 优化器和‘binary_crossentropy’ 作 为 我 们 的 损 失 函 数 , 因 为 只 有 两 个


类。此外,也甚至可以使用MobileNetV2以获得更高的精度。

预训练CNN模型
在构建我们的模型之后,我们创建“train_generator”和“validation_generator”,以便在下一步将它
们与我们的模型相匹配。 我们看到训练集中总共有2200张图像,测试集中有551张图像。

1 Found 2200 images belonging to 2 classes.


2 Found 551 images belonging to 2 classes.

训练CNN模型
这一步是主要的步骤,我们使用训练集中的图像来训练我们的模型,并使用测试集中的数据来测试我
们的训练结果,给出准确率。我们进行了30次迭代,我们训练的输出结果在下面给出。同时,我们可
以训练更多的迭代,以获得更高的精度,以免发生过拟合。

1 history = model.fit_generator(train_generator,
2 epochs=30,
3 validation_data=validation_generator,
4 callbacks=[checkpoint])>>Epoch 30/30
5 220/220 [==============================] - 231s 1s/step - loss: 0.0368 - acc: 0.9886

我们看到,在第30个时期之后,我们的模型中训练集的精度为98.86%,测试集的精度为96.19%。
这意味着它是训练结果很好,没有任何过度拟合。

标记信息

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488108&idx=1&sn=9dd54acec574d08fcfa33a784e6b6556&chksm=fb56f080… 3/5
2020/7/29 使用TensorFlow和OpenCV实现口罩检测

在建立模型后,我们为我们的结果标记了两个概率
[‘0’ 作为‘without_mask’ 和‘1’作为‘with_mask’]。我们还使用RGB值设置边界矩形颜色。 [‘RED’ 代
表 ‘without_mask’ 和‘GREEN’ 代表 ‘with_mask]

1 labels_dict={0:'without_mask',1:'with_mask'}
2 color_dict={0:(0,0,255),1:(0,255,0)}

导入人脸检测程序
在此之后,我们打算使用PC的网络摄像头来检测我们是否佩戴口罩。为此,首先我们需要实现人脸检
测。在此,我们使用基于Haar特征的级联分类器来检测人脸的特征。

1 face_clsfr=cv2.CascadeClassifier('haarcascade_frontalface_default.xml')

这种级联分类器是由OpenCV设计的,通过训练数千幅图像来检测正面的人脸。代码中需要的.xml文
件可以在公众号后台回复“口罩检测”获得。

检测是否戴口罩
在最后一步中,我们通过OpenCV库运行一个无限循环程序,使用我们的网络摄像头,在其中我们使
用Cascade Classifier检测人脸。代码是webcam = cv2.VideoCapture(0)表示使用网络摄像头。

该模型将预测两类中每一类的可能性([without_mask, with_mask])。基于概率的大小,标签将被选
择并显示在我们脸的区域。

此外,还可以下载用于手机和PC的DroidCam 应用程序来使用我们的移动相机,并将代码中的0改为
1 webcam= cv2.VideoCapture(1).

测试:
我们来看一下测试的结果

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488108&idx=1&sn=9dd54acec574d08fcfa33a784e6b6556&chksm=fb56f080… 4/5
2020/7/29 使用TensorFlow和OpenCV实现口罩检测

从上面的演示视频中,我们看到模型能够正确地检测是否佩戴面具并在标签上显示相同的内容。

如果小伙伴需要本文的相关代码和文件,可以在公众号后台回复【口罩检测】获得。

//
//
https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247488108&idx=1&sn=9dd54acec574d08fcfa33a784e6b6556&chksm=fb56f080… 5/5
2020/7/29 使用TensorFlow物体检测模型、Python和OpenCV的社交距离检测器

使用TensorFlow物体检测模型、Python和OpenCV的社交距离检测器
原创 小鱼 小白学视觉 1周前

来自专辑

OpenCV应用

点击上方“小白学视觉”,选择加"星标"或“置顶”
重磅干货,第一时间送达

0.介绍
疫情期间,我们在GitHub上搜索TensorFlow预训练模型,发现了一个包含25个物体检测预训练模
型的库,并且这些预训练模型中包含其性能和速度指标。结合一定的计算机视觉知识,使用其中的模
型来构建社交距离程序会很有趣。

学习OpenCV的过程中,小伙伴们应该知道对于一些小型项目OpenCV具有很强大的功能,其中一个
就是对图片进行鸟瞰转换,鸟瞰图是对一个场景自上而下的表示,也是构建自动驾驶应用程序时经常
需要执行的任务。

车载摄像头鸟瞰系统的实现

这说明将鸟瞰转换的技术应用到监视社交距离的场景中可以提高监视质量。

本期我们将介绍了如何使用深度学习模型以及计算机视觉方面的一些知识来构建强大的社交距离检测
器。

本文的结构如下:
·模型选择
·人员检测
·鸟瞰图转换
·社交距离测量

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489175&idx=1&sn=7e977c6402250fc5c710a59e0e91d4bc&chksm=fb56f47b… 1/8
2020/7/29 使用TensorFlow物体检测模型、Python和OpenCV的社交距离检测器

·结果与改进

所有代码及安装说明可以以下链接中找到:https://github.com/basileroth75/covid-social-
distancing-detection

1.模型选择
在TensorFlow物体检测模型zoo中的所有可用模型已经在COCO数据集(Context中的通用物体)上
进行了预训练。COCO数据集包含120000张图像,这些图像中总共包含880000个带标签的物体。这
些模型经过预训练可以检测90种不同类型的物体,物体类型的完整列表可以在GitHub的data部分得
到,地址为:
https://github.com/tensorflow/models/blob/master/research/object_detection/data/msco
co_complete_label_map.pbtxt

可用模型的非详尽清单

模型的预测速度不同,性能表现也不同。为了决定如何根据模型的预测速度来利用模型,我进行了一
些 测 试 。 由 于 社 交 距 离 检 测 器 的 目 标 不 是 执 行 实 时 分 析 , 因 此 最 终 选 择 了
fast_rcnn_inception_v2_coco ,它的mAP(验证集上检测器的性能)为28,执行速度为58ms,
非常强大,下载地址为:
http://download.tensorflow.org/models/object_detection/faster_rcnn_inception_v2_coco_
2018_01_28.tar.gz

2.人员检测
使用上述模型检测人员,必须完成一些步骤:
·将包含模型的文件加载到TensorFlow图中,并定义我们想从模型获得的输出。
·对于每一帧,将图像输入到TensorFlow图以获取所需的输出。
·过滤掉弱预测和不需要检测的物体。

加载并启动模型:
TensorFlow模型的工作方式是使用graphs(图)。第一步意味着将模型加载到TensorFlow图中,该图
将包含所需检测。下一步是创建一个session(会话),该会话是负责执行定义在图中操作的一个实
体。有关图和会话的更多说明,参见https://danijar.com/what-is-a-tensorflow-session/ 。在这
里我们实现了一个类,将与TensorFlow图有关的所有数据关联在一起。

1 class Model:
https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489175&idx=1&sn=7e977c6402250fc5c710a59e0e91d4bc&chksm=fb56f47b… 2/8
2020/7/29 使用TensorFlow物体检测模型、Python和OpenCV的社交距离检测器

2 """
3 Class that contains the model and all its functions
4 """
5 def __init__(self, model_path):
6 """
7 Initialization function
8 @ model_path : path to the model
9 """
10
11
# Declare detection graph
12
self.detection_graph = tf.Graph()
13
# Load the model into the tensorflow graph
14
with self.detection_graph.as_default():
15
od_graph_def = tf.compat.v1.GraphDef()
16
with tf.io.gfile.GFile(model_path, 'rb') as file:
17
serialized_graph = file.read()
18
od_graph_def.ParseFromString(serialized_graph)
19
tf.import_graph_def(od_graph_def, name='')
20
21
# Create a session from the detection graph
22
self.sess = tf.compat.v1.Session(graph=self.detection_graph)
23
24
25
def predict(self,img):

26 """

27 Get the predicition results on 1 frame


28 @ img : our img vector
29 """
30 # Expand dimensions since the model expects images to have shape: [1, None
31 img_exp = np.expand_dims(img, axis=0)
32 # Pass the inputs and outputs to the session to get the results
33 (boxes, scores, classes) = self.sess.run([self.detection_graph.get
return (boxes, scores, classes)

通过模型传递每一帧
对于需要处理的每个帧,都会启动一个新会话,这是通过调用run()函数完成的。这样做时必须指
定一些参数,这些参数包括模型所需的输入类型以及我们要从中获取的输出。在我们的案例中所需的
输出如下:

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489175&idx=1&sn=7e977c6402250fc5c710a59e0e91d4bc&chksm=fb56f47b… 3/8
2020/7/29 使用TensorFlow物体检测模型、Python和OpenCV的社交距离检测器

·每个物体的边界框坐标
·每个预测的置信度(0到1)
·预测类别(0到90)
·过滤弱预测和不相关物体

人员检测结果

模 型 能 检 测 到 的 很 多 物 体 类 别 , 其 中 之 一 是 人 并 且 与 其 关 联 的 类 为 1 。 为 了 排 除 弱 预 测 ( 阈值:
0.75)和除人以外的所有其他类别的物体,我使用了if语句,将这两个条件结合起来以排除任何其他
物体,以免进一步计算。

1 if int(classes[i]) == 1 and scores[i] > 0.75

但是因为这些模型已经经过预训练,不可能仅检测此类(人)。因此,这些模型要花很长时间才能运
行,因为它们试图识别场景中所有90种不同类型的物体。

3.鸟瞰图转换
如引言中所述,执行鸟瞰图转换可为我们提供场景的俯视图。值得庆幸的是OpenCV具有强大的内置
函数,此函数可以将从透视图角度拍摄的图像转换为俯视图。我使用了Adrian Rosebrock的教程 来
了 解 如 何 做 到 这 一 点 : https://www.pyimagesearch.com/2014/08/25/4-point-opencv-
getperspective-transform-example/

第一步选择原始图像上的4个点,这些点将成为要转换的图的角点。这些点必须形成一个矩形,至少
两个相对的边平行,如果不这样做,则转换发生时的比例将不同。我已经在我的仓 库中 实现了一个

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489175&idx=1&sn=7e977c6402250fc5c710a59e0e91d4bc&chksm=fb56f47b… 4/8
2020/7/29 使用TensorFlow物体检测模型、Python和OpenCV的社交距离检测器

脚本,该脚本使用OpenCV的setMouseCallback()函数来获取这些坐标。计算变换矩阵的函数还
需要使用图像的image.shape属性计算图像尺寸。

1 width, height, _ = image.shape

这将返回宽度、高度和其他不相关的颜色像素值。让我们看看如何使用它们计算变换矩阵:

1 def compute_perspective_transform(corner_points,width,height,image):
2 """ Compute the transformation matrix
3 @ corner_points : 4 corner points selected from the image
4 @ height, width : size of the image
5 return : transformation matrix and the transformed image
6 """
7 # Create an array out of the 4 corner points
8
corner_points_array = np.float32(corner_points)
9
# Create an array with the parameters (the dimensions) required to build t
10
img_params = np.float32([[0,0],[width,0],[0,height],[width,height]])
11
# Compute and return the transformation matrix
12
matrix = cv2.getPerspectiveTransform(corner_points_array,img_params)
13
img_transformed = cv2.warpPerspective(image,matrix,(width,height))
14
return matrix,img_transformed

注意函数的返回值是矩阵,因为在下一步中将使用这个矩阵计算每个被检测到的人的新坐标,新坐标
是帧中每个人的“ GPS”坐标,使用这些新坐标而不是使用原始基点结果更为准确,因为在透视图中当
人们处于不同平面时,距离是不一样的,并且距相机的距离也不相同。与使用原始检测框中的点相
比,这可以大大改善社会距离的测量。

对于检测到的每个人,将返回构建边界框所需的2个点,这两个点是边界框的左上角和右下角。通过
获取两点之间的中点来计算边界框的质心,使用此结果,计算位于边界框底部中心的点的坐标,我认
为这一点(称为“基点”)是图像中人坐标的最佳表示。

然后使用变换矩阵为每个检测到的基点计算变换后的坐标。在检测到人之后,在每一帧上使用
cv2.perspectiveTransform()完成此操作。实现此任务的方式:

1 def compute_point_perspective_transformation(matrix,list_downoids):
2 """ Apply the perspective transformation to every ground point which have
3 @ matrix : the 3x3 matrix
https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489175&idx=1&sn=7e977c6402250fc5c710a59e0e91d4bc&chksm=fb56f47b… 5/8
2020/7/29 使用TensorFlow物体检测模型、Python和OpenCV的社交距离检测器

4 @ list_downoids : list that contains the points to transform


5 return : list containing all the new points
6 """
7 # Compute the new coordinates of our points
8 list_points_to_detect = np.float32(list_downoids).reshape(-1, 1, 2)
9 transformed_points = cv2.perspectiveTransform(list_points_to_detect, mat
10 # Loop over the points and add them to the list that will be returned
11
transformed_points_list = list()
12
for i in range(0,transformed_points.shape[0]):
13
transformed_points_list.append([transformed_points[i][0][0],transforme
14
return transformed_points_list

4.社交距离测量
在每帧上调用此函数后,将返回一个包含所有新转换点的列表,从这个列表中,计算每对点之间的距
离。这里我使用了itertools库的Combination()函数,该函数允许在列表中获取所有可能的组合
而 无 需 保 留 双 精 度 。 在 https://stackoverflow.com/questions/104420/how-to-generate-all-
permutations-of-a-list 堆栈溢出问题中对此进行了 很好的解释。其余的是简单的数学运算:使用
math.sqrt()函数计算两点之间的距离。选择的阈值为120像素,因为它在我们的场景中大约等于2
英尺。

1 # Check if 2 or more people have been detected (otherwise no need to detec


2 if len(transformed_downoids) >= 2:
3 # Iterate over every possible 2 by 2 between the points combinations
4 list_indexes = list(itertools.combinations(range(len(transformed_downoids)
5 for i,pair in enumerate(itertools.combinations(transformed_downoids, r=2))
6 # Check if the distance between each combination of points is less t
7 if math.sqrt( (pair[0][0] - pair[1][0])**2 + (pair[0][1] - pair[1][1])**2
8 # Change the colors of the points that are too close from each oth
9
change_color_topview(pair)
10
# Get the equivalent indexes of these points in the original frame
11
index_pt1 = list_indexes[i][0]
12
index_pt2 = list_indexes[i][1]
13
change_color_originalframe(index_pt1,index_pt2)

一旦确定两个点之间的距离太近,标记该点的圆圈的颜色将从绿色更改为红色,原始框架上的边界框
的颜色也做相同的颜色变换操作。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489175&idx=1&sn=7e977c6402250fc5c710a59e0e91d4bc&chksm=fb56f47b… 6/8
2020/7/29 使用TensorFlow物体检测模型、Python和OpenCV的社交距离检测器

5.结果
回顾项目的工作原理:
·首先获取图的4个角点,然后应用透视变换获得该图的鸟瞰图并保存透视变换矩阵。
·获取原始帧中检测到的每个人的边界框。
·计算这些框的最低点,最低点是位于人双脚之间的点。
·对这些点应用变换矩阵,获取每一个人的真实“ GPS”坐标。
·使用itertools.combinations()测量帧中每个点到所有其它点的距离。
·如果检测到违反社交距离,将边框的颜色更改为红色。
我使用来自PETS2009 数据集http://www.cvg.reading.ac.uk/PETS2009/a.html#s0 的视频,该视
频由包含不同人群活动的多传感器序列组成,它最初是为诸如人群中人员计数和密度估计之类的任务
而构建的。我决定从第一个角度使用视频,因为它是最宽的一个,具有最佳的场景视角。该视频介绍
了获得的结果:
https://youtu.be/3b2GPwN2_I0

6.结论与改进
如今,隔离以及其他基本卫生措施对抑制Covid-19的传播速度非常重要。但该项目仅是概念的证明,
并且由于道德和隐私问题,不能用于监视公共或私人区域的社交距离。

这个项目存在一些小的缺陷,改进思路如下:
·使用更快的模型来执行实时社交距离分析。
·使用对遮挡更具鲁棒性的模型。
·自动校准是计算机视觉中一个众所周知的问题,可以在不同场景上极大地改善鸟瞰图的转换。

7.参考资料

https://towardsdatascience.com/analyse-a-soccer-game-using-tensorflow-object-
detection-and-opencv-e321c230e8f2
https://www.pyimagesearch.com/2014/08/25/4-point-opencv-getperspective-transform-
example/
https://developer.ridgerun.com/wiki/index.php?
title=Birds_Eye_View/Introduction/Research
http://www.cvg.reading.ac.uk/PETS2009/a.html#s0

如果本文对小伙伴有帮助,希望可以在文末来个“一键三连”。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489175&idx=1&sn=7e977c6402250fc5c710a59e0e91d4bc&chksm=fb56f47b… 7/8
2020/7/29 使用TensorFlow物体检测模型、Python和OpenCV的社交距离检测器

交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、
检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),请扫描下面微信号加
群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备
注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会
请出群,谢谢理解~

//
//

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489175&idx=1&sn=7e977c6402250fc5c710a59e0e91d4bc&chksm=fb56f47b… 8/8
2020/7/29 使用深度学习和OpenCV的早期火灾检测系统

使用深度学习和OpenCV的早期火灾检测系统
原创 花生 小白学视觉 1周前

点击上方“小白学视觉”,选择加"星标"或“置顶”

重磅干货,第一时间送达

创建用于室内和室外火灾检测的定制InceptionV3和CNN架构。

嵌入式处理技术的最新进展已使基于视觉的系统可以在监视过程中使用卷积神经网络检测火灾。在本文
中,两个定制的CNN模型已经实现,它们拥有用于监视视频的高成本效益的火灾检测CNN架构。第一个模型
是 受 AlexNet 架 构 启 发 定 制 的 基 本 CNN 架 构 。 我 们 将 实 现 和 查 看 其 输 出 和 限 制 , 并 创 建 一 个 定 制 的
InceptionV3模型。为了平衡效率和准确性,考虑到目标问题和火灾数据的性质对模型进行了微调。我们
将使用三个不同的数据集来训练我们的模型。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489440&idx=1&sn=a441f2d47223b668a6ac6c4d9ea4e734&chksm=fb56f54c… 1/8
2020/7/29 使用深度学习和OpenCV的早期火灾检测系统

创建定制的CNN架构

我们将使用TensorFlow API Keras构建模型。首先,我们创建用于标记数据的ImageDataGenerator。[1]


和[2]数据集在这里用于训练。最后,我们将提供980张图像用于训练和239张图像用于验证。我们也将使
用数据增强。

1 import tensorflow as tf
2 import keras_preprocessing
3 from keras_preprocessing import image
4 from keras_preprocessing.image import ImageDataGenerator
5 TRAINING_DIR = "Train"
6 training_datagen = ImageDataGenerator(rescale = 1./255,
7 horizontal_flip=True,
8 rotation_range=30,
9 height_shift_range=0.2,
10 fill_mode='nearest')
11 VALIDATION_DIR = "Validation"
12 validation_datagen = ImageDataGenerator(rescale = 1./255)
13 train_generator = training_datagen.flow_from_directory(TRAINING_DIR,
14 target_size=(224,224),
15
16 class_mode='categorical',
17 batch_size = 64)
18 validation_generator = validation_datagen.flow_from_directory(
19 VALIDATION_DIR,
20 target_size=(224,224),
21
22 class_mode='categorical',
23 batch_size= 16)

在上面的代码中应用了3种数据增强技术,它们分别是水平翻转,旋转和高度移位。

现在,我们将创建我们的CNN模型。该模型包含三对Conv2D-MaxPooling2D层,然后是3层密集层。为了克
服过度拟合的问题,我们还将添加dropout层。最后一层是softmax层,它将为我们提供火灾和非火灾两类
的概率分布。通过将类数更改为1,还可以在最后一层使用‘Sigmoid’激活函数。

1 from tensorflow.keras.optimizers import Adam


2 model = tf.keras.models.Sequential([
3 tf.keras.layers.Conv2D(96, (11,11), strides=(4,4), activation='relu', input_shape=(2
4 tf.keras.layers.Conv2D(256, (5,5), activation='relu'),
5 tf.keras.layers.MaxPooling2D(pool_size = (3,3), strides=(2,2)),

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489440&idx=1&sn=a441f2d47223b668a6ac6c4d9ea4e734&chksm=fb56f54c… 2/8
2020/7/29 使用深度学习和OpenCV的早期火灾检测系统

6 tf.keras.layers.Conv2D(384, (5,5), activation='relu'),


7 tf.keras.layers.MaxPooling2D(pool_size = (3,3), strides=(2,2)),
8 tf.keras.layers.Flatten(),
9 tf.keras.layers.Dropout(0.2),
10 tf.keras.layers.Dense(2048, activation='relu'),
11 tf.keras.layers.Dropout(0.25),
12 tf.keras.layers.Dense(1024, activation='relu'),
13 tf.keras.layers.Dropout(0.2),
14 tf.keras.layers.Dense(2, activation='softmax')])model.compile(loss='categorical_cros
15 optimizer=Adam(lr=0.0001),
16 metrics=['acc'])history = model.fit(
17 train_generator,
18 steps_per_epoch = 15,
19 epochs = 50,
20 validation_data = validation_generator,
21 validation_steps = 15
22 )

我们将使用Adam作为学习率为0.0001的优化器。经过50个时期的训练,我们得到了96.83的训练精度和
94.98的验证精度。训练损失和验证损失分别为0.09和0.13。

我们的训练模型

让我们测试模型中的所有图像,看看它的猜测是否正确。为了进行测试,我们选择了3张图像,其中包括
有火的图像,没有火的图像以及包含火样颜色和阴影的照片。

我们最终得到上面创建的模型在对图像进行分类时犯了一个错误。该模型52%的把握确定图像中有火焰。
这是因为已进行训练的数据集中几乎没有图像可以说明室内火灾的模型。所以该模型仅知道室外火灾情
况,而当给出一张室内火样的阴影图像时会出现错误。另一个原因是我们的模型不具备可以学习火的复杂
特征。

接下来,我们将使用标准的InceptionV3模型并对其进行自定义。复杂模型能够从图像中学习复杂特征。

创建定制的InceptionV3模型

这次我们将使用不同的数据集[3],其中包含室外和室内火灾图像。我们已经在该数据集中训练了我们之
前的CNN模型,结果表明它是过拟合的,因为它无法处理这个相对较大的数据集和从图像中学习复杂的特
https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489440&idx=1&sn=a441f2d47223b668a6ac6c4d9ea4e734&chksm=fb56f54c… 3/8
2020/7/29 使用深度学习和OpenCV的早期火灾检测系统

征。

我们开始为自定义的InceptionV3创建ImageDataGenerator。数据集包含3个类,但对于本文,我们将仅使
用2个类。它包含用于训练的1800张图像和用于验证的200张图像。另外,我添加了8张客厅图像,以在数
据集中添加一些噪点。

1 import tensorflow as tf
2 import keras_preprocessing
3 from keras_preprocessing import image
4 from keras_preprocessing.image import ImageDataGeneratorTRAINING_DIR = "Train"
5 training_datagen = ImageDataGenerator(rescale=1./255,
6 zoom_range=0.15,
7 horizontal_flip=True,
8 fill_mode='nearest')VALIDATION_DIR = "/content/FIRE-SMOKE-DATASET/Test"
9 validation_datagen = ImageDataGenerator(rescale = 1./255)train_generator = training_
10 TRAINING_DIR,
11 target_size=(224,224),
12 shuffle = True,
13 class_mode='categorical',
14 batch_size = 128)validation_generator = validation_datagen.flow_from_directory(
15 VALIDATION_DIR,
16 target_size=(224,224),
17 class_mode='categorical',
18 shuffle = True,
19 batch_size= 14)

为了使训练更加准确,我们可以使用数据增强技术。在上面的代码中应用了2种数据增强技术-水平翻转和
缩放。

让我们从Keras API导入InceptionV3模型。我们将在InceptionV3模型的顶部添加图层,如下所示。我们
将添加一个全局空间平均池化层,然后是2个密集层和2个dropout层,以确保我们的模型不会过拟合。最
后,我们将为2个类别添加一个softmax激活的密集层。

接下来,我们将首先仅训练我们添加并随机初始化的图层。我们将在这里使用RMSprop作为优化器。

1 from tensorflow.keras.applications.inception_v3 import InceptionV3


2 from tensorflow.keras.preprocessing import image
3 from tensorflow.keras.models import Model
4 from tensorflow.keras.layers import Dense, GlobalAveragePooling2D, Input, Dropoutinp
5 base_model = InceptionV3(input_tensor=input_tensor, weights='imagenet', include_top=
6 x = GlobalAveragePooling2D()(x)

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489440&idx=1&sn=a441f2d47223b668a6ac6c4d9ea4e734&chksm=fb56f54c… 4/8
2020/7/29 使用深度学习和OpenCV的早期火灾检测系统

7 x = Dense(2048, activation='relu')(x)
8 x = Dropout(0.25)(x)
9 x = Dense(1024, activation='relu')(x)
10 x = Dropout(0.2)(x)
11 predictions = Dense(2, activation='softmax')(x)model = Model(inputs=base_model.input
12 layer.trainable = Falsemodel.compile(optimizer='rmsprop', loss='categorical_crosse
13 train_generator,
14 steps_per_epoch = 14,
15 epochs = 20,
16 validation_data = validation_generator,
17 validation_steps = 14)

在训练了顶层20个周期之后,我们将冻结模型的前249层,并训练其余的层(即顶层2个初始块)。在这
里,我们将使用SGD作为优化器,学习率为0.0001。

1 #To train the top 2 inception blocks, freeze the first 249 layers and unfreeze the re
2 layer.trainable = Falsefor layer in model.layers[249:]:
3 layer.trainable = True#Recompile the model for these modifications to take effectfr
4 model.compile(optimizer=SGD(lr=0.0001, momentum=0.9), loss='categorical_crossentropy'
5 train_generator,
6 steps_per_epoch = 14,
7 epochs = 10,
8 validation_data = validation_generator,
9 validation_steps = 14)

经过10个周期的训练,我们获得了98.04的训练准确度和96.43的验证准确度。训练损失和验证损失分别为
0.063和0.118。

以上10个时期的训练过程

我们用相同的图像测试我们的模型,看看是否它可以正确猜出。

这次我们的模型可以使所有三个预测正确。96%的把握可以确定图像中没有任何火。我用于测试的其他两
个图像如下:

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489440&idx=1&sn=a441f2d47223b668a6ac6c4d9ea4e734&chksm=fb56f54c… 5/8
2020/7/29 使用深度学习和OpenCV的早期火灾检测系统

 
来自下面引用的数据集中的非火灾图像

实时测试

现在,我们的模型已准备好在实际场景中进行测试。以下是使用OpenCV访问我们的网络摄像头并预测每帧
图像中是否包含火的示例代码。如果框架中包含火焰,我们希望将该框架的颜色更改为B&W。

1 import cv2
2 import numpy as np
3 from PIL import Image
4 import tensorflow as tf
5 from keras.preprocessing import image#Load the saved model
6 model = tf.keras.models.load_model('InceptionV3.h5')
7 video = cv2.VideoCapture(0)while True:
8 _, frame = video.read()#Convert the captured frame into RGB
9 im = Image.fromarray(frame, 'RGB')#Resizing into 224x224 because we trained
10 im = im.resize((224,224))
11 img_array = image.img_to_array(im)
12 img_array = np.expand_dims(img_array, axis=0) / 255
13 probabilities = model.predict(img_array)[0]
14 #Calling the predict method on model to predict 'fire' on the image
15 prediction = np.argmax(probabilities)
16 #if prediction is 0, which means there is fire in the frame.
17 if prediction == 0:
18 frame = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
19 print(probabilities[prediction])cv2.imshow("Capturing", frame)
20 key=cv2.waitKey(1)
21 if key == ord('q'):
22 break
23 video.release()
24 cv2.destroyAllWindows()

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489440&idx=1&sn=a441f2d47223b668a6ac6c4d9ea4e734&chksm=fb56f54c… 6/8
2020/7/29 使用深度学习和OpenCV的早期火灾检测系统

这个项目的Github链接在这里:https://github.com/DeepQuestAI/Fire-Smoke-Dataset。可以从那里找
到数据集和上面的所有代码。

结论

使用智能相机可以识别各种可疑事件,例如碰撞,医疗紧急情况和火灾。其中,火灾是最危险的异常事
件,因为在早期阶段无法控制火灾会导致巨大的灾难,从而造成人员,生态和经济损失。受CNN巨大潜力
的启发,我们可以在早期阶段从图像或视频中检测到火灾。本文展示了两种用于火灾探测的自定义模型。
考虑到CNN模型的火灾探测准确性,它可以帮助灾难管理团队按时管理火灾,从而避免巨额损失。

本文使用的数据集:
数据集1:https://www.kaggle.com/atulyakumar98/test-dataset
数据集2:https://www.kaggle.com/phylake1337/fire-dataset
https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489440&idx=1&sn=a441f2d47223b668a6ac6c4d9ea4e734&chksm=fb56f54c… 7/8
2020/7/29 使用深度学习和OpenCV的早期火灾检测系统

数据集3:https://github.com/DeepQuestAI/Fire-Smoke-Dataset
如果本文对小伙伴有帮助,希望可以在文末来个“一键三连”。

交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、
检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),请扫描下面微信号加
群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备
注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会
请出群,谢谢理解~

//
//

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247489440&idx=1&sn=a441f2d47223b668a6ac6c4d9ea4e734&chksm=fb56f54c… 8/8
2020/7/29 用OpenCV实现猜词游戏

用OpenCV实现猜词游戏
原创 小白 小白学视觉 2019-07-01

小伙伴们是不是在用OpenCV来处理图像处理的相关任务,从来没有想过还可以通过OpenCV设计一
款游戏,今天小白将为各位小伙伴们介绍如何通过OpenCV创建一个猜词的小游戏。为了增加趣味
性,我们给小游戏起了一个比较具有故事性的名字“刽子手游戏(Hangman)”,我们先来看一下该
游戏的视频。

00:34

这是一个猜电影名字的游戏,会在屏幕下方显示电影的单词数目以及每个单词的字母个数,我们需要
猜电影名字中含有的字母,如果猜测错误,右侧的刽子手处就会依次出现人头、身体、手和脚等,当
猜错6次之后,刽子手就会行动,游戏就输了。但是为了增加获胜几率,在出现错误的时候会给出关
于电影的部分提示,当把电影名字全部猜出后,我们就取得了胜利。

接下来将介绍如何实现这个有趣的小游戏,我们将本小游戏实现分成4个子功能模块,其构成如下图
所示。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247485904&idx=1&sn=5ccf74f836ab299b6f0fa8807864d535&chksm=fb56eb3c… 1/11
2020/7/29 用OpenCV实现猜词游戏

数据处理
为了确保游戏不会很快耗尽电影和线索,我们选择TMDb数据集(电影数据库),数据处理步骤包括
删除却是值,仅保留所需的内容-发行年份、演员表、导演、电影关键字和标语。同时为了减少数据集
大小,只保留标题不超过20个字符且标语不超过30的电影。

电影和线索选择
首先我们从CSV文件加载数据集并以字典格式存储它
def read_from_csv(csv_f):
  with open(csv_f,'r') as f:
    movie_data = {}
    for line in f.readlines():
      line_split = line.strip().split(",")
      year = line_split[-1].split("|")
      keywords = line_split[-2].split("|")
      tagline = line_split[-3].split("|")
      director = line_split[-4].split("|")
      cast = line_split[-5].split("|")
      movie = line_split[0].upper()
      movie_data[movie] = [year,keywords,tagline,director,cast]
  return movie_data

请注意,我们使用以下字典格式存储电影和电影详细信息。
movie_title:[year,list of keywords, tagline, director, list of cast]

接下来,我们将从电影列表中获取一部随机电影并获取该电影的信息(年份,关键字,标语,导演和
演员)
def get_movie_info(movies_data):
  movies_list = list(movies_data.keys())
  movie = np.random.choice(movies_list,1)[0].upper()
  movie_info = movies_data[movie]
  return movie,movie_info

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247485904&idx=1&sn=5ccf74f836ab299b6f0fa8807864d535&chksm=fb56eb3c… 2/11
2020/7/29 用OpenCV实现猜词游戏

我们将使用另一个函数select_hints来选择任意3个随机提示。如果关键字或强制转换作为提示存在,
我们将从它们中随机选择一个元素
def select_hints(movie_info):
# We will randomly select 3 types of
# hints to display
hints_index = list(np.random.choice(5,3,replace=False))
hints = []
hints_labels = ["Release Year","Keyword","Tagline","Director","Cast"]
labels = []
for hint_index in hints_index:
hint = np.random.choice(movie_info[hint_index],1)[0].upper()
hints.append(hint)
labels.append(hints_labels[hint_index].upper())
return hints,labels

编程显示
加载 Hangman Canvas
首先,我们显示下面的的hangman模板

def get_canvas(canvas_file):
  img = cv2.imread(canvas_file,1)
  return img

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247485904&idx=1&sn=5ccf74f836ab299b6f0fa8807864d535&chksm=fb56eb3c… 3/11
2020/7/29 用OpenCV实现猜词游戏

获得角色尺寸
接下来,这里有一个棘手的部分。我们想要显示空白框,以便用户知道电影标题的长度。考虑到角色
的宽度和高度,我们将使它更有趣和更通用。我们将使用该标题中所有字符的最大高度和宽度。我们
将使用函数get_char_coords来计算这些框的坐标。
def get_char_coords(movie):
  x_coord = 100
  y_coord = 400

  char_ws = []
  char_hs = []

  for i in movie:
    char_width, char_height = cv2.getTextSize(i,\
      cv2.FONT_HERSHEY_SIMPLEX,1,2)[0]
    char_ws.append(char_width)
    char_hs.append(char_height)

  max_char_h = max(char_hs)
  max_char_w = max(char_ws)

  char_rects = []

  for i in range(len(char_ws)):
    rect_coord = [(x_coord,y_coord-max_char_h),\
      (x_coord+max_char_w,y_coord)]
    char_rects.append(rect_coord)
    x_coord = x_coord + max_char_w
  return char_rects

画空白框
一旦我们有这些坐标,我们将使用它们来绘制框。
def draw_blank_rects(movie,char_rects,img):
  for i in range(len(char_rects)):
    top_left, bottom_right = char_rects[i]
    if not movie[i].isalpha() or \
       ord(movie[i]) < 65 or \
       ord(movie[i]) > 122 or \
       (ord(movie[i]) > 90 and \
       ord(movie[i]) < 97):
      cv2.putText(img,movie[i],(top_left[0],bottom_right[1]),cv2.FONT_HERSHEY_SIMPLEX,1,(0,
      continue
    cv2.rectangle(img,top_left,\bottom_right,(0,0,255),thickness=1,lineType = cv2.LINE_8)
  return img

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247485904&idx=1&sn=5ccf74f836ab299b6f0fa8807864d535&chksm=fb56eb3c… 4/11
2020/7/29 用OpenCV实现猜词游戏

显示提示
要显示的提示取决于不正确尝试的次数。如果没有不正确的尝试,我们将不会显示任何提示。如果一
次尝试不正确,我们将显示第一个提示。类似地,对于少于4次不正确的尝试,我们将显示第二个提
示,最后,对于少于7个不正确的尝试,我们将显示第三个提示。
def draw_hint(img,hints,labels,incorrect_attempts):
  x,y = 20,30
  if incorrect_attempts == 0:
    return img
  elif incorrect_attempts <= 1:
    index = 0
  elif incorrect_attempts <= 3:
    index = 1
  elif incorrect_attempts <= 6:
    index = 2
  cv2.putText(img,"HINT: {}".format(labels[index]),(x,y),cv2.FONT_HERSHEY_SIMPLEX,0.6,(255,0,
  cv2.putText(img,"{}".format(hints[index]),(x,y+30),cv2.FONT_HERSHEY_SIMPLEX,0.6,(255,0,255)
  return img

显示正确或不正确的尝试
如果电影标题中出现猜测的字母,需要进行提示。
def draw_wrong(img,incorrect_attempts):
  cv2.putText(img,"WRONG {}/6".format(incorrect_attempts+1),(380,40),\
  cv2.FONT_HERSHEY_SIMPLEX,1,(0,0,255),2)
  return img

def draw_right(img):
  cv2.putText(img,"RIGHT",(380,40),cv2.FONT_HERSHEY_SIMPLEX,0.7,(0,255,0),2)
  return img

显示游戏输赢
如果游戏赢了或输了,同样需要进行提示
def draw_lost(img):
  cv2.putText(img,"YOU LOST",(380,40),cv2.FONT_HERSHEY_SIMPLEX,0.7,(0,0,255),2)
  return img

def draw_won(img):
  cv2.putText(img,"YOU WON",(380,40),cv2.FONT_HERSHEY_SIMPLEX,0.7,(0,255,0),2)
  return img

显示无效的移动和字符重用
现在,我们还有两个案例需要考虑。如果输入了无效字符怎么办?这可以是数字或非字母数字字符。
第二,如果用户输入之前已输入的角色,该怎么办

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247485904&idx=1&sn=5ccf74f836ab299b6f0fa8807864d535&chksm=fb56eb3c… 5/11
2020/7/29 用OpenCV实现猜词游戏

def draw_invalid(img):
  cv2.putText(img,"INVALID INPUT",(300,40),cv2.FONT_HERSHEY_SIMPLEX,0.7,(0,0,255),2)
  return img

def draw_reuse(img):
  cv2.putText(img,"ALREADY USED",(300,40),cv2.FONT_HERSHEY_SIMPLEX,0.7,(0,0,255),2)
  return img

显示使用的字符
现在,为了让用户更容易知道已经使用了哪些字符,我们也将显示它们。我们将在这里再添一个。我

们将显示以红色输入的有效字符,以便用户可以看到他们输入的字符。

def draw_used_chars(img,chars_entered,letter):
  cv2.putText(img,"Letters used:",(300,80),cv2.FONT_HERSHEY_SIMPLEX,0.5,(0,0,0),1)
  y = 120
  x = 350
  count = 0
  for i in chars_entered:
    if count == 10:
      x += 50
      y = 120
    if i==letter:
      cv2.putText(img,i,(x,y),cv2.FONT_HERSHEY_SIMPLEX,0.5,(0,0,255),1)
    else:
      cv2.putText(img,i,(x,y),cv2.FONT_HERSHEY_SIMPLEX,0.5,(0,0,0),1)
    y += 20
    count += 1
  return img

显示刽子手身体
接下来轮到编写函数来绘制刽子手的不同身体部位 - 面部,背部,左右手臂,左右腿。将绘制什么身
体部位取决于不正确尝试的次数
def draw_hangman(img,num_tries):
  if num_tries==1:
    return draw_circle(img)
  elif num_tries==2:
    return draw_back(img)
  elif num_tries==3:
    return draw_left_hand(img)
  elif num_tries==4:
    return draw_right_hand(img)
  elif num_tries==5:
    return draw_left_leg(img)
  elif num_tries==6:
    return draw_right_leg(img)

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247485904&idx=1&sn=5ccf74f836ab299b6f0fa8807864d535&chksm=fb56eb3c… 6/11
2020/7/29 用OpenCV实现猜词游戏

  else:
    return img

def draw_circle(img):
    cv2.circle(img,(190,160),40,(0,0,0),thickness=2,lineType=cv2.LINE_AA)
  return img

def draw_back(img):
    cv2.line(img,(190,200),(190,320),(0,0,0),thickness=2,lineType=cv2.LINE_AA)
  return img

def draw_left_hand(img):
    cv2.line(img,(190,240),(130,200),(0,0,0),thickness=2,lineType=cv2.LINE_AA)
  return img

def draw_right_hand(img):
  cv2.line(img,(190,240),(250,200),(0,0,0),thickness=2,lineType=cv2.LINE_AA)
  return img

def draw_left_leg(img):
  cv2.line(img,(190,320),(130,360),(0,0,0),thickness=2,lineType=cv2.LINE_AA)
  return img

def draw_right_leg(img):
  cv2.line(img,(190,320),(250,360),(0,0,0),thickness=2,lineType=cv2.LINE_AA)
  return img

显示字符出现次数
我们还希望显示在电影标题中输入的所有字母(如果它出现在电影标题中)。
def displayLetter(img,letter,movie,char_rects):
  for i in range(len(movie)):
    if movie[i]==letter:
      top_left, bottom_right = char_rects[i]
      cv2.putText(img, movie[i],(top_left[0],bottom_right[1]),cv2.FONT_HERSHEY_SIMPLEX,1,(2
  return img

显示电影标题
最后,我们想在游戏结束后揭示正确的电影片名。
def revealMovie(movie,img,char_rects):
#img = cv2.imread(canvas_file,1)
  for i in range(len(movie)):
    top_left, bottom_right = char_rects[i]
    cv2.putText(img,movie[i],(top_left[0],bottom_right[1]),cv2.FONT_HERSHEY_SIMPLEX,1,(0,255
  return img

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247485904&idx=1&sn=5ccf74f836ab299b6f0fa8807864d535&chksm=fb56eb3c… 7/11
2020/7/29 用OpenCV实现猜词游戏

编程游戏
现在我们已经涵盖了所有功能,让我们看看我们将如何在游戏中使用它们。我们将从读取CSV文件中

的数据并获取随机电影开始。

import cv2
import numpy as np
from utils import *

movie_csv = "movies-list-short.csv"
canvas = "blank-canvas.png"

movies_data = read_from_csv(movie_csv)

movie, movie_info = get_movie_info(movies_data)

我们还将选择之前讨论的3个随机提示。
hints,labels = select_hints(movie_info)

现在,让我们从空白的Hangman画布开始,零尝试不正确。
char_rects = get_char_coords(movie)

img = draw_blank_rects(movie,char_rects,img)

cv2.namedWindow("Hangman", cv2.WND_PROP_FULLSCREEN)
cv2.setWindowProperty("Hangman",cv2.WND_PROP_FULLSCREEN,cv2.WINDOW_FULLSCREEN)

cv2.imshow("Hangman",img)

chars_entered = []

incorrect_attempts = 0

img_copy = img.copy()

接下来,我们将对该部分进行编码,以便从用户那里获取输入并显示输入的字母。我们还需要显示尝

试是正确还是错误,或者是否无效或已经使用过。如果用户用完了尝试,循环将中断。

我们通过以下方式实现上述目标。

1. 创建当前图像的副本。这是为了确保我们不会覆盖诸如错误,正确等字样或提示。
2. 接下来,根据不正确的尝试次数,我们将在图像上显示提示。
3. 如果用户已经用完了所有的生命,我们将显示您丢失并且循环将中断。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247485904&idx=1&sn=5ccf74f836ab299b6f0fa8807864d535&chksm=fb56eb3c… 8/11
2020/7/29 用OpenCV实现猜词游戏

4. 如果用户设法猜出电影的所有字符,我们将显示你WON并打破循环。
5. 要检查用户输入的字符是否有效,我们将检查字符是否位于az或AZ之间。如果移动无效,
我们将显示相应的消息 - INVALID MOVE,游戏将继续。
6. 将检查用户输入的有效字符以查看它之前是否已被使用过,在这种情况下将显示相应的消
息并且游戏将继续。
7. 请注意,在最后两个步骤中,不会更改不正确的尝试次数。
8. 如果输入的字符是新字符,我们将首先将其附加到所用字符列表中,然后检查它是否出现
在电影标题中,在这种情况下,我们将显示CORRECT并显示电影中所有出现的字符。
9. 如果在电影标题中找不到该字符,我们将显示错误并增加错误尝试次数。
10. 最后,一旦游戏获胜或失败,我们将揭示正确的电影标题。
while 1:
  img = img_copy.copy()
  img = draw_hint(img,hints,labels,incorrect_attempts)
  if incorrect_attempts >= 6:
    img = draw_lost(img)
    break
  elif check_all_chars_found(movie, chars_entered):
    img = draw_won(img)
    break
  else:
    letter = cv2.waitKey(0) &amp; 0xFF
    if letter < 65 or letter > 122 or (letter > 90 and letter < 97):
      img = draw_invalid(img)
      cv2.imshow("Hangman",img)
      continue
    else:
      letter = chr(letter).upper()
    if letter in chars_entered:
      img = draw_reuse(img)
      img = draw_used_chars(img,chars_entered,letter)
      cv2.imshow("Hangman",img)
      continue
    else:
      chars_entered.append(letter)
      if letter in movie:
        img = draw_right(img)
        img = displayLetter(img,letter,movie,char_rects)
        img_copy = displayLetter(img_copy,letter,movie,char_rects)
      else:
        img = draw_wrong(img,incorrect_attempts)
         incorrect_attempts += 1
  img = draw_used_chars(img,chars_entered,letter)
  img = draw_hangman(img,incorrect_attempts)
  img_copy = draw_used_chars(img_copy,chars_entered,letter)
  img_copy = draw_hangman(img_copy,incorrect_attempts)

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247485904&idx=1&sn=5ccf74f836ab299b6f0fa8807864d535&chksm=fb56eb3c… 9/11
2020/7/29 用OpenCV实现猜词游戏

  cv2.imshow("Hangman",img)

img = revealMovie(movie,img,char_rects)
cv2.imshow("Hangman",img)
cv2.waitKey(0)

cv2.destroyAllWindows()

最后,让我们借助下面显示的图像快速总结一下Hangman游戏的不同部分。

往期文章一览

1、人脸识别中的活体检测算法综述
2、CVPR2019:PizzaGAN通过深度学习制作披萨
3、漫话:如何给女朋友解释为什么计算机只认识0和1?
4、10个不得不知的Python图像处理工具,非常全了!
5、OpenCV4.0实现人脸识别
6、基于内容的图像检索技术综述-传统经典方法
7、为什么不建议你入门计算机视觉
8、机器视觉检测系统中这些参数你都知道么?

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247485904&idx=1&sn=5ccf74f836ab299b6f0fa8807864d535&chksm=fb56eb3… 10/11
2020/7/29 用OpenCV实现猜词游戏

加群交流

扫码添加助手,可申请加入OpenCV 与数字图像处理交流群。一定要备注:图像处理 + 地点 + 学校 / 公司
+ 昵称 (如目标检测+上海+上交+卡卡西),不根据格式申请,一律不通过。

//
//

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247485904&idx=1&sn=5ccf74f836ab299b6f0fa8807864d535&chksm=fb56eb3… 11/11
2020/9/30 基于 OpenCV 的图像分割

基于 OpenCV 的图像分割
原创 努比 小白学视觉 1周前

点击上方“小白学视觉”,选择加"星标"或“置顶”
重磅干货,第一时间送达

本期我们将一起来实现一个有趣的问题 -图像分割的算法。

本文的示例代码可以在以下链接中找到:

https://github.com/kiteco/kite-python-blog-post-code/tree/master/image-
segmentation

作为我们的例子,我们将对KESM显微镜获取的图像进行分割以获取其中的血管组
织。

数据科学家和医学研究人员可以将这种方法作为模板,用于更加复杂的图像的数据集
(如天文数据),甚至一些非图像数据集中。由于图像在计算机中表示为矩阵,我们
有一个专门的排序数据集作为基础 。 在整个处理过程中,我们将使用 Python 包,以
及OpenCV、scikit 图像等几种工具。除此之外,我们还将使用 numpy ,以确保内存
中的值一致存储。

主要内容
https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247491154&idx=1&sn=a9b4ce79edcac1931408265c3330efc3&chksm=fb56fcb… 1/16
2020/9/30 基于 OpenCV 的图像分割

去噪

为了消除噪声,我们使用简单的中位数滤波器来移除异常值,但也可以使用一些不同
的噪声去除方法或伪影去除方法。这项工件由采集系统决定(显微镜技术),可能需
要复杂的算法来恢复丢失的数据。工件通常分为两类:

1. 模糊或焦点外区域

2. 不平衡的前景和背景(使用直方图修改正确)

分割

对于本文,我们使用Otsu 的方法分割,使用中位数滤波器平滑图像后,然后验证结
果。只要分段结果是二进制的,就可以对任何分段算法使用相同的验证方法。这些算
法包括但不限于考虑不同颜色空间的各种循环阈值方法。

一些示例包括:

1. 李阈值

2. 依赖于局部强度的自适应阈值方法

3. 在生物医学图像分割中常用的Unet等深度学习算法

4. 在语义上对图像进行分段的深度学习方法

验证

我们从已手动分割的基础数据集开始。为了量化分段算法的性能,我们将真实数据与
预测数据的二进制分段进行比较,同时显示准确性和更有效的指标。尽管真阳性
(TP) 或假阴性 (FN) 数量较低,但精度可能异常高。在这种情况下,F1 分数
和 MCC是二进制分类的更好量化指标。稍后我们将详细介绍这些指标的优缺点。

为了定性验证,我们叠加混淆矩阵结果,即真正的正极、真负数、假阳性、假负数像
素正好在灰度图像上。此验证也可以应用于二进制图像分割结果上的颜色图像,尽管
本文中使用的数据是灰度图像。最后,我们将介绍整个实现过程。现在,让我们看看
数据和用于处理这些数据的工具。

Loading and visualizing data

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247491154&idx=1&sn=a9b4ce79edcac1931408265c3330efc3&chksm=fb56fcb… 2/16
2020/9/30 基于 OpenCV 的图像分割

我们将使用以下模块加载、可视化和转换数据。这些对于图像处理和计算机视觉算法
非常有用,具有简单而复杂的数组数学。如果单独安装,括号中的模块名称会有所帮
助。

如果在运行示例代码中,遇到 matplotlib 后端的问题,请通过删除 plt.ion() 调用


来禁用交互式模式,或是通过取消注释示例代码中的建议调用来在每个部分的末尾调
用 plt.show()。"Agg"或"TkAgg"将作为图像显示的后端。绘图将显示在文章中。

代码导入

1 import cv2
2 import matplotlib.pyplot as plt
3 import numpy as np
4 import scipy.misc
5 import scipy.ndimage
6 import skimage.filters
7 import sklearn.metrics
8 # Turn on interactive mode. Turn off with plt.ioff()
9 plt.ion()

在本节中,我们将加载可视化数据。数据是小老鼠脑组织与印度墨水染色的图像,由
显微镜(KESM)生成。此 512 x 512 图像是一个子集,称为图块。完整的数据集为
17480 x 8026 像素,深度为 799,大小为 10gb。因此,我们将编写算法来处理大小
为 512 x 512 的图块,该图块只有 150 KB。

各个图块可以映射为在多处理/多线程(即分布式基础结构)上运行,然后再缝合在一
起即获得完整的分段图像。我们不介绍具体的缝合方法。简而言之,拼接涉及对整个
矩阵的索引并根据该索引将图块重新组合。可以使用map-reduce进行,Map-Reduce

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247491154&idx=1&sn=a9b4ce79edcac1931408265c3330efc3&chksm=fb56fcb… 3/16
2020/9/30 基于 OpenCV 的图像分割

的指标例如所有图块的所有F1分数之和等。我们只需将结果添加到列表中,然后执行
统计摘要即可。

左侧的黑色椭圆形结构是血管,其余的是组织。因此,此数据集中的两个类是:

• 前景(船只)—标记为255

• 背景(组织)—标记为0

右下方的最后一个图像是真实图像。通过绘制轮廓并填充轮廓以手动方式对其进行追
踪,通过病理学家获得真实情况。我们可以使用专家提供的类似示例来训练深度学习
网络,并进行大规模验证。我们还可以通过将这些示例提供给其他平台并让他们以更
大的比例手动跟踪一组不同的图像以进行验证和培训来扩充数据。

1 grayscale = scipy.misc.imread('grayscale.png')
2 grayscale = 255 - grayscale
3 groundtruth = scipy.misc.imread('groundtruth.png')
4 plt.subplot(1, 3, 1)
5 plt.imshow(255 - grayscale, cmap='gray')
6 plt.title('grayscale')
7 plt.axis('off')
8 plt.subplot(1, 3, 2)
9 plt.imshow(grayscale, cmap='gray')
10 plt.title('inverted grayscale')
11 plt.axis('off')
12 plt.subplot(1, 3, 3)
13 plt.imshow(groundtruth, cmap='gray')
14 plt.title('groundtruth binary')
15 plt.axis('off')

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247491154&idx=1&sn=a9b4ce79edcac1931408265c3330efc3&chksm=fb56fcb… 4/16
2020/9/30 基于 OpenCV 的图像分割

前处理

在分割数据之前,我们应该检查一下数据集,以确定是否存在由于成像系统而造成了
伪影。在此示例中,我们仅讨论一个图像。通过查看图像,我们可以看到没有任何明
显的伪影会干扰分割。但是,小伙伴们可以使用中值滤镜消除离群值噪声并平滑图
像。中值过滤器用中值(在给定大小的内核内)替换离群值。

内核大小3的中值过滤器

1 median_filtered = scipy.ndimage.median_filter(grayscale, size=3)


2 plt.imshow(median_filtered, cmap='gray')
3 plt.axis('off')
4 plt.title("median filtered image")

要确定哪种阈值技术最适合分割,我们可以先通过阈值确定是否存在将这两个类别分
开的独特像素强度。在这种情况下,可以使用通过目视检查获得的强度对图像进行二
值化处理。我们使用的图像许多像素的强度小于50,这些像素与反转灰度图像中的背
景类别相对应。

尽管类别的分布不是双峰的,但仍然在前景和背景之间有所区别,在该区域中,较低
强度的像素达到峰值,然后到达谷底。可以通过各种阈值技术获得该精确值。分割部
分将详细研究一种这样的方法。

可视化像素强度的直方图

1 counts, vals = np.histogram(grayscale, bins=range(2 ** 8))


2 plt.plot(range(0, (2 ** 8) - 1), counts)
https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247491154&idx=1&sn=a9b4ce79edcac1931408265c3330efc3&chksm=fb56fcb… 5/16
2020/9/30 基于 OpenCV 的图像分割

3 plt.title("Grayscale image histogram")


4 plt.xlabel("Pixel intensity")
5 plt.ylabel("Count")

分割

去除噪声后,我们可以用skimage滤波器模块对所有阈值的结果进行比较,来确定所需
要使用的像素。有时,在图像中,其像素强度的直方图不是双峰的。因此,可能会有
另一种阈值方法可以比基于阈值形状在内核形状中进行阈值化的自适应阈值方法更
好。Skimage中的函数可以方便看到不同阈值的处理结果。

尝试所有阈值

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247491154&idx=1&sn=a9b4ce79edcac1931408265c3330efc3&chksm=fb56fcb… 6/16
2020/9/30 基于 OpenCV 的图像分割

1 result = skimage.filters.thresholding.try_all_threshold(median_filtered

最简单的阈值处理方法是为图像使用手动设置的阈值。但是在图像上使用自动阈值方
法可以比人眼更好地计算其数值,并且可以轻松复制。对于本例中的图像,似乎
Otsu,Yen和Triangle方法的效果很好。

在本文中,我们将使用Otsu阈值技术将图像分割成二进制图像。Otsu通过计算一个最
大化类别间方差(前景与背景之间的方差)并最小化类别内方差(前景内部的方差或

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247491154&idx=1&sn=a9b4ce79edcac1931408265c3330efc3&chksm=fb56fcb… 7/16
2020/9/30 基于 OpenCV 的图像分割

背景内部的方差)的值来计算阈值。如果存在双峰直方图(具有两个不同的峰)或阈
值可以更好地分隔类别,则效果很好。

Otsu阈值化和可视化

1 threshold = skimage.filters.threshold_otsu(median_filtered)
2 print("Threshold value is {}".format(threshold))
3 predicted = np.uint8(median_filtered > threshold) * 255
4 plt.imshow(predicted, cmap='gray')
5 plt.axis('off')
6 plt.title("otsu predicted binary image")

如果上述简单技术不能用于图像的二进制分割,则可以使用UNet,带有FCN的ResNet
或其他各种受监督的深度学习技术来分割图像。要去除由于前景噪声分段而产生的小
物体,也可以考虑尝试skimage.morphology.remove_objects()。

验证方式

一般情况下,我们都需要由具有图像类型专长的人员手动生成基本事实,来验证准确
性和其他指标,并查看图像的分割程度。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247491154&idx=1&sn=a9b4ce79edcac1931408265c3330efc3&chksm=fb56fcb… 8/16
2020/9/30 基于 OpenCV 的图像分割

confusion矩阵

我 们 sklearn.metrics.confusion_matrix() 用 来 获 取 该 矩 阵 元 素 , 如 下 所
示。假设输入是带有二进制元素的元素列表,则Scikit-learn混淆矩阵函数将返回混淆
矩阵的4个元素。对于一切都是一个二进制值(0)或其他(1)的极端情况,sklearn
仅返回一个元素。我们包装了sklearn混淆矩阵函数,并编写了我们自己的这些边缘情
况,如下所示:

get_confusion_matrix_elements()

1 def get_confusion_matrix_elements(groundtruth_list, predicted_list):


2 """returns confusion matrix elements i.e TN, FP, FN, TP as floats
3 See example code for helper function definitions
4 """
5 _assert_valid_lists(groundtruth_list, predicted_list)
6
7 if _all_class_1_predicted_as_class_1(groundtruth_list, predicted_l
8 tn, fp, fn, tp = 0, 0, 0, np.float64(len(groundtruth_list))
9
10 elif _all_class_0_predicted_as_class_0(groundtruth_list, predicted_
11 tn, fp, fn, tp = np.float64(len(groundtruth_list)), 0, 0, 0
12
13 else:
14 tn, fp, fn, tp = sklearn.metrics.confusion_matrix(groundtruth_
15 tn, fp, fn, tp = np.float64(tn), np.float64(fp), np.float64(fn
16
17 return tn, fp, fn, tp

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247491154&idx=1&sn=a9b4ce79edcac1931408265c3330efc3&chksm=fb56fcb… 9/16
2020/9/30 基于 OpenCV 的图像分割

准确性

在二进制分类的情况下,准确性是一种常见的验证指标。计算为

其中TP =真正,TN =真负,FP =假正,FN =假负

get_accuracy()

1 def get_accuracy(groundtruth_list, predicted_list):


2
3 tn, fp, fn, tp = get_confusion_matrix_elements(groundtruth_list, pre
4
5 total = tp + fp + fn + tn
6 accuracy = (tp + tn) / total
7
8 return accuracy

它在0到1之间变化,0是最差的,1是最好的。如果算法将所有东西都检测为整个背景
或前景,那么仍然会有很高的准确性。因此,我们需要一个考虑班级人数不平衡的指
标。特别是由于当前图像比背景0具有更多的前景像素(类1)。

F1分数从0到1不等,计算公式为:

0是最差的预测,而1是最好的预测。现在,考虑边缘情况,处理F1分数计算。

get_f1_score()

1 def get_f1_score(groundtruth_list, predicted_list):


2 """Return f1 score covering edge cases"""
3
4 tn, fp, fn, tp = get_confusion_matrix_elements(groundtruth_list, p

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247491154&idx=1&sn=a9b4ce79edcac1931408265c3330efc3&chksm=fb56fc… 10/16
2020/9/30 基于 OpenCV 的图像分割

5
6 if _all_class_0_predicted_as_class_0(groundtruth_list, predicted_l
7 f1_score = 1
8 elif _all_class_1_predicted_as_class_1(groundtruth_list, predicted_
9 f1_score = 1
10 else:
11 f1_score = (2 * tp) / ((2 * tp) + fp + fn)
12
13 return f1_score

高于0.8的F1分数被认为是良好的F1分数,表明预测表现良好。

客户中心

MCC代表马修斯相关系数,其计算公式为:

它位于-1和+1之间。-1是实际情况与预测之间绝对相反的相关性,0是随机结果,其中
某些预测匹配,而+1是实际情况与预测之间绝对匹配,保持正相关。因此,我们需要
更好的验证指标,例如MCC。

在MCC计算中,分子仅由四个内部单元(元素的叉积)组成,而分母由混淆矩阵的四
个外部单元(点的积)组成。在分母为0的情况下,MCC将能够注意到我们的分类器
方向错误,并且会通过将其设置为未定义的值(即numpy.nan)进行警告。但是,为
了获得有效值,并能够在必要时对不同图像平均MCC,我们将MCC设置为-1(该范围
内最差的值)。其他边缘情况包括将MCC和F1分数设置为1的所有正确检测为前景和背
景的元素。否则,将MCC设置为-1且F1分数为0。

想要了解有关MCC和边缘案例,以及MCC为什么比准确性或F1分数更好,可以阅读下
面这篇文章:
https://lettier.github.io/posts/2016-08-05-matthews-correlation-coefficient.html
https://en.wikipedia.org/wiki/Matthews_correlation_coefficient#Advantages_of
_MCC_over_accuracy_and_F1_score

get_mcc()

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247491154&idx=1&sn=a9b4ce79edcac1931408265c3330efc3&chksm=fb56fc… 11/16
2020/9/30 基于 OpenCV 的图像分割

1 def get_mcc(groundtruth_list, predicted_list):


2 """Return mcc covering edge cases"""
3
4 tn, fp, fn, tp = get_confusion_matrix_elements(groundtruth_list, p
5
6 if _all_class_0_predicted_as_class_0(groundtruth_list, predicted_l
7 mcc = 1
8 elif _all_class_1_predicted_as_class_1(groundtruth_list, predicted_
9 mcc = 1
10 elif _all_class_1_predicted_as_class_0(groundtruth_list, predicted_
11 mcc = -1
12 elif _all_class_0_predicted_as_class_1(groundtruth_list, predicted_
13 mcc = -1
14
15 elif _mcc_denominator_zero(tn, fp, fn, tp) is True:
16 mcc = -1
17
18 # Finally calculate MCC
19 else:
20 mcc = ((tp * tn) - (fp * fn)) / (
21 np.sqrt((tp + fp) * (tp + fn) * (tn + fp) * (tn + fn)))
22
23 return mcc

最后,我们可以按结果并排比较验证指标。

1 >>> validation_metrics = get_validation_metrics(groundtruth, predicted)


2 {'mcc': 0.8533910225863214, 'f1_score': 0.8493358633776091, 'tp': 5595

精度接近1,因为示例图像中有很多背景像素可被正确检测为背景(即,真实的底片自
然更高)。这说明了为什么精度不是二进制分类的好方法。

F1分数是0.84。因此,在这种情况下,我们可能不需要用于二进制分割的更复杂的阈
值算法。如果堆栈中的所有图像都具有相似的直方图分布和噪声,则可以使用Otsu并
获得相当不错的预测结果。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247491154&idx=1&sn=a9b4ce79edcac1931408265c3330efc3&chksm=fb56fc… 12/16
2020/9/30 基于 OpenCV 的图像分割

所述MCC 0.85高时,也表示地面实况和预测图像具有高的相关性,从在上一节的预测
图像图片清楚地看到。

现在,让我们可视化并查看混淆矩阵元素TP,FP,FN,TN在图像周围的分布位置。
它向我们显示了在不存在阈值(FP)的情况下阈值正在拾取前景(容器),在未检测
到真实血管的位置(FN),反之亦然。

验证可视化

为了可视化混淆矩阵元素,我们精确地找出混淆矩阵元素在图像中的位置。例如,我
们发现TP阵列(即正确检测为前景的像素)是通过找到真实情况和预测阵列的逻辑
“与”。同样,我们使用逻辑布尔运算通常称为FP,FN,TN数组。

get_confusion_matrix_intersection_mats()

1 def get_confusion_matrix_intersection_mats(groundtruth, predicted):


2 """ Returns dict of 4 boolean numpy arrays with True at TP, FP, FN
3 """
4
5 confusion_matrix_arrs = {}
6
7 groundtruth_inverse = np.logical_not(groundtruth)
8 predicted_inverse = np.logical_not(predicted)
9
10 confusion_matrix_arrs['tp'] = np.logical_and(groundtruth, predicte
11 confusion_matrix_arrs['tn'] = np.logical_and(groundtruth_inverse,
12 confusion_matrix_arrs['fp'] = np.logical_and(groundtruth_inverse,
13 confusion_matrix_arrs['fn'] = np.logical_and(groundtruth, predicte
14
15 return confusion_matrix_arrs

然后,我们可以将每个数组中的像素映射为不同的颜色。对于下图,我们将TP,FP,
FN,TN映射到CMYK(青色,品红色,黄色,黑色)空间。同样可以将它们映射到
(绿色,红色,红色,绿色)颜色。然后,我们将获得一张图像,其中所有红色均表
示错误的预测。CMYK空间使我们能够区分TP,TN。

get_confusion_matrix_overlaid_mask()

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247491154&idx=1&sn=a9b4ce79edcac1931408265c3330efc3&chksm=fb56fc… 13/16
2020/9/30 基于 OpenCV 的图像分割

1 def get_confusion_matrix_overlaid_mask(image, groundtruth, predicted,


2 """
3 Returns overlay the 'image' with a color mask where TP, FP, FN, TN
4 each a color given by the 'colors' dictionary
5 """
6 image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB)
7 masks = get_confusion_matrix_intersection_mats(groundtruth, predict
8 color_mask = np.zeros_like(image)
9 for label, mask in masks.items():
10 color = colors[label]
11 mask_rgb = np.zeros_like(image)
12 mask_rgb[mask != 0] = color
13 color_mask += mask_rgb
14 return cv2.addWeighted(image, alpha, color_mask, 1 - alpha, 0)
15
16 alpha = 0.5
17 confusion_matrix_colors = {
18 'tp': (0, 255, 255), #cyan
19 'fp': (255, 0, 255), #magenta
20 'fn': (255, 255, 0), #yellow
21 'tn': (0, 0, 0) #black
22 }
23 validation_mask = get_confusion_matrix_overlaid_mask(255 - grayscale,
24 print('Cyan - TP')
25 print('Magenta - FP')
26 print('Yellow - FN')
27 print('Black - TN')
28 plt.imshow(validation_mask)
29 plt.axis('off')
30 plt.title('confusion matrix overlay mask')

我们在此处使用OpenCV将此颜色蒙版作为透明层覆盖到原始(非反转)灰度图像上。
这称为Alpha合成:

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247491154&idx=1&sn=a9b4ce79edcac1931408265c3330efc3&chksm=fb56fc… 14/16
2020/9/30 基于 OpenCV 的图像分割

总结

存储库中的最后两个示例通过调用测试函数来测试边缘情况和在小的数组(少于10个
元素)上的随机预测场景。如果我们测试该算法的简单逻辑,则测试边缘情况和潜在
问题很重要。

Travis CI对于测试我们的代码是否可以在需求中描述的模块版本上工作以及在新更改
合并到主版本中时所有测试通过均非常有用。最佳做法是保持代码整洁,文档完善,
并对所有语句进行单元测试和覆盖。这些习惯限制了在复杂的算法建立在可能已经进
行了单元测试的简单功能块之上时,消除错误的需求。通常,文档和单元测试可帮助
其他人随时了解功能意图。整理有助于提高代码的可读性,而flake8是实现此目的的
良好Python包。

以下是本文的重要内容:

1. 适用于内存中不适合的数据的拼接和拼接方法

2. 尝试不同的阈值技术

3. 验证指标的精妙之处

4. 验证可视化

5. 最佳实践
交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计
算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),
请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交
大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入
相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247491154&idx=1&sn=a9b4ce79edcac1931408265c3330efc3&chksm=fb56fc… 15/16
2020/9/30 基于 OpenCV 的图像分割

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247491154&idx=1&sn=a9b4ce79edcac1931408265c3330efc3&chksm=fb56fc… 16/16
2020/7/29 利用OpenCV实现图像修复(含源码链接)

利用OpenCV实现图像修复(含源码链接)
原创 小白 小白学视觉 2019-04-22

前一段时间小白分享过关于图像修复技术介绍的推文(点击可以跳转 ) ,有小伙伴后台咨询能不能分享一下
关于图像修复的项目或者程序。今天小白带着满满的诚意,带来了通过OpenCV实现图像修复的C++代码与
Python代码。

图像修复技术应用在什么地方呢?

想想一下,我们有一张非常棒的相片,但是由于时间比较久远,没有电子版留底,而纸质版的又十分不便于
保存。因此长采用扫描的方式获得电子版。但是非常不幸,扫描过程中落入了一根头发,或者是机器出现故
障,对相片造成了影响,这个时候就可以通过图像修复技术解决这个问题。

强大的OpenCV库里集成了两种用与图像修复的方法

INPAINT_NS: 基于Navier-Stokes的图像修复
该方法在2001年提出,其神奇之处竟然是基于流体力学理论提出的方法。根据其作者提出,我们需要解决的
问题可以抽象成在一个鞋子图片上有一个黑色的区域,通过填充黑色区域,使得最佳的恢复鞋子的样子。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247485719&idx=1&sn=64541c892062361e353b8710cb277d1e&chksm=fb56ebf… 1/5
2020/7/29 利用OpenCV实现图像修复(含源码链接)

对于如何填补这个黑色区域,可以抽象成存在一条曲线,使得由A到B将黑色区域分开,并且保证在曲线的一
侧是蓝色,另一侧是白色。这个曲线应具有如下的约束:
1. 保持边缘特征
2. 在平滑区域中保持颜色信息

通过构建一个偏微分方程来更新具有上诉约束的区域内的图像强度,同时利用拉普拉斯算子估计图像平滑度
信息,并沿着等照度传播。

由于这些方程与Navier-Stokes方程(流体力学中的方程,感兴趣的小伙伴可以自行百度)相关且类似,因
此可以通过流体力学中的方法进行求解。

由于小白对流体力学不是很了解,具体就不详细解释了,感兴趣的小伙伴可以阅读该论文了解详情。
论文地址:http://www.math.ucla.edu/~bertozzi/papers/cvpr01.pdf

INPAINT_TELEA:基于快速行进方法的图像修复
该方法中没有使用拉普拉斯算子作为平滑度的估计,而是使用像素的已知图像邻域上的加权平均值来描述。
同时利用邻域像素和梯度恢复填充区域像素的颜色。
当像素被修复之后,通过快速行进方法更新边界。

论文地址:
https://pdfs.semanticscholar.org/622d/5f432e515da69f8f220fb92b17c8426d0427.pdf

相关API介绍
C++:

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247485719&idx=1&sn=64541c892062361e353b8710cb277d1e&chksm=fb56ebf… 2/5
2020/7/29 利用OpenCV实现图像修复(含源码链接)

void inpaint(
    Mat&amp;src,
    Mat&amp;&nbsp;inpaintMask,
    Mat&amp;&nbsp;dst,
    double&nbsp;inpaintRadius,
    int&nbsp;flags)

Python:
dst =cv2.inpaint(
    src,
    inpaintMask,
    inpaintRadius,
    flags)

其中各参数的含义如下:
src =源图像
inpaintMask =二进制掩码,指示要修复的像素。
DST =目标图像
inpaintRadius =像素周围的邻域补绘。通常,如果要修复的区域很薄,使用较小的值会产生较少的模糊。
flags: INPAINT_NS(基于Navier-Stokes的方法)或INPAINT_TELEA(基于快速行进的方法)

示例

左边的第一个图像是输入图像,第二个图像是掩模,第三个图像是INPAINT_TELEA的结果,最终结果是INPAINT_NS

关于这个图片有一个小小的故事,1865年2月5日星期日,在华盛顿特区的加德纳画廊,亚历山大·加德纳拍
摄了几张总统的多镜头照片。在本届会议结束之前,加德纳请求为总统拍摄最后一个姿势。他把相机拉得更
近,拍了一张林肯头部、肩膀和胸部的照片。但是玻璃板在这个时候突然破裂,对拍摄图像产生影响。加德
纳小心翼翼地将它带到了他的黑暗房间,制作一张相片,发现在林肯的脸上有一个不祥的裂缝。这张相片,
即O-118,至今仍然存在。多年来,许多人认为这一裂缝是10周后林肯中弹的预言。

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247485719&idx=1&sn=64541c892062361e353b8710cb277d1e&chksm=fb56ebf… 3/5
2020/7/29 利用OpenCV实现图像修复(含源码链接)

让我们看一个更复杂的例子,在图片上写上英文单词,之后通过opencv函数去修复该单词。

左:带有Scribbles的原始图像。中:使用快速行进方法修复,右:使用Navier-Stokes方法修复。

该程序的源码和使用的图片链接为:
https://github.com/spmallick/learnopencv/tree/master/Image-Inpainting

往期文章一览

1、【OpenCV入门之十五】随心所欲绘制想要图形
2、【OpennCV入门之十四】揭开mask
3、【OpenCV入门之十三】如何在ROI中添加Logo
4、如何让黑白相片恢复生机
5、我竟然用OpenCV实现了卡尔曼滤波
6、【走进OpenCV】滤波代码原来这么写
7、【走进OpenCV】这样腐蚀下来让我膨胀
8、小心!你看到的图像可能隐藏了重大机密

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247485719&idx=1&sn=64541c892062361e353b8710cb277d1e&chksm=fb56ebf… 4/5
2020/7/29 利用OpenCV实现图像修复(含源码链接)

//
//

https://mp.weixin.qq.com/s?__biz=MzU0NjgzMDIxMQ==&mid=2247485719&idx=1&sn=64541c892062361e353b8710cb277d1e&chksm=fb56ebf… 5/5

You might also like