c++與python間傳輸圖像


 

本文旨在記錄下c++和python之間的圖像傳輸方式。由於項目的需要,我需要將c++編寫的程序中的圖像借助python的深度學習來做一些圖像處理的算法設計。在網上查閱了大量資料后,找到一篇大牛寫個博文,鏈接:https://blog.jiejingma.cn/2020/11/30/communication/zai-c-yu-python-jian-chuan-shi-pin-zheng/

按照大牛寫的博文,我也利用共享內存的方式實現了圖像的跨語言(c++到python)的傳遞,下文具體內容為轉載大牛的博文。

引言

本案例旨在實現跨語言(C++和python間)視頻的實時通信。這一工作內容在實際工程中很常見。由於python語言支持很多第三方庫,對於開發深度學習項目很方便,驗真算法速度快,很多開源算法也大多基於python實現。這時可能就會出現C++的代碼借助python語言做一些圖像處理(包括目標檢測、姿態估計、目標跟蹤等任務)的需求。

平台環境:

  • Win10
  • VS2019
  • OpenCV

進程間通信方式:共享內存

1.進程間通信

進程間通信方式有很多種。工程上最常用的是共享內存socket機制。前者效率高,基本思想就是開辟一塊公共的內存空間,供兩個或多個進程之間使用。為了標識這個公共空間,要給它起個名。但是共享內存的方式不支持多平台。而socket剛好就是支持多平台間進程通信的方式。當然這種方式也會慢一些。

在本案例中,分別嘗試了兩種方式。雖然最終共享內存的方式寫內存幀率只達到15fps左右,但是要比socket快了近20倍(大概0.5-1fps左右)。下面將介紹這兩種機制的具體實現過程。

2.基於共享內存的視頻傳輸

2.1 C++之間的通信

2.1.1 接口函數

首先驗證C++之間能通信。這里使用的是CreateFileMappingMapViewOfFile進行共享內存的創建和映射。

其中CreateFileMapping的接口為,參數含義詳解請點擊鏈接。

 
cpp
HANDLE CreateFileMapping( HANDLE hFile, LPSECURITY_ATTRIBUTES lpFileMappingAttributes, DWORD flProtect, DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, LPCSTR lpName );

MapViewOfFile的接口為

 
cpp
LPVOID MapViewOfFile( HANDLE hFileMappingObject, DWORD dwDesiredAccess, DWORD dwFileOffsetHigh, DWORD dwFileOffsetLow, SIZE_T dwNumberOfBytesToMap );

2.1.2 創建數據格式和共享內存信息

首先需要一個圖像的頭部

 
cpp
typedef struct { int width; int height; int type; }ImgInf; //圖像信息

由於sizeofintint=4,所以這里ImgInf結構體大小為12B。在進行共享內存映射時,我們需要這個大小去做偏移量,找到圖像數據。

接下來要定義圖像的數據信息

 
cpp
#define FRAME_NUMBER 1 // 圖像路數 #define FRAME_W 1920 #define FRAME_H 1080 #define FRAME_W_H FRAME_W*FRAME_H // 圖像分辨率:彩色圖(3通道)+圖像信息結構體 #define FRAME_SIZE FRAME_W_H*sizeof(unsigned char)*3+sizeof(ImgInf) #define MEMORY_SIZE FRAME_NUMBER*FRAME_SIZE

 

圖像數據空間分配
圖像數據空間分配

 

定義共享內存類SHAREDMEMORY

 
cpp
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; //共享內存句柄 TCHAR sShareMemName[30] = TEXT("CppPytonSharedFrame"); // 共享內存名稱 LPCTSTR pBuf; };

其中SendMat為圖像數據發送,ReceiveMat為圖像接收。
SendStr為字符串發送,ReceiveStr為字符串接收。

