- 1.はじめに
- 2.C++版OpenCVにおける画像データ
- 3.Python版OpenCVにおける画像データ
- 4.ZeroMQを利用したプロセス間通信
- 5.C++からPythonへの交換
- 6.PythonからC++への交換
- まとめ
1.はじめに
弊社では社長を含めてメンバーの中で一番年上の林です。新卒で業務系ソフトウエア開発会社にエンジニアとして入社したのち、海外での寄り道を経由して、ビジネス番組を衛星放送およびインターネットに配信している会社にて番組企画・プロデューサーとして働いた後、現在は縁あってFuture Standardにて主にRaspberry Piを利用したシステムの開発を行ってます。エンジニア歴としては、UNIXを利用したC言語系システム開発の経験が長い一方、Webのフロント開発は苦手です。
先日のエントリにありましたとおり、弊社ではRaspberry Pi、USBカメラ、Pythonの組み合わせで、簡単に映像解析(顔検知、バーコード読み取り、AI物体認識など)を行うことができるSCORER SDKをリリースしました。そこで、本エントリではSCORER SDKで使われている技術の一部を説明します。
SCORER SDKの内部では、Gstreamer+OpenCVを使ってUSBカメラからの画像取得を行っています。このプログラムはC++で書かれています。
一方、SCORER SDKは使用言語がPythonとなります。そこでSCORER SDK内部では、C++のOpenCV形式で保持されている画像データを、Python形式に交換しています。またSDKで操作された画像データをクラウドにアップロードするプログラムもC++で作られている事から、PythonからC++へのデータ交換も実施しています。この交換方法が本エントリーのテーマです。
2.C++版OpenCVにおける画像データ
例えばある画像の高さ(rows)、幅(cols)、ピクセルデータ(data)がわかっている場合、C++版OpenCVにおいては、下記の手法で画像データ(img)を作成する事ができます。 なお、今回は簡便化の為にC++側、Python側共に画素データの形式はBGR形式を前提とします。
static const int GRAY = 1; cv::Mat create_image(int type, int rows, int cols, void* data){ cv:: Mat img; if( type == GRAY){ //グレイ画像 img = cv::Mat(rows, cols, CV_8UC1 , data); //CV_8UC1 はOpenCVの定数 }else{ //カラー画像 img = cv::Mat(rows, cols, CV_8UC3 , data); //CV_8UC3はOpenCVの定数 } return img; }
3.Python版OpenCVにおける画像データ
Python版OpenCVにてある画像の高さ(rows)、幅(cols)、ピクセルデータ(data)があらかじめ分かっている場合、画像データ(img)の生成方法を考えます。
Python版OpenCVでは、Numpy形式でデータを保持します。そこでピクセルデータをNumpy形式に交換する必要があります。SCORER SDKではピクセルデータをNumpy形式に交換する際、高速化のためNumpyが提供する機能であるfrombufferを利用しています。USBカメラからの画像取得など、連続したデータを扱う場合はfrombufferを利用する事を前提に考えた方が良いでしょう。
GRAY=1 def create_image(type, rows, cols, data): if type == GRAY: # グレイ画像 # 画素データをnumpy形式に交換 img=numpy.frombuffer(data, dtype=np.uint8).reshape((rows, cols)) # グレイデータに交換 gray = cv2.cvtColor(self.image, cv2.COLOR_BGR2GRAY) else: # カラー画像 # 画素データをnumpy形式に交換f。このimgをそのまま利用可能 img=numpy.frombuffer(data, dtype=np.uint8).reshape((rows, cols,3)) return img
frombufferに関する参考記事:http://stackoverflow.com/questions/5674960/efficient-python-array-to-numpy-array-conversion
4.ZeroMQを利用したプロセス間通信
C++版、Python版それぞれのOpenCVにおいて画像データの作成方法が整理出来ましたので、次はC++のプログラムとPythonプログラムの間の通信方法を考えます。先日のエントリにも説明がありますが、SCORER SDKではこのようなプログラム間の通信には、軽量なメッセージング・ミドルウエアであるZeroMQを利用しています。ZeroMQでは利用する接続文字列を変える事で、プログラムを修正する必要なく同一マシン間でのプロセス間通信と、マシンもしくはネットワークを跨いだ通信が可能になります。そこで本エントリーでもZeroMQを利用します。
5.C++からPythonへの交換
それではC++プログラムから読み込んだBMP画像を、Pythonプログラムに送信して、ファイル書き出しするプログラムを見てみます。
5.1: 送信側C++プログラム
送信プログラムでは、引数で与えられた画像ファイルをOpenCV形式に読み込んだのちに、画像の情報をZeroMQを利用して送信します。
#include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include "zmq.hpp" #include <stdlib.h> #include <stdio.h> void my_free(void *data, void *hint) { free(data); } main( int argc, char *argv[]){ cv::Mat image; int i; // File Open if( !strcmp(argv[2], "color") ){ printf("color\n"); image = cv::imread(argv[1], CV_LOAD_IMAGE_COLOR); }else{ printf("gray\n"); image = cv::imread(argv[1], CV_LOAD_IMAGE_GRAYSCALE); } // Image Info int32_t info[3]; info[0] = (int32_t)image.rows; info[1] = (int32_t)image.cols; info[2] = (int32_t)image.type(); // Open ZMQ Connection zmq::context_t context (1); zmq::socket_t socket (context, ZMQ_REQ); socket.connect ("tcp://localhost:5555"); // Send Rows, Cols, Type for(i=0; i<3; i++ ){ zmq::message_t msg ( (void*)&info[i], sizeof(int32_t), NULL ); socket.send(msg, ZMQ_SNDMORE); } // Pixel data void* data = malloc(image.total() * image.elemSize()); memcpy(data, image.data, image.total() * image.elemSize()); // Send Pixel data zmq::message_t msg2(data, image.total() * image.elemSize(), my_free, NULL); socket.send(msg2); return 0; }
5.2: 受信側Pythonプログラム
受信側では、送信プログラムから送られてきた画像情報を、PythonにおけるOpenCV形式に交換したのちに、ファイルに出力します。ZeroMQでは受信されたデータはbyte列となっています。Pythonプログラムにおいて、変数がbyte列のままでは画像の高さ、横幅といった数字を正しく扱えません。そこでこのプログラムは受信したバイト列をPythonで扱える数字に交換するために、struct.upackメソッドを利用してバイト列の交換を行っています。
import zmq import cv2 import struct import numpy as np # Connection String conn_str = "tcp://*:5555" # Open ZMQ Connection ctx = zmq.Context() sock = ctx.socket(zmq.REP) sock.bind(conn_str) # Receve Data from C++ Program byte_rows, byte_cols, byte_mat_type, data= sock.recv_multipart() # Convert byte to integer rows = struct.unpack('i', byte_rows) cols = struct.unpack('i', byte_cols) mat_type = struct.unpack('i', byte_mat_type) if mat_type[0] == 0: # Gray Scale image = np.frombuffer(data, dtype=np.uint8).reshape((rows[0],cols[0])); else: # BGR Color image = np.frombuffer(data, dtype=np.uint8).reshape((rows[0],cols[0],3)); # Write BMP Image cv2.imwrite("recv.bmp", image);
6.PythonからC++への交換
それでは今度はPythonプログラムにて読み込んだBMP画像を、C++プログラムへ送信するプログラムの実例をみてみます。
6.1: 送信側Pythonプログラム
5.1と同様に引数で与えられた画像ファイルをOpenCV形式に読み込んだのち、画像の情報をZeroMQを利用して送信します。
import zmq import cv2 import sys import numpy as np conn_str="tcp://localhost:5556" args = sys.argv ctx = zmq.Context() sock = ctx.socket(zmq.REQ) sock.connect(conn_str) if( args[2] == "color"): # Color img = cv2.imread(args[1], cv2.IMREAD_COLOR); else: # Gray img = cv2.imread(args[1], cv2.IMREAD_GRAYSCALE); height, width = img.shape[:2] ndim = img.ndim data = [ np.array( [height] ), np.array( [width] ), np.array( [ndim] ), img.data ] sock.send_multipart(data)
6.2: 受信側C++プログラム
こちらの受信プログラムも5.2と同様にZeroMQ経由で送信されてきた画像情報をOpenCV形式に交換した後にファイルに出力しています。
#include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include "zmq.hpp" #include <stdlib.h> #include <stdio.h> main( int argc, char *argv[]){ int cnt=0; int rows, cols, type; cv::Mat img; void *data; // Open ZMQ Connection zmq::context_t context (1); zmq::socket_t socket (context, ZMQ_REP); socket.bind("tcp://*:5556"); while(1){ zmq::message_t rcv_msg; socket.recv(&rcv_msg, 0); // Receive Data from ZMQ switch(cnt){ case 0: rows = *(int*)rcv_msg.data(); break; case 1: cols = *(int*)rcv_msg.data(); break; case 2: type = *(int*)rcv_msg.data(); break; case 3: data = (void*)rcv_msg.data(); printf("rows=%d, cols=%d type=%d\n", rows, cols, type); if (type == 2) { img = cv::Mat(rows, cols, CV_8UC1, data); }else{ img = cv::Mat(rows, cols, CV_8UC3, data); } cv::imwrite("recv.bmp", img); break; } if( !rcv_msg.more() ){ // No massage any more break; } cnt++; } return 0; }
まとめ
本エントリでは、C++ <-> Python間におけるOpenCV形式の画像データの交換方法を解説してみました。OpenCV自体はC++で開発されているので、C++でプログラムを書いた方が最新の機能などを利用できるかもしれません。しかし、Python版OpenCVはNumpy形式でデータを扱うため、Numpyが提供する各種機能を活用する事でプログラムをシンプルに書ける場合もあるでしょう。
そこでOpenCVを活用したC++とPythonのプログラムを連携させたい場合が生じた場合などは、本エントリで例示した方法が役立てば幸いです。