2012年12月9日日曜日

ソケット通信によるpythonとC++の連携

pythonでインターフェース作ったり、Arduinoのコントロールしながら、
C++での画像処理結果(前に何があるか、とか)をpythonに送りたくなったのでソケット通信でやって見ました。

pythonだとopencvで特徴量記述とかが出来ないし(そもそも遅いし)、C++でインターフェースとかシリアル通信なんかやってられないので、双方の弱点をカバーする方法としてプロセス間通信が最も横着な方法であると判断。

参考文献:
1、Pythonメモ 様の記事 ttp://yoshi-python.blogspot.jp/2009/10/blog-post_19.html
2、Geekなぺーじ 様の記事 ttp://www.geekpage.jp/programming/linux-network/tcp-1.php
3、信州大学HP 様 ttp://intuniv.cs.shinshu-u.ac.jp/Lecture/NetProg/chapter3/struct.html
4、pythonのリファレンス


■ 今回はpythonをサーバとしてC++側と双方向通信させました。 ちなみにサーバとクライアントの違いについてはよくわかってません。接続を待つ方がサーバぐらいの認識でいいのだろうか。

以下、参考文献のページのコードをほぼ引用した自分用のメモ

python側:

import socket 

def server(host, port):
    s = socket.socket()      ←ソケットのインスタンスを作る
    s.bind((host, port))   ←バインド(IPとポートを設定する)
    s.listen(1)       ←接続待ちに必要。引数は接続できるノードの数。
    print "waiting"
    conn, addr = s.accept()  #  ←クライアントの接続を待つ。connはつながってきたソケット
               addrはノードのアドレス(使い所は不明)
    while True:   ←ずっとループ

        data = conn.recv(1024)  # クライアントからのデータを受信する(ここでは1024byte)
              ↑ここでのdataの形式がよくわからないが多分char(C++側ではconst charで送ってる)  
        print "server: receive '%s'" % data

        datalist=str(data)  ←とりあえずデータをstring化

        datasp=datalist.split(",") ←データをカンマで区切る。数字をカンマ区切りで送ることを考えている。

        print datasp
        object.set_value(float(datasp[0])) 
  ↑任意の変数に受信データの値を入力。これでArduinoでもなんでも動かせる(pythonと繋がってれば)
       
        conn.send("sendfrompython")  # クライアントにデータを送信する。ここでpythonからの命令(右に曲がるから右に注目しろ、とか)をC++に送ることが出来る。

    conn.close()  ←クライアントのソケットを閉じる。
    s.close()    ←こっちのソケットを閉じる。 
閉じてもすぐに切断されるわけではないらしい。連続で同じポート使ったりできないことがあるので時間開けるかポート番号変えるのが吉。いまのところpython-C++ではこの事象は起こってないがpythonのプログラム間(同ソース別スレッド)でこの事象を確認。

class Server(threading.Thread):######### C++との通信スレッド
    def run(self):
        
            server(HOST, PORT) ←上記関数。HOST(127.0.0.1とか)とPORT(12345とか)はグローバル変数か何かにしとく


