詳解opencv?rtsp?硬件解碼
討論使用opencv的reader
硬件解碼的方案有太多種,如果使用ffmpeg硬件解碼是最方便的,不方便的是把解碼過(guò)后的GPU 拉到 CPU 上,再使用opencv的Mat 從cpu 上上載到gpu上,是不是多了兩個(gè)過(guò)程,應(yīng)該是直接從GPU mat 直接去處理, 最后一步再?gòu)腉PU mat 上下載到cpu,render顯示。
GPU 硬件解碼是nv12 格式,我們?yōu)榱孙@示和cpu使用直接轉(zhuǎn)成了RGB或者BGR, 使用opencv再映射封裝,最后又上載到cuda,這個(gè)過(guò)程很耗時(shí)間,而且不是必要的。
windows下使用cuda
經(jīng)過(guò)實(shí)驗(yàn),cv::cudacodec::createVideoReader 是可以拉取rtsp 流的,官方編譯的可以讀取rtsp,但是在文件流上出了問(wèn)題,而且還有一個(gè)bug,就是在顯示的時(shí)候,必須關(guān)閉一次窗口,才能顯示后續(xù)的幀,而且還有一點(diǎn),就是注意這個(gè)窗口必須是opengl 窗口,而且要打開(kāi)這個(gè)窗口,而且在編譯支持cuda的opencv時(shí)必須把opengl 勾選上,所以達(dá)不到產(chǎn)品化的要求,以下是測(cè)試代碼:
#include <iostream> #include "opencv2/opencv_modules.hpp" #if defined(HAVE_OPENCV_CUDACODEC) #include <string> #include <vector> #include <algorithm> #include <numeric> #include <opencv2/opencv.hpp> #include <opencv2/core.hpp> #include <opencv2/core/opengl.hpp> #include <opencv2/cudacodec.hpp> #include <opencv2/highgui.hpp> #if _DEBUG #pragma comment(lib,"opencv_world460.lib") #else #pragma comment(lib,"opencv_world460.lib") #endif int main() { cv::cuda::printCudaDeviceInfo(cv::cuda::getDevice()); int count = cv::cuda::getCudaEnabledDeviceCount(); printf("GPU Device Count : %d \n", count); const std::string fname("rtsp://127.0.0.1/101-640.mkv"); //視頻文件 // const std::string fname("test_222.mp4"); //視頻文件 // cv::namedWindow("CPU", cv::WINDOW_NORMAL); cv::namedWindow("GPU", cv::WINDOW_OPENGL); cv::cuda::setGlDevice(); cv::Mat frame; cv::VideoCapture reader(fname); cv::cuda::GpuMat d_frame; cv::Ptr<cv::cudacodec::VideoReader> d_reader = cv::cudacodec::createVideoReader(fname); cv::TickMeter tm; std::vector<double> cpu_times; std::vector<double> gpu_times; int gpu_frame_count = 0, cpu_frame_count = 0; #if 0 for (;;) { tm.reset(); tm.start(); if (!reader.read(frame)) break; tm.stop(); cpu_times.push_back(tm.getTimeMilli()); cpu_frame_count++; cv::imshow("CPU", frame); if (cv::waitKey(1) > 0) break; } #endif for (;;) { tm.reset(); tm.start(); if (!d_reader->nextFrame(d_frame)) break; tm.stop(); //d_frame.step = d_frame.cols * d_frame.channels(); //cv::cuda::GpuMat gpuMat_Temp = d_frame.clone(); gpu_times.push_back(tm.getTimeMilli()); gpu_frame_count++; if (gpu_frame_count > 2) { cv::Mat test; d_frame.download(test); d_frame.release(); // cv::cvtColor(test, test, cv::COLOR_BGRA2BGR); //cv::imwrite("./test1.jpg", test); cv::imshow("GPU", test); } if (cv::waitKey(1) > 0) break; } if (!cpu_times.empty() && !gpu_times.empty()) { std::cout << std::endl << "Results:" << std::endl; std::sort(cpu_times.begin(), cpu_times.end()); std::sort(gpu_times.begin(), gpu_times.end()); double cpu_avg = std::accumulate(cpu_times.begin(), cpu_times.end(), 0.0) / cpu_times.size(); double gpu_avg = std::accumulate(gpu_times.begin(), gpu_times.end(), 0.0) / gpu_times.size(); std::cout << "CPU : Avg : " << cpu_avg << " ms FPS : " << 1000.0 / cpu_avg << " Frames " << cpu_frame_count << std::endl; std::cout << "GPU : Avg : " << gpu_avg << " ms FPS : " << 1000.0 / gpu_avg << " Frames " << gpu_frame_count << std::endl; } return 0; }
經(jīng)過(guò)release版本的測(cè)試,cuda硬件解碼比cpu慢很多,我cpu是intel 13代 13700,速度很快,gpu是3060ti, 實(shí)際測(cè)試就是如此。說(shuō)明在windows下實(shí)際類里面解碼的時(shí)候在cpu和gpu上轉(zhuǎn)換的時(shí)間太多
綜上所述,必須使用更為簡(jiǎn)單的方法,放棄windows上的做法,放到linux上, ffmpeg硬件解碼 然后映射到gpu mat上,至于解碼ffmpeg 可以看我的其他文章,至于ffmpeg 編解碼 nvidia 上官網(wǎng)也是有介紹的:
編譯ffmpeg
使用python和linux,使用python的作用是取消c++ 到python之間的內(nèi)存共享,在windows上編譯pynvcodec 會(huì)遇到各種問(wèn)題,建議在linux 編譯 pynvcodec,為什么不使用ffmpeg直接解碼,因?yàn)椋何覀兪褂胒fmpeg解碼得到的YUV格式,我們只能在CPU下轉(zhuǎn)化到RGB的色彩空間,缺少在GPU上進(jìn)行全部轉(zhuǎn)化的流程,因此我們使用vpf 來(lái)進(jìn)行python上的視頻處理,同時(shí)結(jié)束時(shí)可以直接轉(zhuǎn)化成pytorch的張量來(lái)處理。
VideoProcessingFramework(VPF)是NVIDIA開(kāi)源的適用于Python的視頻處理框架,可用于硬件加速條件下的視頻編解碼等處理類任務(wù)。同時(shí)對(duì)于Pytorch比較友好,能夠?qū)⒔馕龀鰜?lái)的圖像數(shù)據(jù)直接轉(zhuǎn)化成Tensor()的格式。以下為例子:
import PyNvCodec as nvc import PytorchNvCodec as pnvc while True: # Read data. # Amount doesn't really matter, will be updated later on during decode. bits = proc.stdout.read(read_size) if not len(bits): print("Can't read data from pipe") break else: rt += len(bits) # Decode enc_packet = np.frombuffer(buffer=bits, dtype=np.uint8) pkt_data = nvc.PacketData() try: surf = nvdec.DecodeSurfaceFromPacket(enc_packet, pkt_data) # 獲取流的數(shù)據(jù) # Convert to planar RGB rgb_pln = to_rgb.run(surf) # 轉(zhuǎn)換到rgb_pln if rgb_pln.Empty(): break # PROCESS YOUR TENSOR HERE. # THIS DUMMY PROCESSING JUST ADDS RANDOM ROTATION. src_tensor = surface_to_tensor(rgb_pln) # 轉(zhuǎn)化為Tensor(),數(shù)據(jù)存儲(chǔ)在GPU中 dst_tensor = T.RandomRotation(degrees=(-1, 1))(src_tensor) surface_rgb = tensor_to_surface(dst_tensor, gpu_id) # Convert back to NV12 dst_surface = to_nv12.run(surface_rgb) # 再轉(zhuǎn)換回碼流 if src_surface.Empty(): break # Handle HW exceptions in simplest possible way by decoder respawn except nvc.HwResetException: nvdec = nvc.PyNvDecoder(w, h, f, c, g) continue
使用gstreamer
近來(lái)來(lái)opencv的下載是一個(gè)問(wèn)題,動(dòng)不動(dòng)就下載出錯(cuò),使用gstreamer 在windows下和ffmpeg 差不離,編譯也比較麻煩,我們盡量在linux下編譯
sudo apt-get update sudo apt-get install build-essential cmake git pkg-config sudo apt-get install libjpeg8-dev libtiff4-dev libjasper-dev libpng12-dev sudo apt-get install libgtk2.0-dev sudo apt-get install libavcodec-dev libavformat-dev libswscale-dev libv4l-dev sudo apt-get install libatlas-base-dev gfortran //在opencv里面安裝gstreamer插件 sudo apt-get install gstreamer1.0-tools gstreamer1.0-alsa gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav sudo apt-get install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libgstreamer-plugins-good1.0-dev libgstreamer-plugins-bad1.0-dev cd /home/opencv git clone https://github.com/opencv.git cd opencv git checkout 4.7.0 cd /home/opcv nkdir build cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local -D CUDA_GENERATION=Kepler .. make -j4 sudo make install
int main() { // std::cout << cv::getBuildInformation() << std::endl; using std::chrono::steady_clock; typedef std::chrono::milliseconds milliseconds_type; const int interval = 15; std::stringstream ss; std::string rtsp_url = "rtsp://127.0.0.1/101-640.mkv"; size_t latency = 200; size_t frame_width = 1920; size_t frame_height = 1080; size_t framerate = 15; ss << "rtspsrc location=" << rtsp_url << " latency=" << latency << " ! application/x-rtp, media=video, encoding-name=H264 " << "! rtph264depay ! video/x-h264, clock-rate=90000, width=" << frame_width << ", height=" << frame_height << ", framerate=" << framerate << "/1 ! nvv4l2decoder ! video/x-raw(memory:NVMM), width=" << frame_width << ", height=" << frame_height << ", framerate=" << framerate << "/1 ! nvvideoconvert ! video/x-raw, format=BGRx ! videoconvert ! video/x-raw, format=BGR ! appsink"; std::cout << ss.str() << std::endl; cv::VideoCapture cap = cv::VideoCapture(ss.str(), cv::CAP_GSTREAMER); if (!cap.isOpened()) { std::cerr << "error to open camera." << std::endl; return -1; } std::cout << cv::getBuildInformation() << std::endl; cv::Mat frame; steady_clock::time_point start = steady_clock::now(); size_t frame_idx = 0; while (1) { bool ret = cap.read(frame); if (ret) { // cv::imwrite("tmp.jpg", frame); ++frame_idx; } if (frame_idx % interval == 0) { steady_clock::time_point end = steady_clock::now(); milliseconds_type span = std::chrono::duration_cast<milliseconds_type>(end - start); std::cout << "it took " << span.count() / frame_idx << " millisencods." << std::endl; start = end; } } return 0; }
一點(diǎn)一點(diǎn)排除,在windows上很難復(fù)現(xiàn)很多代碼,很多都是不穩(wěn)當(dāng)?shù)淖龇?,只能做做demo,完全產(chǎn)品化不了,我們目前穩(wěn)定的做法,1 是使用live555 ,下拉 rtsp,ffmpeg 硬件解碼,轉(zhuǎn)成mat,轉(zhuǎn)成gpumat,再轉(zhuǎn)成mat。這個(gè)方案不斷修改吧。等我更新。
到此這篇關(guān)于opencv rtsp 硬件解碼的文章就介紹到這了,更多相關(guān)opencv rtsp 硬件解碼內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python學(xué)習(xí)Selenium介紹及安裝部署詳解
這篇文章主要為大家介紹了python學(xué)習(xí)中Selenium介紹以及如何安裝部署的詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2021-10-10python網(wǎng)絡(luò)爬蟲之模擬登錄 自動(dòng)獲取cookie值 驗(yàn)證碼識(shí)別的具體實(shí)現(xiàn)
有時(shí),我們需要爬取一些基于個(gè)人用戶的用戶信息(需要登陸后才可以查看)就要進(jìn)行模擬登陸,因?yàn)轵?yàn)證碼往往是作為登陸請(qǐng)求中的請(qǐng)求參數(shù)被使用,就需要識(shí)別驗(yàn)證碼2021-09-09最新PyCharm從安裝到PyCharm永久激活再到PyCharm官方中文漢化詳細(xì)教程
這篇文章涵蓋了最新版PyCharm安裝教程,最新版PyCharm永久激活碼教程,PyCharm官方中文(漢化)版安裝教程圖文并茂非常詳細(xì),需要的朋友可以參考下2020-11-11Pandas數(shù)據(jù)合并的兩種實(shí)現(xiàn)方法
本文主要介紹了Pandas數(shù)據(jù)合并的兩種實(shí)現(xiàn)方法,DataFrame數(shù)據(jù)合并主要使用merge()方法和concat()方法,具有一定的參考價(jià)值,感興趣的可以了解一下2023-11-11Python使用PyAudio制作錄音工具的實(shí)現(xiàn)代碼
這篇文章主要介紹了Python使用PyAudio制作錄音工具,音頻錄制與視頻錄制相似,也是以數(shù)據(jù)幀的方式錄制保存,這次使用強(qiáng)大的第三方包PyAudio和內(nèi)置的wave模塊編寫,需要的朋友可以參考下2022-04-04Python中定時(shí)器用法詳解之Timer定時(shí)器和schedule庫(kù)
目前所在的項(xiàng)目組需要經(jīng)常執(zhí)行一些定時(shí)任務(wù),于是選擇使用 Python 的定時(shí)器,下面這篇文章主要給大家介紹了關(guān)于Python中定時(shí)器用法詳解之Timer定時(shí)器和schedule庫(kù)的相關(guān)資料,需要的朋友可以參考下2024-02-02