最后的ShareMemory.h文件如下:

 
cpp
#pragma once // ShareMemory.h : 此文件包含共享內存數據定義、大小確定、位置分配、信息定義 // 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> //=================================共享內存數據定義================================= typedef struct { int width; int height; int type; }ImgInf; //圖像信息 //=================================共享內存大小確定================================= // 為圖像分配空間 #define FRAME_NUMBER 1 // 圖像路數 #define FRAME_W 1920 #define FRAME_H 1080 #define FRAME_W_H FRAME_W*FRAME_H // 圖像分辨率:彩色圖(3通道)+圖像信息結構體 #define FRAME_SIZE FRAME_W_H*sizeof(unsigned char)*3+sizeof(ImgInf) #define MEMORY_SIZE FRAME_NUMBER*FRAME_SIZE //=================================共享內存信息定義================================= #define INITSUCCESS 0 #define CREATEMAPFAILED 1 #define MAPVIEWFAILED 2 class 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; //共享內存句柄 TCHAR sShareMemName[30] = TEXT("ShareMedia"); // 共享內存名稱 LPCTSTR pBuf; }; #endif // !ShareMemory_H

對應的ShareMemory.cpp文件為類的實現。

 
cpp
#pragma once // ShareMemory.cpp : 此文件包含信息定義SHAREDMEMOR類的實現 // 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 :構造函數創建共享內存 Input :None Output :None **************************************************************************************/ SHAREDMEMORY::SHAREDMEMORY() { hShareMem = CreateFileMapping( INVALID_HANDLE_VALUE, // use paging file NULL, //default security PAGE_READWRITE, //read/write access 0, // maximum object size(high-order DWORD) MEMORY_SIZE, //maximum object size(low-order DWORD) sShareMemName); //name of mapping object if (hShareMem) { // 映射對象視圖,得到共享內存指針,設置數據 pBuf = (LPTSTR)MapViewOfFile( hShareMem, //handle to map object FILE_MAP_ALL_ACCESS, // read/write permission 0, 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 :析構函數釋放 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 :發送Mat數據 Input : Mat img 發送圖像 char indexAddress 共享內存中起始位置,若只有一路視頻則無偏移 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 dst img.data, // Src data img.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 dst img.data, // Src data img.cols * img.rows * img.channels() // size of data ); } cout << "write shared mem successful." << endl; } /************************************************************************************* FuncName :cv::Mat SHAREDMEMORY::ReceiveMat(char indexAddress) Desc :接收Mat數據 Input : char indexAddress 共享內存中起始位置,若只有一路視頻則無偏移 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 :發送str數據 Input : Mat img 發送圖像 char indexAddress 共享內存中起始位置,若只有一路視頻則無偏移 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數據 Input :None Output :獲取的字符串 **************************************************************************************/ char* SHAREDMEMORY::ReceiveStr(){ char* str= (char*)pBuf; cout << "receive is:"<< str << endl; return str; } #endif // !ShareMemory_CPP

2.1.3 C++之間共享內存通信

創建一個新的工程,導入上面兩個文件,並創建WriteMem.cpp文件

 
cpp
// WriteMem.cpp : 此文件為寫共享內存 // 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; }

創建一個新的工程,導入上面兩個文件,並創建ReadMem.cpp文件

 
cpp
// ReadMem.cpp : 此文件為讀共享內存 // 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.s int64 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++之間共享內存通信視頻測試結果

這里看到,寫共享內存速度為15fps,讀共享內存速度為65fps(超實時),寫速度主要的影響因素與opecv有關。如果優化,還需改視頻編解碼部分。

C++和python共享內存傳視頻測試結果
C++和python共享內存傳視頻測試結果

 

2.2 C++和python間視頻通信

這里以C++作為發送端,python作為接受端。逆向過程還有待測試。網上有教程提到python不能創建共享內存作為發送端,這種說法是錯的。本人已測試過,只是發送數據都是字符串型,對於圖像數據還有待研究。

2.2.1 接口函數

這里主要用到的是mmapnumpy的frombuffer.

關於mmap,請參考官網的接口說明。十分詳細,不再贅述。

frombuffer:

 
python
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 inbytesinbytes; default: 0.

