基于python2.7的opencv3.3-ffmpeg-nginx-rtmp视频处理并推送流直播

做什么

当初一开始就是如此设想,通过opencv获取视频(摄像头)的图片帧,图像处理识别之后加工(绘制)图片,并把该图片作为视频流的一帧推送rtmp,然后远端直播,之间走了很多很多弯路(甚至想要手动实现rtmp推流)也就是了,搜索了一两周的攻略,断断续续的总算是实现了demo,

大致流程

图片帧采集(视频/摄像头)
图片帧加工(识别人脸,绘制信息)
图片帧写入服务器 (写入文件备份,写入管道直播 推流 到服务器)
客户端直播 (拉流)

关键参考地址:

https://stackoverflow.com/questions/36422211/processing-camera-stream-in-opencv-pushing-it-over-rtmp-nginx-rtmp-module-usi

测试代码

项目git地址
关键代码文件如下:


mycv = CvHelp()#我自己的opencv工具类,提供绘图识别工具

# 视频来源 地址需要替换自己的可识别文件地址
filePath='/mnt/e/nginx-rtmp/'
camera = cv2.VideoCapture(filePath+"test2.mp4") # 从文件读取视频
#这里的摄像头可以在树莓派3b上使用
# camera = cv2.VideoCapture(0) # 参数0表示第一个摄像头 摄像头读取视频
# if (camera.isOpened()):# 判断视频是否打开 
#     print 'Open camera'
# else:
#     print 'Fail to open camera!'
#     return
# camera.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)  # 2560x1920 2217x2217 2952×1944 1920x1080
# camera.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
# camera.set(cv2.CAP_PROP_FPS, 5)

# 视频属性 自定义设定
size = (int(camera.get(cv2.CAP_PROP_FRAME_WIDTH)), int(camera.get(cv2.CAP_PROP_FRAME_HEIGHT)))
sizeStr = str(size[0]) + 'x' + str(size[1])
fps = camera.get(cv2.CAP_PROP_FPS)  # 30p/self
fps = int(fps)
hz = int(1000.0 / fps)
print 'size:'+ sizeStr + ' fps:' + str(fps) + ' hz:' + str(hz)

# 视频文件输出
fourcc = cv2.VideoWriter_fourcc(*'XVID')
out = cv2.VideoWriter(filePath+'res_mv.avi',fourcc, fps, size)
# 直播管道输出 
# ffmpeg推送rtmp 重点 : 通过管道 共享数据的方式 将图片帧 通过ffmpeg上传到服务器 上
command = ['ffmpeg',
    '-y',
    '-f', 'rawvideo',
    '-vcodec','rawvideo',
    '-pix_fmt', 'bgr24',
    '-s', sizeStr,
    '-r', str(fps),
    '-i', '-',
    '-c:v', 'libx264',
    '-pix_fmt', 'yuv420p',
    '-preset', 'ultrafast',
    '-f', 'flv', 
     'rtmp://39.107.26.100:1935/myapp/test1' # nginx rtmp 直播模块 服务器直播推流地址
     ]
#管道特性配置 按需测试优化
# pipe = sp.Popen(command, stdout = sp.PIPE, bufsize=10**8)
pipe = sp.Popen(command, stdin=sp.PIPE) #,shell=False
# pipe.stdin.write(frame.tostring())  

#业务数据计算
lineWidth = 1 + int((size[1]-400) / 400)# 400 1 800 2 1080 3
textSize = size[1] / 1000.0# 400 0.45 
heightDeta = size[1] / 20 + 10# 400 20
count = 0
faces = []
while True:
    ###########################图片采集
    count = count + 1
    ret, frame = camera.read() # 逐帧采集视频流
    if not ret:
        break

    if(count % fps == 0):#隔帧处理
    	###########################图片识别检测
        # 探测图片中的人脸 延帧检测 很基本的通用性人脸检测 网上攻略一大把 
        faces = mycv.classfier.detectMultiScale(frame,scaleFactor=1.1,minNeighbors=5,minSize=(5,5))
        pass

    for (x, y, w, h) in faces:#绘制矩形框出人脸区域
        pass
        # cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
        mycv.drawRect(frame, (x, y), (x+w, y+h), (128, 64, 255), line_width=lineWidth )
        # 当发现人脸 进行 操作 
        # 保存图片文件 
        # 记录数据库  
        # 推送 厂商渠道 手机提醒

        pass

    # 绘制推送图片帧信息
    # print(len(faces))
    fpsshow = "Fps  :" + str(int(fps)) + "  Frame:" + str(count)  
    nframe  = "Play :" + str(int(count / fps))
    ntime   = "Time :" + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
    if(count % fps == 0):
        print(fpsshow + " " + ntime)
    mycv.drawText(frame, (0, heightDeta * 1), fpsshow, textSize=textSize, lineWidth=lineWidth )
    mycv.drawText(frame, (0, heightDeta * 2), nframe, textSize=textSize, lineWidth=lineWidth )
    mycv.drawText(frame, (0, heightDeta * 3), ntime, textSize=textSize, lineWidth=lineWidth )

    ############################图片输出
    # 结果帧处理 存入文件 / 推流 / ffmpeg 再处理
    pipe.stdin.write(frame.tostring())  # 存入管道用于直播
    out.write(frame)    #同时 存入视频文件 记录直播帧数据
    pass
