视频转换过程中的几个基本注意事项

1.故障现象:迟滞

海康的摄像头迟滞大概会到1秒的量级,一般如果你自己搭个框架做转发,迟滞有时会达到20秒,这是为什么呢?请看例程:

python 复制代码
class VideoCamera(object):
    def __init__(self):
        # 打开系统默认摄像头
        self.cap = cv2.VideoCapture(rtsp_url)
        if not self.cap.isOpened():
            raise RuntimeError('Could not open camera.')

        # 设置帧宽和高度
        self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
        self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 640)
        
        ...
 
        #other init-process.

        # main loop
        while self.isRunning:
             ret, self.frame = self.cap.read() 
             ...

注意,cv2的输入流是在 cv2.VideoCapture(rtsp_url)处就打开了。你转换后实际输出的视频,大概率就从这个时候开始,然后在漫长的初始化工作完成后才开始接收。所以,你的输出视频的时间起点,人为地拉长了。

修正原则:对于视频流这类资源,什么时候用,什么时候开闸。

2.故障现象:花屏

导致花屏的错误代码参见:

python 复制代码
       while self.isRunning:
            ret, self.frame = self.cap.read()
            else:
                flagNotHandle = False
                ret = True
            if ret:
                if(self.queueFrameDone.qsize()>5):
                    continue;
                self.frame = self.frame[zone[1]:zone[1]+zone[3], zone[0]:zone[0]+zone[2]]
                self.image = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB)
                self.image = cv2.resize(self.image, (640,640))
                self.outputs = self.rknn_lite.inference(inputs=self.image) #self.gp_inference(self.image)
                self.frame = process_image(self.image, self.outputs) #image in...image out.
                self.frame = self.image
                #print(type(self.image), type(self.outputs), type(self.frame))
                rtsp_out.rtsp_out_push(self.out, self.frame, 640, 640)
                ret, image = cv2.imencode('.jpg', self.frame) #to bytes
                if ret:
                    arByte = image.tobytes()
                    #self.queueFrameDone.put(arByte);
                    self.resultImage = bytearray(arByte);
        self.out.release()

self.rknn_lite.inference和接下来的process_image是高耗时的计算单元。它一定不能阻塞输入流。这是花屏的根本原因。

3 推荐的处理策略

完全没有技术含量,对吧,是个人都能想到。但程序的bug就是这么造成的。一旦所做的事情比较新,你会很容易丧失掉一些关注点。会犯低级错误。它其实很类似弹吉他。弹吉他没有什么特别大的窍门,但是你是无法自如地弹奏的,你的错误会极顽固。

所以,结对儿编程,代码互检,真的很必要。

如果用python来处理视频转发,一个比较简单的策略是类似马保国师傅的接化发,接收码流,发送码流和码流的帧解算三个线程相互独立。

3.1 接线程

因为python的queue可以自行处理线程同步,所以,利用它做了视频流的输入输出接口。配置移到了cat4Config。然后包含一个简单的自测试。

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 获取当前脚本文件所在目录的父目录,并构建相对路径
import os
import sys
current_dir = os.path.dirname(os.path.abspath(__file__))
project_path = os.path.join(current_dir, '..')
sys.path.append(project_path)
sys.path.append(current_dir)
import cv2
import threading
import time
import warnings
import queue
import cat4Config

class StreamInThread(threading.Thread):
    def __init__(self, name, pjtConfig):
        threading.Thread.__init__(self)
        self.name = name
        self.isRunning = True
        self.frame = None  #the very last frame
        self.cap = None
        self.queueIn = queue.Queue()
        self.MAX_QUEUE_LEN = 5
        self.imageCache = None #the very old frame
        self.isFirstImageComing = False

        self.width = pjtConfig.width
        self.height = pjtConfig.height
        self.zone = pjtConfig.zone
        self.rtsp_url = pjtConfig.stream_in

    def run(self):
        flagNotHandle= False
        zone = self.zone
        self.cap = cv2.VideoCapture(self.rtsp_url)
        if not self.cap.isOpened():
            return;
        self.isFirstImageComing = False 
        while self.isRunning:
            ret, self.frame = self.cap.read()
            if ret:
                self.frame = self.frame[zone[1]:zone[1]+zone[3], zone[0]:zone[0]+zone[2]] #截取
                self.image = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB) #色彩变换
                self.image = cv2.resize(self.image, (self.width,self.height)) #缩放
                self.queueIn.put(self.image)
        self.cap.release()

    def stop(self):
        self.isRunning = False

    def __del__(self):
        self.isRunning = False
        #if(self.cap != None):
        #    self.cap.release()

    def rawImage(self):
        while self.queueIn.qsize()>(self.MAX_QUEUE_LEN*2):
            self.queueIn.get()
            continue;
        if(self.queueIn.qsize()>0):
            self.imageCache = self.queueIn.get()
            self.isFirstImageComing  = True
        return self.isFirstImageComing, self.imageCache

    def purge(self):
        while self.queueIn.qsize()>1:
            self.queueIn.get()
            continue;



if __name__ == "__main__":
     pjtConfig = cat4Config.Cat4Config()
     thread = StreamInThread("Stream In Thread", pjtConfig)
     thread.start()
     time.sleep(10)
     thread.stop();
     

3.2 AI解算线程

这个部分很容易可以从接发线程推导出来,逻辑很简单,并且三个线程具备相似的结构。

python 复制代码
代码从略...

