UROP日记

2019 Summer UROP

Posted by Sizhe Song on 2019-08-10

暑假做了Gary的UROP,title是AI Meets Big Data,具体内容是从监控视频中输出每个人的位置(轨迹),然鹅因为太菜所以这个暑假做的内容完全没有涉及AI。这里记录一下整个暑假踩过的坑和学到的一点点东西。

face_recognition

最开始的时候打算用脸部识别来做定位,而且用脸部来做也可以较为准确地确定谁是谁。上Github找了一下,有一个封装得比较好的Python包叫face_recognition。项目里面有中文的README,介绍得比较详细,可以直接使用pip安装这个包,但是要先装好dlibCMake。官方说只支持了MacOS和Linux,Windows不保证可以使用,我自己这边使用的是Windows的Linux子系统。

1
2
3
4
sudo apt-get update
sudo apt-get install cmake
pip install dlib
pip install face_recognition

但是当我写完了一个demo之后,才发现我们的监控摄像头是AV画质,根本不可能识别出人脸。另外监控视频的角度天生对人脸识别(特指用于定位)不友好,不容易得到人的真实位置,而且行人稍微侧一下脸、低一下头,就都识别不出来了。于是前两周的时间就这样纯白给了。

Yolo

后面经PhD学长安利,了解到了这样一个存在:Yolo,一个实时对象跟踪识别工具。Yolo的性能还是非常强大的,和JW老哥在Liba的LC里面随便测试了一下就被惊到了。

Yolo

除了把LC的桌子识别成了Dining Table(其实也挺像的),更流批的是我的笔记本在图片左下角乱入的一点点居然都被识别出来了,而JW老哥坐的椅子在图片里面就是一条缝也识别出来了。当下就决定用Yolo来做行人识别了。

OpenCV

考虑到时间成本,我们不可能也没有必要去处理视频的每一帧,于是就需要抽帧。大致查了一下,Python处理视频一般用OpenCV这个库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import cv2

# Open the video
video = cv2.VideoCapture(videoPath)

# Check if it's correctly opened
if vc.isOpened():
rval, frame = video.read()
else:
rval = False

i = 0
while rval:
# Read a frame
rval, frame = video.read()

# Write the frame to a image file
cv2.imwrite(str(i)+'.jpg', frame)
i += 1
cv2.waitKey(1)

# Release the video
video.release()

上述代码中只要添加对变量i的判断就可以实现抽帧了。

Python None

这是码code过程中踩的一个坑。我写了一个函数用于调用Yolo,识别出图片中的所有人,并返回他们的坐标,所以正常情况下这个函数应该返回一个list,对于图片打开失败的异常情况,我返回了一个None。这里没有设计报错也没有停下来处理异常,因为一个视频往往要处理几十万张图片,几张图片打不开完全不影响。所以在外部调用这个函数的时候,我只需要比较一下是不是None就好了。

1
2
3
4
5
6
for frame in frames:
locations = func(frame)
if locations = None:
continue
else:
<statements>

但是这时就出现了问题,由于Python太人性化,以至于当我比较一个正常结果list等不等于None的时候,Python贴心地停下来向我报错,并问我是想比较list的全部成员为None还是部分成员为None。之前我都不知道Python有这种功能。其实这是一个设计失误,我不该返回None,返回一个空list就应该没这么多事,但是既然踩到坑了,这里就说一下比较一个对象是不是None的最稳妥的办法是:

1
2
if type(obj) == type(None):
<statements>

Progress Bar

正如刚才提到的,处理一个完整的视频要识别十几万张图,所以可能会花上两三个小时,这个时候如果程序没有输出,就完全无法得知程序现在的状态。如果每跑一张图就输出一个序号,这样比较偷懒,但是看得眼睛疼。于是就想做一个像pip安包的时候的那种在原位置持续更新进度条。

在自己尝试造了一会轮子之后放弃了,查了一下果然有现成的包:progressbar2

1
pip install progressbar2

progressbar2官方文档里面贴心地给了一堆example,对应了各种样式的进度条,挑一个自己喜欢的拿来就能用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import progressbar
import time

// Total amount of work
total = 100

// Create a progress bar
bar = progressbar.ProgressBar(max_value=total).start()

// Update the progress bar every second
for i in range(1,101):
bar.update(i)
time.sleep(1)

// Finished, end the progress bar
bar.finish()

需要注意的是在finish一个progress bar之前,不能向Console输出任何其他内容,否则会鬼畜。至少对于我给的这个例子是这样的。

JSON

程序最终的输出应该是一系列的“时间+坐标+身份”的记录,每一条记录代表了某个人在某个时间出现在了某个地点。输出这种统一格式的数据,很容易想到用JSON。Python读写JSON的库是jsonpatchjsonpointerjsonschema

1
2
pip install jsonpatch
pip install jsonschema

主要用到的两个函数时load()dump(),分别对应读和写。

1
2
3
4
5
6
7
8
import json

f = open(filePath,'r')
dicts = json.load(f)
f.close()

for dict in dicts:
print(dict)
1
2
3
4
5
6
7
8
import json

dicts = [{'name':'Sausage','age':6},
{'name':'SONG Sizhe','age':20}]

f = open(filePath,'w')
dicts = json.dump(dicts,f)
f.close()

上面的例子演示了用JSON读写一个dictionary list,其实JSON中的数据结构可以比给的这个例子复杂的多,可以嵌套很多层,只要读和写是一致的就行。根据经验,唯一的限制是整个结构需要是一个整体,就是一个大的list或者一个大的dictionary。

Python Try

跑Yolo的过程中遇到了报错,于是就想写个try语句处理一下异常,专门设置成了捕捉所有异常,但是奇怪的是再跑的时候try语句没有生效,也就是没有捕捉到任何异常,但是Console却依然是有报错信息。

查了一圈发现原因是Yolo的内部其实调用了很多用Cython写的库,而我遇到的报错就是来源于其中的一个。也就是说,我的Console里面的报错信息是来自于一段C语言的代码,它把报错的信息传给了Console,并且自己用exit()终止了程序,但是这个异常本身没有传递给Python,所以我的try什么都没有捕捉到。