前言
原本採用web架构,但web对于RTSP支援程度较差(MJPG才是网页常见的格式,原生支援度高),目前使用的作法是透过架NodeJS接收RTSP串流,再经由websocket传输到浏览器画面上,但这样多一转换层,除了效能的疑虑,也会有一些系统不稳定的风险,假如websocket断掉,影像就进不来了,而且同一时间要连的RTSP不只有一个,不确定websocket是否能承受大量的资讯传输,因此觉得还是client直接接收RTSP串流比较稳一点;原本的想法是在react里面埋nodejs,透过VNC抓RTSP的dll元件来去达成目的,但工程浩大,也还不知道怎么做,最后整个重写,使用python tkinter来呈现RTSP摄影机的影像了。
安装opencv for python套件
这篇的重点就是要用OpenCV来读RTSP,当然套件是不能少的,先pip一下吧
> pip install opencv-python
接着读取RTSP的影像串流,呈现在画面上拨放它
URL我们先用国外测试的RTSP网址:rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov,用VNC稍作测试可以播放后,就把它放进来opencv套件吧
from cv2 import cv2video = cv2.VideoCapture('rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov')
这样子相当于已经準备好跟RTSP的连线了,再来我们使用read()来取每一格frame,那他回传的是一个阵列,第一个回传状态,第二个才是frame,所以我们用(status,frame)去接他,并用while去接每一格frame,while跳出条件当然是status啰,也就是当RTSP串流播放完毕跳出迴圈,另外,我会习惯再加上手动停止的flag,所以看起来会是这样...
(status, frame) = video.read()while self.__active and status: (status, frame) = video.read()
取到frame后,会处理一个色彩空间转换的步骤,把BGR转换成我们常见的RGBA,让他生成pixel阵列后,再透过PIL套件转成Image物件
imgArray = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)image = Image.fromarray(imgArray)photo = ImageTk.PhotoImage(image=image)
最后,把RTSP影像放在tkinter的Label widget做呈现啰
# 建立承载RTSP影像串流的容器物件window = tk.Label(bg="black", image=photo, width=320, height=240)# avoid garbage collection(避免资源被回收)(把图片注册到物件中,并自订属性,避免被回收)window.rtspImage = photo# 放置在画面上window.place(x=0, y=0, anchor='nw')
最后的最后,非常重要的一环,资源释放,一定要切断跟RTSP的连线,否则容易占用记忆体空间
video.release()
我到底看了什么? 录下来吧
到这边,客户又给了一个新需求,要再检视摄影机RTSP串流的时候,同步录影下来,还好我们有OpenCV大神加持,没问题,马上录;使用VideoWriter()提供的写入影像功能就可以达成目的了
record = cv2.VideoWriter([档案路径名称], [影片编码], [FPS], ([影片宽度], [影片高度]))
但是笔者在实做过程中,编码如果跟来源不一样,都没有办法成功录进去档案里,于是再多做了一些处理,就是取得来源RTSP串流的编码、FPS与宽高
# 取得来源RTSP影像的编码def getSourceFourcc(sourceVideo): code = sourceVideo.get(cv2.CAP_PROP_FOURCC) code = "".join([chr((int(code) >> 8 * i) & 0xFF) for i in range(4)]) return coderecordForucc = cv2.VideoWriter_fourcc(*getSourceFourcc(video))recordFPS = int(video.get(cv2.CAP_PROP_FPS))recordWidth = int(video.get(cv2.CAP_PROP_FRAME_WIDTH))recordHeight = int(video.get(cv2.CAP_PROP_FRAME_HEIGHT))# 建立录影实体物件record = cv2.VideoWriter('CameraRecord.avi', recordForucc, recordFPS, (recordWidth, recordHeight))
接着,在每次成功取得frame后,就write到档案中,这个动作会根据设定的FPS来写到影片档案中
record.write(frame) # 录製影片到档案
最后最后,很重要,要记得释放,影片档案才不会一直被占用住喔
record.release()