3.3 发线程

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 获取当前脚本文件所在目录的父目录,并构建相对路径
import os
import sys
current_dir = os.path.dirname(os.path.abspath(__file__))
project_path = os.path.join(current_dir, '..')
sys.path.append(project_path)
sys.path.append(current_dir)
import cv2
import threading
import time
import warnings
import queue
import cat4Config
import thread_stream_ai as stream_ai
import thread_stream_in as stream_in
import numpy as np


class StreamOutThread(threading.Thread):
    def __init__(self, name, pjtConfig, streamGeneratorRhs:stream_ai.YoloV5Thread):
        threading.Thread.__init__(self)
        self.name = name
        self.isRunning = True
        self.nextTime = time.time() - 3
        self.streamGenerator= streamGeneratorRhs
        self.MAX_QUEUE_LEN = 5
        self.out = None
        self.frameCache = None #the very old frame
        self.isFirstFrameGenedYet = False
        self.nextTime2Sent = None
        self.queueOut = queue.Queue()

        self.width = pjtConfig.width
        self.height = pjtConfig.height
        self.zone = pjtConfig.zone
        self.rtsp_url = pjtConfig.stream_out
        self.fps = pjtConfig.fps_out
        self.timeStep = (1.0/self.fps)

    def run(self):
        self.nextTime2Sent = time.time()-3
        self.out = None
        self.streamGenerator.purge();
        self.isFirstFrameGenedYet = False
        while self.isRunning:
            ret, frame = self.streamGenerator.outFrame() #read next
            #print(ret, frame)
            if ret:
                #print(">>>>>>>>>>>>>>>>rknn first frame gened");
                self.queueOut.put(frame)
                self.isFirstFrameGenedYet = True
                if(self.out == None):
                    self.rtsp_out_init()
                    self.nextTime2Sent = time.time()
                ret, frame = self.outFrame()
                self.rtsp_out_push(frame)
                self.nextTime2Sent += self.timeStep
            else:
                time.sleep(0.01)
        
    def stop(self):
        self.isRunning = False

    def __del__(self):
        self.isRunning = False
        #if(self.cap != None):
        #    self.cap.release()

    def outFrame(self):
        while self.queueOut.qsize()>(self.MAX_QUEUE_LEN*2):
            self.queueOut.get()
            continue;
        nOfQueue = self.queueOut.qsize()
        if(nOfQueue):
            dumb = self.queueOut.get();
            #print("rknnQueue=%d" %(nOfQueue), dumb)
            arMat = np.zeros((self.height, self.width, 3), dtype=np.uint8)
       	    arMat[:,:,:] = dumb
            self.frameCache = arMat
        return self.isFirstFrameGenedYet, self.frameCache

    def purge(self):
        while self.queueOut.qsize()>1:
            self.queueOut.get()
            continue;

    def rtsp_out_init(self):
       print('start post to %s' %(self.rtsp_url))
       self.out = cv2.VideoWriter('appsrc ! videoconvert' + \
           ' ! video/x-raw,format=I420' + \
           ' ! x264enc speed-preset=ultrafast bitrate=600 key-int-max=' + str(self.fps * 2) + \
           ' ! video/x-h264,profile=baseline' + \
           ' ! rtspclientsink location=%s' %(self.rtsp_url),
           cv2.CAP_GSTREAMER, 0, self.fps, (self.width, self.height), True)
       if not self.out.isOpened():
           raise Exception("can't open video writer")


    def rtsp_out_push(self, npArray):
        out = self.out
        frame = npArray
        out.write(frame)


if __name__ == "__main__":
     pjtConfig = cat4Config.Cat4Config()
     threadIn = stream_in.StreamInThread("Stream In Thread", pjtConfig)
     threadCalc = stream_ai.YoloV5Thread("Stream Calc Thread", pjtConfig, threadIn)
     threadOut = StreamOutThread("Stream RTSP Out Thread", pjtConfig, threadCalc)
     threadOut.start()
     threadCalc.start()
     threadIn.start()
     time.sleep(3600*24)
     threadOut.stop()
     threadCalc.stop()
     threadIn.stop()

4.最终的实现效果

迟滞还有,但是另外的原因,然后花屏没有了。

相关推荐
葫三生18 分钟前
如何评价《论三生原理》在科技界的地位?
人工智能·算法·机器学习·数学建模·量子计算
m0_751336391 小时前
突破性进展:超短等离子体脉冲实现单电子量子干涉,为飞行量子比特奠定基础
人工智能·深度学习·量子计算·材料科学·光子器件·光子学·无线电电子
美狐美颜sdk4 小时前
跨平台直播美颜SDK集成实录:Android/iOS如何适配贴纸功能
android·人工智能·ios·架构·音视频·美颜sdk·第三方美颜sdk
DeepSeek-大模型系统教程5 小时前
推荐 7 个本周 yyds 的 GitHub 项目。
人工智能·ai·语言模型·大模型·github·ai大模型·大模型学习
郭庆汝5 小时前
pytorch、torchvision与python版本对应关系
人工智能·pytorch·python
小雷FansUnion7 小时前
深入理解MCP架构:智能服务编排、上下文管理与动态路由实战
人工智能·架构·大模型·mcp
资讯分享周7 小时前
扣子空间PPT生产力升级:AI智能生成与多模态创作新时代
人工智能·powerpoint
思则变8 小时前
[Pytest] [Part 2]增加 log功能
开发语言·python·pytest
melonbo8 小时前
使用FFmpeg将H.264码流封装为MP4
ffmpeg·音视频·h.264
叶子爱分享8 小时前
计算机视觉与图像处理的关系
图像处理·人工智能·计算机视觉