簡単なビデオストリーミング

サーバ・クライアントモデルで、シングルクライアントのサンプルです。IplImage構造体をネットワーク上で送受信することで、簡単なビデオストリーミングを実現しています。

sample_04.png

Fig.1 とても簡単なイメージ図

ところが IplImage構造体の大きさは一定ではなく、また画像データは IplImage->imageData構造体に入っているため、正確な大きさをまじめに調べて送信するのは面倒 ( ゚Д゚)マンドクセーので、とりあえずデータサイズを決め打ちでやってます。すいません。きちんとすべてのデータを送るときは、cvCopy()関数などの実装を参考に作ると良いと思います。cvCopy()関数の実体は、opencv\cxcore\src\cxcopy.cpp にあります。

またサンプルでは、無圧縮の画像サイズが約230KBあります。これを 30 frame/secで送ると仮定すると、ネットワーク帯域が 6.9MB/sec = 55.2Mbps必要です。簡単に実装するなら、簡単な差分データを生成するか、データの送受信に JPEGを使うと良いと思います。

ということで、もう少しまじめに実装したものが、NetCVです。
便利だと思いますので、ぜひご利用ください。

サーバ側ソースコード

/**************************************************************************************************
   Title           : OpenCV サンプルプログラム - サーバ
   Programer       : Yousuke FURUSAWA
   Comitter        : Yousuke FURUSAWA.
   Copyright       : Copyright (C) Yousuke FURUSAWA. 2006
   Since           : 2006/05/09 16:50:53

   Filename        : server.c
   Last up date    : 2006/05/09 17:26:31
   Kanji-Code      : Shift JIS
   TAB Space       : 4
**************************************************************************************************/


/*=================================================================================================
ヘッダファイルのインクルード
=================================================================================================*/
#include <windows.h>
#include <winsock.h>
#pragma comment(lib, "WSock32.lib")

#include <stdio.h>

#include <cv.h>
#include <highgui.h>
#pragma comment(lib, "cv.lib")
#pragma comment(lib, "cvaux.lib")
#pragma comment(lib, "cvcam.lib")
#pragma comment(lib, "cvhaartraining.lib")
#pragma comment(lib, "highgui.lib")


/*=================================================================================================
プロトタイプ宣言
=================================================================================================*/
SOCKET getServerSocket(unsigned short portnumber);


/**************************************************************************************************
スタートアップ関数
**************************************************************************************************/
int main(int argc, char **argv){
   char buf[32];
   IplImage *frame;
   CvCapture *capture;
   WSADATA wsaData;
   SOCKET socket;

   /* デバイスを開く */
   if( !(capture = cvCaptureFromCAM(0)) ){
       printf("ERROR: Could not open Camera Device\n");
       return(-1);
   }

   /* WinSockの初期化 */
   if( WSAStartup(MAKEWORD(2, 1), &wsaData) != 0){
       printf("ERROR: WSAStartup");
       return(-2);
   }

   /* 接続待ち... */
   if( (socket = getServerSocket(5030)) == -1){
       printf("ERROR: Cound not connet\n");
       return(-3);
   }

   /* 初期化 */
   cvNamedWindow("result", 1);
   Sleep(500);

   /* データを送り続ける */
   while( cvWaitKey(1) != 'q' ){
       if( !(frame = cvQueryFrame(capture)) ) return(-4);
       send(socket, frame, 112, 0);
       while(recv(socket, &buf, 2, 0) != 2) Sleep(0);
       send(socket, frame->imageData, 230400, 0);
       while(recv(socket, &buf, 2, 0) != 2) Sleep(0);
       cvShowImage("result", frame);
       Sleep(500);
   }

   /* 終了処理 */
   cvDestroyWindow("result");
   shutdown(socket, 2);
   closesocket(socket);
   WSACleanup();
   return(0);
}