C++側:参考資料2からほぼ引用、難しそうな構造体については参考文献3を参照
 
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
↑必要なヘッダの皆さん。特に意識せずに使えたので最初から入ってると思われる。



 void* thread(void* pParam)  //pythonへの情報送信用スレッド
{
////ソケット通信による通信
int sockmode=1;   ////1ならソケット通信ON ←通信をONOFFできて便利

if(sockmode==1){
struct sockaddr_in server;  ←アドレスなんかを入れておく構造体インスタンスを定義

 int sock;    ←ソケットをとりあえず定義
 char buf[64];  ←バッファを定義[]内はバイト数。多すぎず少なすぎずで。(少ないと即死)


 int n;  ←データ読み込み用

 /* ソケットの作成 */
 sock = socket(AF_INET, SOCK_STREAM, 0); 
↑AF_INETはIPv4であること、SOCK_STREAMはTCPであることを表している。多分どの言語でも共通の定数。ここらを変えるとIPv6にしたりUDPにもできる。サーバと共通じゃないとだめ。

 /* 接続先指定用構造体の準備 */↓python側の設定を書く。つまり宛先の設定
 server.sin_family = AF_INET;
 server.sin_port = htons(12347); ←ポート番号。htons()はバイトオーダを変換する関数
                 (詳細不明。接続のための何らかの規格化だろう) 
 server.sin_addr.s_addr = inet_addr("127.0.0.1");←このIPは自分自身(PC)を示す。変えると別の機器(PC他)にももちろんつながる。はず。関数は文字列をちゃんとアドレスの形式にする関数

 /* サーバに接続 */↓詳細は「C++ connect socket」でぐぐってね
 connect(sock, (struct sockaddr *)&server, sizeof(server));

while(1){ /* サーバからデータを受信。 送信も */
    memset(buf, 0, sizeof(buf)); ←バッファのリセット
    stringstream sts; ←とっても便利なstringstream(以下ss)オブジェクトを定義

    string str="0,0,end"; ←送信文字列の初期化。文字は適当に
 最後の「,end」はとても大事。python側でsplitするときとか特に





↓   別のところで「進んでOK」とか「止まれ」とかのフラグ出すようにしてる。それに応じて送信値変更。明らかに改善の余地あり。
 if(forwardflag ==1){str="100,100,end";}

    if(stopflag==1){str="0,0,end";}


    cout << "send:" <<str <<endl;

    char sdata[64]; ←送信用バッファの定義
    sts <<  str;  ←事前に作った文字列をssへ
    sts >> sdata; ←ssから送信用charへ自動変換&代入。このツールまじ便利


    const char* sdataconst= sdata; ←charをconst charへ送信関数の引数がconst〜なので(このへんの変換でちょっと詰まった。)

    write(sock, sdataconst, strlen(sdataconst));←データをあっちに送信。第3引数は送信データの文字数にする。strlenは全角文字入ると正確じゃなくなるので注意

    cout<<"senddata"<<sdataconst <<endl;

    int n = read(sock, buf, sizeof(buf)); ←pythonからbufへ読み込み。nは読み込めたかどうかとかを確認するための数字(読み込め無かったら-1とか)。読み込みデータじゃないので注意
    cout <<"resdata" <<buf << endl;

    sleep(1);←1秒に1回通信。もっと少なくてもいいけどスリープ入れないと多分ろくなことにならない

    sts.clear(); ←ssの中身をクリア
}
 /* socketの終了 */
 close(sock); ←ソケット閉じる
}

 return 0;


以上。
これでとりあえず通信ができることを確認。強制終了前提のコードなのであくまで参考で。

↓左がC++で右がpython(eclipseのコンソール)。なんとか通信している。








2012年12月1日土曜日

ここのところ覚えた小技のメモ

いろいろアイデアを考えるのはいいけど、結構実装の面で詰まることも多かったので詰まったところをメモ。主にC++の基本的なところとちょっとした小技(?)、忘れると1〜数日潰す可能性あり。

参考資料 : ググって出てきた様々なページ。ヤフー知恵袋、stackoverflow

1.vectorのvectorの作り方とvectorのきほん

 vector<vector<double> > ←最後の「>」の手前に半角スペース入れないとだめ(環境に因るかも)


2.vectorの要素の追加

  (vector).push_back(入れたい値) でvectorの最後尾に新しい要素を入れる。

カラのvectorの定義だけして vector[n]=値 とかで代入しようとするとエラーになる。
まずvector.push_back=値 またはassign()とかresize()適当なサイズと初期値を設定する。
vectorのvectorならvector.push_back=2層目vector<〜〜> みたいな感じで使う 。

pythonのリストなら適当でなんとかなるのにね。不便だね。


3.opencvのROIの使い方

 Rect roirect=Rect(0,0,150,150);// まず四角を定義してROIの設定


 Mat imgroi=img;// ←ここでimg(roirect)  にすると定義した四角がROIになったimgをimgroiとして定義できる。

 
ここまではいいがROIのオフセット(ROIの四角の起点になる座標)が(0,0)じゃないときオフセットを考慮する必要がある。 やらないと例えばROIが画面中心らへんだった時検出したキーポイントが画面中心じゃなくて左上とかにずれて表示される。

↓こんな感じ




    for(int i=0;i<keypoint.size();i++){
        keypoint.pt.x += roirect.x;  ←検出したキーポイントの座標をオフセット分だけずらす
        keypoint.pt.y += roirect.y;  ←キーポイント構造体の座標取り出し方法
        (〜.pt.x, 〜.pt.y)も意外に忘れやすいので注意
    }

↑キーポイントはROIの中だけで検出されるけどキーポイント座標(pt.x pt.y)もROIのローカル座標でが決定されるのでオフセット分ずらす必要がある(オフセットが(0,0)なら別にずらさなくていい)。

 4.文字扱いの数字を数値として取り出す方法

ファイルとかから読み込んだ数値をちゃんとintなりdoubleで読み込む(変換する)コツ。stringstream を使うだけ。これもpythonなら意識する必要すらない部分。C++万歳!(皮肉)

↓以下やり方。

stringstream ss; ←stringstreamのインスタンスをつくる

string str;  ←stringと任意の形式(この例ではdouble)を定義
double dbl;

ss << str;// ←stringstreamにstringをぶち込む
ss >>dbl;  ←stringstreamからdoubleに取り出す。これで変換完了。




ss.clear(); ←ssの中身をクリア、しないとろくなことにならないよ。ループで使う時とか。

 5.opencv:任意の座標の色情報の取り出し方

意外に手こずった部分。Vec3bという型を作って以下の書式で取り出す。

 Vec3b color = img.at<Vec3b>(Y座標,X座標); ←.at<Vec3b>はおそらくY座標、X座標の順で定義されている。普通と違うので注意(環境やimgの種類によってちがうかも)。
また、取り出した色情報((B,G,R),0〜256の数字)はデフォルトだと多分charで入ってるので
intへのキャストが必要↓

 (int)ccolor[0]+100 ;   ←例えば取り出した色の青色成分に100足すとき


今日のところはとりあえず終了。