camera.release()
# Release everything if job is finished
out.release()
print("Over!")
pass

大概就是:
只通过管道pipe来使用了ffmpeg提供的rtmp推流工具!
当初本想使用python-rtmp包来实现socket推流的,但是几经尝试感觉过于麻烦,还是这样的管道方式比较容易,目前也还比较快,直播延时大概2s (pc上运行


然而,把代码部署到树莓派上,开启实时摄像头采集视频,却发现性能堪忧! 效率低下,卡顿、延时超级严重2333,开始怀疑嵌入式设备是否适合这种行为处理,这还没加上图像识别呢


后续优化思路:

单独对比测试统计各阶段耗时,进行阶段调优

设备视频流每一帧获取耗时、间隔 》》 减小视频采集码率征率分辨率
每一帧识别绘图耗时 》》 隔帧处理 优化处理算法 多线程协作并行处理帧 gpu加速处理?
每一帧ffmpeg推流耗时 》》 优化ffmpeg参数 调整帧画质 分辨率 多进程加速
服务器处理 网络耗时 》》 优化硬件配置 升级服务器 网络带宽
客户端 拉流耗时 》》 升级客户端设备 网络带宽

自己使用手机app集成android rtmp直播模块 拉流效果:
这里写图片描述

要实现这个功能,你可以使用以下步骤: 1. 使用OpenCV来解码和编码视频。你可以使用cv::VideoCapture来解码视频文件,并使用cv::VideoWriter来编码视频文件。 2. 使用FFmpeg推送RTMP。你需要使用FFmpeg的API来打开一个RTMP,并使用avcodec_encode_video2()函数将OpenCV编码后的视频推送中。 3. 将RTMP推送Nginx服务器。你可以使用RTMP协议将视频推送Nginx服务器。在Nginx服务器上,你需要配置一个RTMP模块,并使用推URL将视频推送到服务器上。 以下是一个简单的示例代码,可以实现将OpenCV视频编码并推送Nginx服务器。 ```cpp #include <opencv2/opencv.hpp> #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavutil/opt.h> #include <libavutil/imgutils.h> #include <libavutil/time.h> #include <librtmp/rtmp.h> int main(int argc, char *argv[]) { // OpenCV video capture and video writer cv::VideoCapture cap(argv[1]); cv::Mat frame; cv::VideoWriter writer("output.mp4", cv::VideoWriter::fourcc('M','J','P','G'), 25, cv::Size(640, 480)); // FFmpeg RTMP stream av_register_all(); avcodec_register_all(); AVFormatContext *fmt_ctx = nullptr; AVOutputFormat *out_fmt = nullptr; AVStream *out_stream = nullptr; AVCodec *codec = nullptr; AVCodecContext *codec_ctx = nullptr; AVPacket pkt; int ret = 0; // Open RTMP stream RTMP *rtmp = RTMP_Alloc(); RTMP_Init(rtmp); RTMP_SetupURL(rtmp, "rtmp://localhost/live/mystream"); RTMP_EnableWrite(rtmp); // Connect to RTMP stream if (!RTMP_Connect(rtmp, nullptr)) { if (!RTMP_ConnectStream(rtmp, 0)) { // Create AVFormatContext avformat_alloc_output_context2(&fmt_ctx, nullptr, "flv", "rtmp://localhost/live/mystream"); if (!fmt_ctx) { fprintf(stderr, "Could not create output context\n"); return -1; } // Create video stream out_fmt = fmt_ctx->oformat; codec = avcodec_find_encoder(out_fmt->video_codec); out_stream = avformat_new_stream(fmt_ctx, codec); if (!out_stream) { fprintf(stderr, "Could not create video stream\n"); return -1; } codec_ctx = avcodec_alloc_context3(codec); avcodec_parameters_to_context(codec_ctx, out_stream->codecpar); codec_ctx->width = 640; codec_ctx->height = 480; codec_ctx->time_base = {1, 25}; codec_ctx->framerate = {25, 1}; codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P; avcodec_open2(codec_ctx, codec, nullptr); // Write header avformat_write_header(fmt_ctx, nullptr); // Encode and push frames while (cap.read(frame)) { // Encode frame ret = avcodec_send_frame(codec_ctx, av_frame); if (ret < 0) { fprintf(stderr, "Error sending frame to encoder\n"); break; } while (ret >= 0) { ret = avcodec_receive_packet(codec_ctx, &pkt); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break; else if (ret < 0) { fprintf(stderr, "Error receiving packet from encoder\n"); break; } // Write packet pkt.stream_index = out_stream->index; av_interleaved_write_frame(fmt_ctx, &pkt); av_packet_unref(&pkt); // Push packet to RTMP stream RTMP_Write(rtmp, (char *)pkt.data, pkt.size); } // Write frame to OpenCV video writer writer.write(frame); } // Write trailer av_write_trailer(fmt_ctx); // Close RTMP stream RTMP_Close(rtmp); RTMP_Free(rtmp); // Cleanup avcodec_free_context(&codec_ctx); avformat_free_context(fmt_ctx); } } return 0; } ``` 注意,这只是一个简单的示例代码,还需要进行更多的错误检查和异常处理
评论 45
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值