/**************************************************************************************************
サーバ機能 : Programmed By Junichi KOSAKA. 2003
**************************************************************************************************/
SOCKET getServerSocket(unsigned short portnumber)
{
   WORD wVersionRequested = MAKEWORD(2,0); // Winsock 2.0 requested
   WSADATA wsaData;                        // Winsock details
   SOCKET s;                               // socket descriptor
   SOCKET cliantS;                         // cliant socket descriptor
   SOCKADDR_IN addr;                       // Internet address
   SOCKADDR_IN cliantAddr;                 // Internet address
   int cliantAddrLen;

   /* Winsock の初期化 */
   if(WSAStartup(wVersionRequested, &wsaData) != 0){
       //printf("WSAStartup() unsuccessful");
       return -1;
   }

   /* 要求したWinsock のバージョンが使用できるかを確認する */
   if(LOBYTE(wsaData.wVersion) != LOBYTE(wVersionRequested) || 
       HIBYTE(wsaData.wVersion) != HIBYTE(wVersionRequested)){
       //printf("Version of Winsock does not agree\n\n");
       WSACleanup(); // terminate Winsock use
       return -1;
   }

   /* ストリームソケットの作成 */
   s = socket(AF_INET, SOCK_STREAM, 0); // AF_INET・SOCK_STREAM の指定によってTCP/IP設定となる

   if(s != INVALID_SOCKET){
       memset(&addr, 0, sizeof(addr));
       /* ソケットのアドレス情報を記入する */
       addr.sin_family = AF_INET;                 // address family
       addr.sin_port = htons(portnumber);         // service port
       addr.sin_addr.s_addr = htonl(INADDR_ANY);  // Internet address
   }else{
       //printf("socket() generated error\n");
       return -1;
   }

   /* ソケットに名称を付与する(ソケットとポートの結合) */
   if(bind(s, (LPSOCKADDR)&addr, sizeof(addr)) == SOCKET_ERROR){
       //printf("bind() generated error\n");
       return -1;
   }

   /* 接続を聴取する */
   if(listen(s, 1) == SOCKET_ERROR){// backlog は 接続するクライアントの数を指定(1<=backlog<=5)
       //printf("listen() generated error\n");
       return -1;
   }
   
   printf("Waiting for connection ...  port = %d\n",portnumber);
   cliantAddrLen = sizeof(cliantAddr);

   /* 接続を受容する */
   cliantS = accept(s, (LPSOCKADDR)&cliantAddr, &cliantAddrLen);
   if(cliantS != INVALID_SOCKET){
/*
       cAddr.s_addr = cliantAddr.sin_addr.s_addr;
       host = gethostbyaddr((const char *)&cAddr , 4 , AF_INET);
       if(host){ // クライアントの名前を獲得するのに成功
           printf("Connected from %s [%s]    port = %d\n",
           inet_ntoa(cliantAddr.sin_addr),host->h_name,ntohs(cliantAddr.sin_port));
       }else{
           printf("Connected from %s [%s]    port = %d\n",
           inet_ntoa(cliantAddr.sin_addr),host->h_name,ntohs(cliantAddr.sin_port));
       }
       */
       return cliantS;
   }else{
       //printf("accept() generated error");   
       return -1;
   }
}

クライアント側ソースコード

/**************************************************************************************************
   Title           : OpenCV サンプルプログラム - クライアント
   Programer       : Yousuke FURUSAWA
   Comitter        : Yousuke FURUSAWA.
   Copyright       : Copyright (C) Yousuke FURUSAWA. 2006
   Since           : 2006/05/09 16:50:53

   Filename        : client.c
   Last up date    : 2006/05/09 17:26:38
   Kanji-Code      : Shift JIS
   TAB Space       : 4
**************************************************************************************************/


/*=================================================================================================
ヘッダファイルのインクルード
=================================================================================================*/
#include <windows.h>
#include <winsock.h>
#pragma comment(lib, "WSock32.lib")

#include <stdio.h>

#include <cv.h>
#include <highgui.h>
#pragma comment(lib, "cv.lib")
#pragma comment(lib, "cvaux.lib")
#pragma comment(lib, "cvcam.lib")
#pragma comment(lib, "cvhaartraining.lib")
#pragma comment(lib, "highgui.lib")


/*=================================================================================================
構造体
=================================================================================================*/
/* サーバ情報 */
typedef struct {
   SOCKET socket;
   struct sockaddr_in addr;
   struct hostent *hp;
} Network;
typedef Network* pNetwork;


/*=================================================================================================
プロトタイプ宣言
=================================================================================================*/
static pNetwork NETWORK_open(char *server, int port);
static int NETWORK_close(pNetwork network);


/**************************************************************************************************
スタートアップ関数
**************************************************************************************************/
int main(int argc, char **argv){
   IplImage frame;
   char data1[230400], data2[230400];
   WSADATA wsaData;
   pNetwork network;

   /* WinSockの初期化 */
   if( WSAStartup(MAKEWORD(2, 1), &wsaData) != 0){
       perror("WSAStartup");
       return(-1);
   }

   /* サーバに接続 */
   if((network = NETWORK_open("192.168.1.208", 5030)) == NULL){
       perror("NETWORK_open(television)");
       return(-2);
   }

   /* 初期化 */
   cvNamedWindow("result", 1);

   /* データを取得し続ける */
   while( cvWaitKey(1) != 'q' ){
       while(recv(network->socket, &frame, 112, 0) != 112);
       send(network->socket, "ok", 2, 0);
       while(recv(network->socket, &data1, 230400, 0) != 230400);
       frame.imageData = &data1;
       cvShowImage("result", &frame);
       send(network->socket, "ok", 2, 0);
       Sleep(0);
   }

   /* 終了処理 */
   cvDestroyWindow("result");
   NETWORK_close(network);
   return;
}


/*-------------------------------------------------------------------------------------------------
サーバに接続する
-------------------------------------------------------------------------------------------------*/
static pNetwork NETWORK_open(char *server, int port)
{
   pNetwork network;

   /* メモリを確保 */
   if((network = (pNetwork)malloc(sizeof(Network))) == NULL){
       perror("malloc(network)");
       return(NULL);
   }

   /* 
    * addrの中身を0にしておかないと、bind()でエラーが起こることがある
    */
   memset((char *)&network->addr, 0, sizeof(network->addr));

   /*
    *  ソケットを作る。このソケットはUNIXドメインで、ストリーム型ソケット。
    */
   if ((network->socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
       perror("socket");
       free(network);
       return(NULL);
   }


   /*
    * ソケットの名前を入れておく
    */
   if ((network->hp = gethostbyname(server)) == NULL) {
       perror("No such host");
       free(network);
       return(NULL);
   }

   memcpy(&network->addr.sin_addr, network->hp->h_addr, network->hp->h_length);
   network->addr.sin_family = AF_INET;
   network->addr.sin_port = htons(port);

   /*
    *  サーバーとの接続を試みる。これが成功するためには、サーバーがすでに
    *  このアドレスをbind()して、listen()を発行していなければならない。
    */
   if (connect(network->socket, (struct sockaddr *)&network->addr, sizeof(network->addr)) < 0){
       perror("connect");
       free(network);
       return(NULL);
   }

   return(network);
}


/*-------------------------------------------------------------------------------------------------
切断する
-------------------------------------------------------------------------------------------------*/
static int NETWORK_close(pNetwork network)
{
   closesocket(network->socket);
   free(network);
   return(TRUE);
}

スクリーンショット

sample_05.png

Fig.2 サーバ側接続待ち

 
sample_06.png

Fig.3 サーバ側接続後

 
sample_07.png

Fig.4 クライアント側

添付ファイル: filesample_07.png PPV件 [詳細] filesample_06.png Expires: Tkh件 [詳細] filesample_05.png 1452件 [詳細] filesample_04.png PPR件 [詳細]