视频分析的处理过程是从摄像头读取帧,逐帧处理(做目标检测)后,放入流媒体服务器,发布出新的视频流。
rtmp流媒体服务器可以采用nginx_rtmp_module、srs。这篇文章里有关于nginx_rtmp_module的说明,https://zhuanlan.zhihu.com/p/28009037,手动安装比较复杂,可以使用直接使用 alfg/nginx-rtmp 镜像 https://github.com/alfg/docker-nginx-rtmp。
最初的代码参考了这篇文章,https://zhuanlan.zhihu.com/p/74260950。
import subprocess as sp
rtmpUrl = ""
camera_path = ""
cap = cv.VideoCapture(camera_path)
# Get video information
fps = int(cap.get(cv.CAP_PROP_FPS))
width = int(cap.get(cv.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv.CAP_PROP_FRAME_HEIGHT))
# ffmpeg command
command = ['ffmpeg',
'-y',
'-f', 'rawvideo',
'-vcodec','rawvideo',
'-pix_fmt', 'bgr24',
'-s', "{}x{}".format(width, height),
'-r', str(fps),
'-i', '-',
'-c:v', 'libx264',
'-pix_fmt', 'yuv420p',
'-preset', 'ultrafast',
'-f', 'flv',
rtmpUrl]
# 管道配置
p = sp.Popen(command, stdin=sp.PIPE)
# read webcamera
while(cap.isOpened()):
ret, frame = cap.read()
if not ret:
print("Opening camera is failed")
break
# process frame
# your code
# process frame
# write to pipe
p.stdin.write(frame.tostring())
基本思路是使用opencv读取视频流,把视频流里帧处理后,放入管道,ffmpeg把管道里的帧推送到rtmp服务器,客户端就可以播放rtmp服务器里的处理后的视频。
在实际运行中,会出现卡顿、花屏问题。原因是这一部分处理帧的代码耗时比较久。
# process frame
# your code
# process frame
解决的方法是控制处理帧的数量和使用多线程并行处理。
对视频文件的fps(帧率)可以用fps = int(cap.get(cv.CAP_PROP_FPS))查看,对于摄像头视频流可以采用https://blog.youkuaiyun.com/zziahgf/article/details/90706839 里的方法计算。我们需要控制输出的视频流。一般电影的fps是24、电视是25或30。输出的视频流的fps是10的话,也可以满足一些监控的需求。
用下面的方法可以控制输出视频流的fps
frame_rate = 25
last_process_frame_time = 0 # 处理上一个帧的时间,用于按frame_rate处理帧
while(self.cap.isOpened()):
time_elapsed = time.time() - last_process_frame_time
ret, frame = self.cap.read()
if not ret:
print("Opening camera is failed")
break
if time_elapsed > 1./frame_rate:
last_process_frame_time = time.time()
process_frame(frame)
按指定的fps取到帧后,也不需要每帧都调用目标检测算法,因为临近的几帧是基本相同的,所以可以隔几帧调用一次算法,对于其它帧使用前一次检测的结果做画图即可,不用再执行算法。
i = 0
count = 5 #每隔5帧提取一次图片
while True:
ret, frame = cap.read()
i = i + 1
if(i == 0):
detect(frame)
else:
drawbox(frame)
i = i + 1
i = i % count
下面的代码里把detect和drawbox放在了process_frame方法中,把i作为frame_index传入了process_frame。
如果采用25的fps,意味着检测每帧的时间要在40毫秒以内。要执行的算法很多,难以要求每个都能达到这样的要求。尤其是当算法部署在其它机器,通过网络调用时,受网络延时影响,更容易超出这一限制。这时,需要用多线程来并行执行算法。
executor = ThreadPoolExecutor(max_workers=10)
future= executor.submit(detect,frame)
多线程会打乱帧的顺序,所以我们使用了队列deque来在主线程里记录每一帧的输入时间,并使用一个dict来记录这一帧的输出。在process_frame时,先把输入时间加入到队列,然后使用多线程执行检测,检测完成后,把结果加入dict,使用输入时间作为key。对于不需要检测,只需要画框的帧,为检测任务future设置回调,检测完成后,回调函数为这些帧画框,然后把画完框的帧加入到dict中。
输出时,先检查deque中最早的帧在dict中有没有结果,如果有结果,就把结果帧取出,放入管道中。同时,删除队列和dict中这一帧的数据。这样就保证了输出顺序。
这时,如果忽略画图的时间,单次调用算法执行时间除以线程数 小于 40毫秒X5(抽帧频率)=200毫秒 就能保证流畅的输出。
# 处理帧,检测或画框
def process_frame(self,stub, frame,frame_index):
try:
input_time = datetime.datetime.now()
input_time_queue.append(input_time)
need_detect = frame_index == 0 # 是否需要调用算法检测
if(need_detect):
self.last_detect_future = self.executor.submit(self.detect_and_save,stub, frame,input_time)
else:
drowbox_after_finish_detect(self.last_detect_future,frame,input_time)
except Exception as e:
logger.exception(repr(e))
logger.exception('process_frame fail')
# 检测帧,并把结果记录到dic中
def detect_and_save(self,stub,frame,input_time):
output = detect(stub, frame)
result_frame_dic[input_time] = output
return output
# 检测完成后,对后续帧画图
def drowbox_after_finish_detect(task, frame,input_time):
try:
logger.debug('into drowbox_after_finish_detect')
for future in concurrent.futures.as_completed([task]):
detect_result = future.result()
out_frame = drawbox(frame, detect_result)
result_frame_dic[input_time] = {"out_frame":out_frame}
except Exception as e:
logger.exception(repr(e))
logger.exception('drowbox_after_finish_detect fail')
# 取出最早的结果帧,放入管道
def write_to_pipe(self):
try:
if(len(input_time_queue)>0):
first_time = input_time_queue[0]
if(first_time in result_frame_dic):
logger.debug('find first_time' )
out_frame = result_frame_dic[first_time]['out_frame']
self.p.stdin.write(out_frame.tostring())
remove_from_queue(first_time)
except Exception as e:
logger.exception(repr(e))
logger.exception('write_to_pipe fail')
# 把帧信息从队列删除
def remove_from_queue(input_time):
result_frame_dic.pop(input_time)
input_time_queue.popleft()
以上就是优化后的主要代码。
回顾下基本思路:
1.控制要处理的帧的数量:
(1)控制fps
(2)抽帧检测,其它帧仅使用检测结果画图
2.多线程处理
(1)多个线程同时调用算法,调用时可以减小分辨率来加速检测
(2)使用回调为后续帧画图
(3)使用队列保证输出帧的顺序和输入顺序一致,保证每一帧都被处理
本文探讨了视频分析中的目标检测性能优化,包括通过控制处理帧的数量、使用多线程并行处理来解决卡顿和花屏问题。介绍了如何计算和控制视频流的fps,以及如何利用帧的相似性降低算法调用频率。通过多线程和队列管理确保输出帧顺序正确,以实现流畅的视频流处理。
2747

被折叠的 条评论
为什么被折叠?



