在C++与python间传视频帧
目錄
- 引言
- 1.進(jìn)程間通信
- 2.基于共享內(nèi)存的視頻傳輸
- 2.1 C++之間的通信
- 2.1.1 接口函數(shù)
- 2.1.2 創(chuàng)建數(shù)據(jù)格式和共享內(nèi)存信息
- 2.1.3 C++之間共享內(nèi)存通信
- 2.1.4 C++之間共享內(nèi)存通信視頻測試結(jié)果
- 2.2 C++和python間視頻通信
- 2.2.1 接口函數(shù)
- 2.2.1 C++與python之間共享內(nèi)存通信
- 3.基于Socket的視頻傳輸
- 3.1 cpp端socket
- 3.2 python端
- 3.3 CMakeList
- 3.4 測試結(jié)果
- 4 結(jié)論
引言
本案例旨在實現(xiàn)跨語言(C++和python間)視頻的實時通信。這一工作內(nèi)容在實際工程中很常見。由于python語言支持很多第三方庫,對于開發(fā)深度學(xué)習(xí)項目很方便,驗真算法速度快,很多開源算法也大多基于python實現(xiàn)。這時可能就會出現(xiàn)C++的代碼借助python語言做一些圖像處理(包括目標(biāo)檢測、姿態(tài)估計、目標(biāo)跟蹤等任務(wù))的需求。
平臺環(huán)境:
- Win10
- VS2019
- OpenCV
進(jìn)程間通信方式:共享內(nèi)存
1.進(jìn)程間通信
進(jìn)程間通信方式有很多種。工程上最常用的是共享內(nèi)存和socket機(jī)制。前者效率高,基本思想就是開辟一塊公共的內(nèi)存空間,供兩個或多個進(jìn)程之間使用。為了標(biāo)識這個公共空間,要給它起個名。但是共享內(nèi)存的方式不支持多平臺。而socket剛好就是支持多平臺間進(jìn)程通信的方式。當(dāng)然這種方式也會慢一些。
在本案例中,分別嘗試了兩種方式。雖然最終共享內(nèi)存的方式寫內(nèi)存幀率只達(dá)到15fps左右,但是要比socket快了近20倍(大概0.5-1fps左右)。下面將介紹這兩種機(jī)制的具體實現(xiàn)過程。
2.基于共享內(nèi)存的視頻傳輸
2.1 C++之間的通信
2.1.1 接口函數(shù)
首先驗證C++之間能通信。這里使用的是CreateFileMapping和MapViewOfFile進(jìn)行共享內(nèi)存的創(chuàng)建和映射。
其中CreateFileMapping的接口為,參數(shù)含義詳解請點擊鏈接。
HANDLE CreateFileMapping(HANDLE hFile,LPSECURITY_ATTRIBUTES lpFileMappingAttributes,DWORD flProtect,DWORD dwMaximumSizeHigh,DWORD dwMaximumSizeLow,LPCSTR lpName );MapViewOfFile的接口為
LPVOID MapViewOfFile(HANDLE hFileMappingObject,DWORD dwDesiredAccess,DWORD dwFileOffsetHigh,DWORD dwFileOffsetLow,SIZE_T dwNumberOfBytesToMap );2.1.2 創(chuàng)建數(shù)據(jù)格式和共享內(nèi)存信息
首先需要一個圖像的頭部
typedef struct {int width;int height;int type; }ImgInf; //圖像信息由于sizeof(int)=4,所以這里ImgInf結(jié)構(gòu)體大小為12B。在進(jìn)行共享內(nèi)存映射時,我們需要這個大小去做偏移量,找到圖像數(shù)據(jù)。
接下來要定義圖像的數(shù)據(jù)信息
#define FRAME_NUMBER 1 // 圖像路數(shù) #define FRAME_W 1920 #define FRAME_H 1080 #define FRAME_W_H FRAME_W*FRAME_H // 圖像分辨率:彩色圖(3通道)+圖像信息結(jié)構(gòu)體 #define FRAME_SIZE FRAME_W_H*sizeof(unsigned char)*3+sizeof(ImgInf)#define MEMORY_SIZE FRAME_NUMBER*FRAME_SIZE定義共享內(nèi)存類SHAREDMEMORY
class SHAREDMEMORY { public:SHAREDMEMORY();~SHAREDMEMORY();//void SendBox(TrackBox& BOX);//void RecBox(TrackBox& BOX);//void SendVectorBox(vector<TrackBox>& VTrackBox);//void RecieveVectorBox(vector<TrackBox>& VTrackBox);void SendMat(cv::Mat img, char indexAddress);cv::Mat ReceiveMat(char indexAddress);void SendStr(const char data[]); char* ReceiveStr();public:int state; private:HANDLE hShareMem; //共享內(nèi)存句柄TCHAR sShareMemName[30] = TEXT("CppPytonSharedFrame"); // 共享內(nèi)存名稱LPCTSTR pBuf; };其中SendMat為圖像數(shù)據(jù)發(fā)送,ReceiveMat為圖像接收。
SendStr為字符串發(fā)送,ReceiveStr為字符串接收。
最后的ShareMemory.h文件如下:
#pragma once // ShareMemory.h : 此文件包含共享內(nèi)存數(shù)據(jù)定義、大小確定、位置分配、信息定義 // Author : Jiejing.Ma // Update : 2020/11/27 #ifndef ShareMemory_H #define ShareMemory_H#include <opencv2/core.hpp> #include <opencv2/videoio.hpp> #include <opencv2/highgui.hpp> #include <opencv2/imgproc.hpp> // cv::Canny() #include <opencv2/opencv.hpp>#include <Windows.h>//=================================共享內(nèi)存數(shù)據(jù)定義================================= typedef struct {int width;int height;int type; }ImgInf; //圖像信息 //=================================共享內(nèi)存大小確定================================= // 為圖像分配空間 #define FRAME_NUMBER 1 // 圖像路數(shù) #define FRAME_W 1920 #define FRAME_H 1080 #define FRAME_W_H FRAME_W*FRAME_H // 圖像分辨率:彩色圖(3通道)+圖像信息結(jié)構(gòu)體 #define FRAME_SIZE FRAME_W_H*sizeof(unsigned char)*3+sizeof(ImgInf)#define MEMORY_SIZE FRAME_NUMBER*FRAME_SIZE//=================================共享內(nèi)存信息定義================================= #define INITSUCCESS 0 #define CREATEMAPFAILED 1 #define MAPVIEWFAILED 2class SHAREDMEMORY { public:SHAREDMEMORY();~SHAREDMEMORY();void SendMat(cv::Mat img, char indexAddress);cv::Mat ReceiveMat(char indexAddress);void SendStr(const char data[]);char* ReceiveStr();public:int state; private:HANDLE hShareMem; //共享內(nèi)存句柄TCHAR sShareMemName[30] = TEXT("ShareMedia"); // 共享內(nèi)存名稱LPCTSTR pBuf; };#endif // !ShareMemory_H對應(yīng)的ShareMemory.cpp文件為類的實現(xiàn)。
#pragma once // ShareMemory.cpp : 此文件包含信息定義SHAREDMEMOR類的實現(xiàn) // Author : MJJ // Update : 2020/11/27 #ifndef ShareMemory_CPP #define ShareMemory_CPP#include "ShareMemory.h" #include <iostream>using namespace cv; using namespace std;/************************************************************************************* FuncName :SHAREDMEMORY::~SHAREDMEMORY() Desc :構(gòu)造函數(shù)創(chuàng)建共享內(nèi)存 Input :None Output :None **************************************************************************************/ SHAREDMEMORY::SHAREDMEMORY() {hShareMem = CreateFileMapping(INVALID_HANDLE_VALUE, // use paging fileNULL, //default securityPAGE_READWRITE, //read/write access0, // maximum object size(high-order DWORD)MEMORY_SIZE, //maximum object size(low-order DWORD)sShareMemName); //name of mapping objectif (hShareMem) {// 映射對象視圖,得到共享內(nèi)存指針,設(shè)置數(shù)據(jù)pBuf = (LPTSTR)MapViewOfFile(hShareMem, //handle to map objectFILE_MAP_ALL_ACCESS, // read/write permission0,0,MEMORY_SIZE);cout << "memory size:" << MEMORY_SIZE<< endl;// 若映射失敗退出if (pBuf == NULL){std::cout << "Could not map view of framebuffer file." << GetLastError() << std::endl;CloseHandle(hShareMem);state = MAPVIEWFAILED;}}else{std::cout << "Could not create file mapping object." << GetLastError() << std::endl;state = CREATEMAPFAILED;}state = INITSUCCESS; }/************************************************************************************* FuncName :SHAREDMEMORY::~SHAREDMEMORY() Desc :析構(gòu)函數(shù)釋放 Input :None Output :None **************************************************************************************/ SHAREDMEMORY::~SHAREDMEMORY() {std::cout << "unmap shared addr." << std::endl;UnmapViewOfFile(pBuf); //釋放;CloseHandle(hShareMem); }/************************************************************************************* FuncName :void SHAREDMEMORY::SendMat(cv::Mat img, char indexAddress) Desc :發(fā)送Mat數(shù)據(jù) Input :Mat img 發(fā)送圖像char indexAddress 共享內(nèi)存中起始位置,若只有一路視頻則無偏移 Output :None **************************************************************************************/ void SHAREDMEMORY::SendMat(cv::Mat img, char indexAddress) {ImgInf img_head;img_head.width = img.cols;img_head.height = img.rows;img_head.type = img.type();if (img_head.type == CV_64FC1) {memcpy((char*)pBuf + indexAddress, &img_head, sizeof(ImgInf));memcpy((char*)pBuf + indexAddress + sizeof(ImgInf), // Address of dstimg.data, // Src dataimg.cols * img.rows * img.channels() * sizeof(double) // size of data);}else{memcpy((char*)pBuf + indexAddress, &img_head, sizeof(ImgInf));memcpy((char*)pBuf + indexAddress + sizeof(ImgInf), // Address of dstimg.data, // Src dataimg.cols * img.rows * img.channels() // size of data); }cout << "write shared mem successful." << endl; }/************************************************************************************* FuncName :cv::Mat SHAREDMEMORY::ReceiveMat(char indexAddress) Desc :接收Mat數(shù)據(jù) Input :char indexAddress 共享內(nèi)存中起始位置,若只有一路視頻則無偏移 Output :Mat圖像 **************************************************************************************/ cv::Mat SHAREDMEMORY::ReceiveMat(char indexAddress) {ImgInf img_head;cv::Mat img;memcpy(&img_head, (char*)pBuf + indexAddress, sizeof(ImgInf));img.create(img_head.height, img_head.width, img_head.type);if (img_head.type == CV_64FC1){memcpy(img.data, (char*)pBuf + indexAddress + sizeof(ImgInf), img.cols * img.rows * img.channels() * sizeof(double));}else{memcpy(img.data, (char*)pBuf + indexAddress + sizeof(ImgInf), img.cols * img.rows * img.channels());}return img; }/************************************************************************************* FuncName :void SHAREDMEMORY::SendStr(cv::Mat img, char indexAddress) Desc :發(fā)送str數(shù)據(jù) Input :Mat img 發(fā)送圖像char indexAddress 共享內(nèi)存中起始位置,若只有一路視頻則無偏移 Output :None **************************************************************************************/ void SHAREDMEMORY::SendStr(const char data[]) {memcpy((char*)pBuf, data, sizeof(data));cout << "write shared mem successful." << endl;getchar(); }/************************************************************************************* FuncName :void SHAREDMEMORY::ReceiveStr() Desc :接收str數(shù)據(jù) Input :None Output :獲取的字符串 **************************************************************************************/ char* SHAREDMEMORY::ReceiveStr(){char* str= (char*)pBuf;cout << "receive is:"<< str << endl;return str; } #endif // !ShareMemory_CPP2.1.3 C++之間共享內(nèi)存通信
創(chuàng)建一個新的工程,導(dǎo)入上面兩個文件,并創(chuàng)建WriteMem.cpp文件
// WriteMem.cpp : 此文件為寫共享內(nèi)存 // Author : Jiejing.Ma // Update : 2020/11/27#include <iostream> #include "ShareMemory.h"using namespace std; using namespace cv;// 讀圖片或視頻 void send_img(SHAREDMEMORY sharedsend) {int index = 0;int64 t0 = cv::getTickCount();;int64 t1 = 0;string fps = "";int nFrames = 0;cv::Mat frame;cout << "Opening video..." << endl;VideoCapture cap("test.flv");while (cap.isOpened()) {cap >> frame;if (frame.empty()){std::cerr << "ERROR: Can't grab video frame." << endl;break;}resize(frame, frame, Size(FRAME_W, FRAME_H));nFrames++;if (!frame.empty()) {if (nFrames % 10 == 0){const int N = 10;int64 t1 = cv::getTickCount();fps = " Send FPS:" + to_string((double)getTickFrequency() * N / (t1 - t0)) + "fps"; t0 = t1;}cv::putText(frame, fps, Point(100, 100), cv::FONT_HERSHEY_COMPLEX, 1, cv::Scalar(255, 255, 255),1);}sharedsend.SendMat(frame, index * FRAME_NUMBER);if ((waitKey(1) & 0xFF) == 'q') break;} }int main() {SHAREDMEMORY sharedmem;//char str[] = "hello";if (sharedmem.state == INITSUCCESS) send_img(sharedmem);//if (sharedmem.state == INITSUCCESS) sharedmem.SendStr(str);return 0; }創(chuàng)建一個新的工程,導(dǎo)入上面兩個文件,并創(chuàng)建ReadMem.cpp文件
// ReadMem.cpp : 此文件為讀共享內(nèi)存 // Author : Jiejing.Ma // Update : 2020/11/27#include <Windows.h> #include <iostream> #include "ShareMemory.h" #include <string>using namespace std; using namespace cv;int main(int argc, char** argv) {int index=0;SHAREDMEMORY sharemem;if (sharemem.state == INITSUCCESS) {// read video frame from shared memory.sint64 t0 = cv::getTickCount();;int64 t1 = 0;string fps = "";int nFrames = 0;namedWindow("ReadMemShow", 0);while (true){nFrames++;Mat frame = sharemem.RecieveMat(index * FRAME_NUMBER);if (!frame.empty()) {if (nFrames % 10 == 0){const int N = 10;int64 t1 = cv::getTickCount();fps = " Average FPS:" + to_string((double)getTickFrequency() * N / (t1 - t0)) + "fps";t0 = t1;}cv::putText(frame, fps, Point(100, 200), cv::FONT_HERSHEY_COMPLEX, 1, cv::Scalar(100, 200, 200),1);imshow("ReadMemShow", frame); }if((waitKey(1) & 0xFF) == 'q') break;}//char* str = sharemem.RecieveStr();}destroyAllWindows();return 0; }同時開啟兩個工程,則可以接收視頻了。
2.1.4 C++之間共享內(nèi)存通信視頻測試結(jié)果
這里看到,寫共享內(nèi)存速度為15fps,讀共享內(nèi)存速度為65fps(超實時),寫速度主要的影響因素與opecv有關(guān)。如果優(yōu)化,還需改視頻編解碼部分。
2.2 C++和python間視頻通信
這里以C++作為發(fā)送端,python作為接受端。逆向過程還有待測試。網(wǎng)上有教程提到python不能創(chuàng)建共享內(nèi)存作為發(fā)送端,這種說法是錯的。本人已測試過,只是發(fā)送數(shù)據(jù)都是字符串型,對于圖像數(shù)據(jù)還有待研究。
2.2.1 接口函數(shù)
這里主要用到的是mmap和numpy的frombuffer.
關(guān)于mmap,請參考官網(wǎng)的接口說明。十分詳細(xì),不再贅述。
frombuffer:
numpy.frombuffer(buffer, dtype=float, count=-1, offset=0)Interpret a buffer as a 1-dimensional array.
Parameters
bufferbuffer_like
An object that exposes the buffer interface.
dtypedata-type, optional
Data-type of the returned array; default: float.
countint, optional
Number of items to read. -1 means all data in the buffer.
offsetint, optional
Start reading the buffer from this offset (in bytes); default: 0.
參考官網(wǎng)
2.2.1 C++與python之間共享內(nèi)存通信
前面已經(jīng)實現(xiàn)C++代碼。不需要改動。只需啟動寫共享內(nèi)存工程即可。
python代碼如下:
import mmap import os import cv2 import numpy as np# -----------------Define info in ShareMemory.h----------------- IMG_HEAD_OFFSET = 12 # typedef struct { # int width; # int height; # int type; # }ImgInf; //圖像信息12字節(jié)FRAME_NUMBER = 1 FRAME_W = 1920 FRAME_H = 1080 FRAME_W_H = FRAME_W * FRAME_H FRAME_SIZE = FRAME_W_H * 3 MEMORY_SIZE = (FRAME_SIZE + IMG_HEAD_OFFSET) * FRAME_NUMBERsShareMemName = "ShareMedia"if __name__ == "__main__":fpx = mmap.mmap(-1, FRAME_SIZE+IMG_HEAD_OFFSET, sShareMemName)# Read img as numpycv2.namedWindow("python_sharedmem_show",0)t0 = cv2.getTickCount()N = 50nFrame = 0fps = 0while 1:nFrame += 1img = np.frombuffer(fpx, dtype=np.uint8)img = img[IMG_HEAD_OFFSET:FRAME_SIZE+IMG_HEAD_OFFSET]img = img.reshape((FRAME_H,FRAME_W,3))# Print Average FPSif nFrame % 50 == 0:t1 = cv2.getTickCount()fps = N*cv2.getTickFrequency() / (t1 - t0)t0 = t1cv2.putText(img, "Average FPS:" + str(fps) + "fps", (100, 200), cv2.FONT_HERSHEY_COMPLEX, 1, (100, 200, 200), 1)cv2.imshow("python_sharedmem_show", img)img = Noneif cv2.waitKey(1) & 0xFF == ord('q'):breakcv2.destroyAllWindows()3.基于Socket的視頻傳輸
這里是基于Linux開發(fā)的Socket通信。而共享內(nèi)存是基于Windows平臺。
3.1 cpp端socket
cppsocket.cpp
#include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h>#include <errno.h> #include <string> #include <iostream> #include <vector>#include <opencv2/opencv.hpp>using namespace cv; using namespace std;int main(int argc, char **argv) {// 定義socket信息char *servInetAddr = "192.168.113.173";int servPort = 8081;int connfd;struct sockaddr_in addr;// 創(chuàng)建socketconnfd = socket(AF_INET,SOCK_STREAM, 0);if (connfd == -1){cout<<"socket創(chuàng)建失敗"<<endl;exit(-1);}// 準(zhǔn)備通信地址addr.sin_family=AF_INET;addr.sin_port=htons(servPort);addr.sin_addr.s_addr = inet_addr(servInetAddr);// bindint res = connect(connfd,(struct sockaddr*)&addr,sizeof(addr));if(res==-1){cout<<"bind連接失敗"<<endl;exit(-1);}cout<<"bind連接成功"<<endl;// 獲取視頻幀并發(fā)送Mat img;VideoCapture capture("./test.flv");vector<uchar> data_encode;while(capture.isOpened()){if(!capture.read(img)) break;imencode(".jpg",img,data_encode);int len_encode = data_encode.size();string len = to_string(len_encode);int length = len.length(); for (int i=0;i<16-length;i++) len=len+' ';// 發(fā)送數(shù)據(jù)send(connfd,len.c_str(),strlen(len.c_str()),0);char send_char[1];for (int i=0;i<len_encode;i++){send_char[0]=data_encode[i];send(connfd,send_char,1,0);}// 接收返回信息char recvBuf[32] = "";if(recv(connfd, recvBuf,32,0)) cout<<recvBuf<<endl;}close(connfd);return 0;}3.2 python端
pysocket.py
import socket import cv2 import numpy import timedef recv_size(sock, count):buf=b''while count:newbuf = sock.recv(count)if not newbuf: return Nonebuf +=newbufcount -= len(newbuf)return bufdef recv_all(sock, count):buf = b''while count:newbuf = sock.recv(1)if not newbuf:return Nonebuf +=newbufcount -= len(newbuf)return buf# 創(chuàng)建socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 準(zhǔn)備通信地址 address = ('192.168.113.173',8081) s.bind(address) s.listen(True) print('Waiting for images...') # 接受TCP鏈接并返回(conn, addr),其中conn是新的套接字對象,可以用來接收和發(fā)送數(shù)據(jù),addr是鏈接客戶端的地址。 conn, addr = s.accept() n = 0while 1:n +=1length = recv_size(conn,16).decode()t0=time.time()if isinstance(length,str): # 若成功接收指定大小信息,進(jìn)一步接收整張圖string_data = recv_all(conn,int(length))data = numpy.fromstring(string_data,dtype='uint8')decimg = cv2.imdecode(data,1)cv2.namedWindow('python-recv')cv2.imshow('python-recv',decimg)if cv2.waitKey(1) & 0xFF == ord('q'):print("111111")breakt1 = time.time()print('Image recieved successfully!fps:'+str(1/(t1-t0)))conn.send('recieved messages!'.encode())t0=t1if cv2.waitKey(1) & 0xFF == ord('q'):breaks.close() cv2.destroyAllWindows()3.3 CMakeList
Linux 下編譯CPP文件,這里使用CMakeList:
cmake_minimum_required(VERSION 3.0.0) project(client VERSION 0.1.0)include(CTest) enable_testing()# find opencv and link find_package(OpenCV REQUIRED) message(STATUS "Opencv library status:") message(STATUS " version:${OpenCV_VERSION}") message(STATUS " libraries:${OpenCV_LIBS}") message(STATUS " include path:${OpenCV_INCLUDE_DIRS}") include_directories(${OpenCV_INCLUDE_DIRS}) link_libraries(${OpenCV_LIBS})add_executable(client cppsocket.cpp)set(CMAKE_CXX_FLAGE "${CMAKE_CXX_FLAGE} -g")3.4 測試結(jié)果
結(jié)果就是速度超級慢,大概一秒多一幀。
4 結(jié)論
C++和python之間通信,可以采用C++調(diào)python的方式。請參考之前的文章。Ubuntu下C++調(diào)python
這種方式,從架構(gòu)的角度來講,最簡單。工程量和已有經(jīng)驗的角度,emm可能坑比較多。速度也應(yīng)該最快(推測)
也可以使用進(jìn)程間通信。當(dāng)然這個成本就高了。有兩種方式,一是共享內(nèi)存機(jī)制,一是socket通信。前者更快,但只能在一個平臺上。后者慢,可以支持不同電腦間通信。
最后關(guān)于基于共享內(nèi)存方式,影響速度的主要是寫共享內(nèi)存,而這又與opencv讀視頻有關(guān),與視頻編解碼有關(guān)。想要提高寫內(nèi)存速度,需要從底層修改視頻編解碼。可以參考UE4的相關(guān)插件解決。
總結(jié)
以上是生活随笔為你收集整理的在C++与python间传视频帧的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: R绘图-KEGG功能注释组间差异分面条形
- 下一篇: python表情,python玩转emo