參考官網

2.2.1 C++與python之間共享內存通信

前面已經實現C++代碼。不需要改動。只需啟動寫共享內存工程即可。

python代碼如下:

 
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字節 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_NUMBER sShareMemName = "ShareMedia" if __name__ == "__main__": fpx = mmap.mmap(-1, FRAME_SIZE+IMG_HEAD_OFFSET, sShareMemName) # Read img as numpy cv2.namedWindow("python_sharedmem_show",0) t0 = cv2.getTickCount() N = 50 nFrame = 0 fps = 0 while 1: nFrame += 1 img = 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 FPS if nFrame % 50 == 0: t1 = cv2.getTickCount() fps = N*cv2.getTickFrequency() / (t1 - t0) t0 = t1 cv2.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 = None if cv2.waitKey(1) & 0xFF == ord('q'): break cv2.destroyAllWindows()

3.基於Socket的視頻傳輸

這里是基於Linux開發的Socket通信。而共享內存是基於Windows平台。

3.1 cpp端socket

cppsocket.cpp

 
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; // 創建socket connfd = socket(AF_INET,SOCK_STREAM, 0); if (connfd == -1) { cout<<"socket創建失敗"<<endl; exit(-1); } // 准備通信地址 addr.sin_family=AF_INET; addr.sin_port=htons(servPort); addr.sin_addr.s_addr = inet_addr(servInetAddr); // bind int res = connect(connfd,(struct sockaddr*)&addr,sizeof(addr)); if(res==-1) { cout<<"bind連接失敗"<<endl; exit(-1); } cout<<"bind連接成功"<<endl; // 獲取視頻幀並發送 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+' '; // 發送數據 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

 
python
import socket import cv2 import numpy import time def recv_size(sock, count): buf=b'' while count: newbuf = sock.recv(count) if not newbuf: return None buf +=newbuf count -= len(newbuf) return buf def recv_all(sock, count): buf = b'' while count: newbuf = sock.recv(1) if not newbuf:return None buf +=newbuf count -= len(newbuf) return buf # 創建socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 准備通信地址 address = ('192.168.113.173',8081) s.bind(address) s.listen(True) print('Waiting for images...') # 接受TCP鏈接並返回(conn, addr),其中conn是新的套接字對象,可以用來接收和發送數據,addr是鏈接客戶端的地址。 conn, addr = s.accept() n = 0 while 1: n +=1 length = recv_size(conn,16).decode() t0=time.time() if isinstance(length,str): # 若成功接收指定大小信息,進一步接收整張圖 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") break t1 = time.time() print('Image recieved successfully!fps:'+str(1/(t1-t0))) conn.send('recieved messages!'.encode()) t0=t1 if cv2.waitKey(1) & 0xFF == ord('q'): break s.close() cv2.destroyAllWindows()

3.3 CMakeList

Linux 下編譯CPP文件,這里使用CMakeList:

 
shell
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 測試結果

結果就是速度超級慢,大概一秒多一幀。

4 結論

C++和python之間通信,可以采用C++調python的方式。請參考之前的文章。Ubuntu下C++調python
這種方式,從架構的角度來講,最簡單。工程量和已有經驗的角度,emm可能坑比較多。速度也應該最快(推測)

也可以使用進程間通信。當然這個成本就高了。有兩種方式,一是共享內存機制,一是socket通信。前者更快,但只能在一個平台上。后者慢,可以支持不同電腦間通信。

最后關於基於共享內存方式,影響速度的主要是寫共享內存,而這又與opencv讀視頻有關,與視頻編解碼有關。想要提高寫內存速度,需要從底層修改視頻編解碼。可以參考UE4的相關插件解決。


來源: 馬捷徑
文章作者: Jiejing.Ma
文章鏈接: https://blog.jiejingma.cn/2020/11/30/communication/zai-c-yu-python-jian-chuan-shi-pin-zheng/
本文章著作權歸作者所有,任何形式的轉載都請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM