摸索:opencv結合socket的網絡圖像傳輸

這是半年前做機器人項目時候想到的。當時的整個設想是這樣:以樹莓派爲主控的機器人用攝像頭獲取圖像,然後傳到我的電腦上。因爲想進行機器人的物體識別,所以把攝像頭採集到的圖像傳上來比較直觀,也便於操作。但最後整個項目還是以失敗告終,畢竟自己的水平不夠。。。不久前無意看到了socket網絡編程,又想到了之前的那個項目,所以還是很想翻出來繼續做下去。

前兩天在archlinux下編譯安裝好了opencv,稍微學習了一下下,主要是用攝像頭採集圖片。然後就一直在考慮怎麼把攝像頭採集的圖像frame傳輸過去。

先是socket,有write和read,send和recv函數,但是這兩個函數傳輸的數據都是char型的,而圖片是IplImage類型的數據。這樣子就肯定無法直接傳嘛,所以要分析處理一下:

IplImage結構體:

typedef struct _IplImage
{
    int  nSize;             /* sizeof(IplImage) */
    int  ID;                /* version (=0)*/
    int  nChannels;         /* Most of OpenCV functions support 1,2,3 or 4 channels */
    int  alphaChannel;      /* Ignored by OpenCV */
    int  depth;             /* Pixel depth in bits: IPL_DEPTH_8U, IPL_DEPTH_8S, IPL_DEPTH_16S,
                               IPL_DEPTH_32S, IPL_DEPTH_32F and IPL_DEPTH_64F are supported.  */
    char colorModel[4];     /* Ignored by OpenCV */
    char channelSeq[4];     /* ditto */
    int  dataOrder;         /* 0 - interleaved color channels, 1 - separate color channels.
                               cvCreateImage can only create interleaved images */
    int  origin;            /* 0 - top-left origin,
                               1 - bottom-left origin (Windows bitmaps style).  */
    int  align;             /* Alignment of image rows (4 or 8).
                               OpenCV ignores it and uses widthStep instead.    */
    int  width;             /* Image width in pixels.                           */
    int  height;            /* Image height in pixels.                          */
    struct _IplROI *roi;    /* Image ROI. If NULL, the whole image is selected. */
    struct _IplImage *maskROI;      /* Must be NULL. */
    void  *imageId;                 /* "           " */
    struct _IplTileInfo *tileInfo;  /* "           " */
    int  imageSize;         /* Image data size in bytes
                               (==image->height*image->widthStep
                               in case of interleaved data)*/
    char *imageData;        /* Pointer to aligned image data.         */
    int  widthStep;         /* Size of aligned image row in bytes.    */
    int  BorderMode[4];     /* Ignored by OpenCV.                     */
    int  BorderConst[4];    /* Ditto.                                 */
    char *imageDataOrigin;  /* Pointer to very origin of image data
                               (not necessarily aligned) -
                               needed for correct deallocation */
}
IplImage;
這個結構體裏不僅有普通的int和char的變量,還有char型的指針,結構體指針。所以,這裏的普通變量和指針變量要分開傳。

我把這個結構體理解成index和data,index中包含的是普通變量,data即結構提中的imageData變量。

index比較簡單,可以這樣發送:

IplImage *frame = 0;
write(skfd, (char*)frame, sizeof(IplImage));

提一下,我這裏暫時不考慮socket傳輸的效率問題,只要能把數據傳成功即可,優化什麼的之後在進行~

接收端:

IplImage header, *frame = 0;
read(cnfd, (char*)&header, sizeof(IplImage));
frame = cvCreateImageHeader(cvSize(header.width, header.height), header.depth, header.nChannels);
這樣之後接受到的frame的index就和發送方完全相同了。

但接下來問題就出來了!!!怎麼傳imageData。。。

發送方完全沒有問題,因爲imageData本身就是char*型,而且send函數的參數也是char*型,直接用下面的代碼發送~

write(skfd, frame->imageData, frame->imageData);
但是接收方需要接收這麼大一個char*型,就需要分配內存空間:

cahr  *databuff;
databuff = (char*)malloc(frame->imageSize);
用read把數據存儲到databuff中。恩,通過打印frame->imageData和databuff測試一下,數據接收到了~

然後把databuff和frame->imageData連接起來:

frame->imageData = databuff;
這樣以後,接收放就接收到發送方的一幀圖片,用cvShowImage顯示一下:

cvShowImage("camera", frame);
咦,怎麼只顯示了一點點,大塊部分是灰的?很明顯,在上面發送時候,超出了一次發送的最大數據量。那麼,我只需把整個imageData分多次進行傳輸就OK:

#define BUFFSIZE 8*1024  //可以隨便設定,只要不超過最大限定就行
dataleft = 0;
while(dataleft < frame->imageSize) {
	if((i = write(skfd, frame->imageData + dataleft, BUFFSIZE)) == -1) {
		perror("Send imagedata Error:");
		exit(1);
	}
	dataleft += BUFFSIZE;
	if(i < BUFFSIZE) {
		printf("Send finished!!\n");
		break;
	}
}
接收方同理。這樣一來,攝像頭拍攝的一幀圖像就傳送完畢啦~

然後循環採集圖像,發送,顯示,這樣就可以算是視頻通信了。不過,問題又來了。。。運行一段時間後會發現,接收方視頻會出問題,應該是沒有同步信號,那就加上同步信號:

write(cnfd, sync, 2);
接受發同理。sync是一個兩個字節的字符型數組,隨便定義即可。

至此,相對比較穩健的攝像頭網絡回傳便完成了。目前只是單機調試,通過localhost來回路傳輸。接下來就是移植到樹莓派上,插上USB攝像頭,放在機器人上,連到同一個wifi裏,電腦上就能顯示機器人看到的畫面啦~~~


把完整的代碼貼上:

//server服務器端
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <string.h>

#include <cv.h>
#include <highgui.h>

#define BUFFSIZE 8*1024

int main(int argc, char *argv[]) {
	int skfd, cnfd, addr_len, i;
	long imagedataleft = 0;
	struct sockaddr_in srv_addr, clt_addr;
	int portnumber;
	char hello[] = "~~WELCOME~~\n";
	char *databuff = {0};
	int framerate = 0;
	char sync[2] = {0, 0};
	
	IplImage *frame = 0, header;
	
	if(2 != argc || 0 > (portnumber = atoi(argv[1]))) {
		printf("Usage:%s port\n", argv[0]);
		exit(1);
	}
	if(-1 == (skfd = socket(AF_INET, SOCK_STREAM, 0))) {
		perror("Socket Error:");
		exit(1);
	}
	bzero(&srv_addr, sizeof(struct sockaddr_in));
	srv_addr.sin_family = AF_INET;
	srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	srv_addr.sin_port = htons(portnumber);
	if(-1 == bind(skfd, (struct sockaddr *)(&srv_addr), sizeof(struct sockaddr))) {
		perror("Bind error:");
		exit(1);
	}
	//開啓監聽
	if(-1 == listen(skfd, 4)) {
		perror("Listen error:");
		exit(1);
	}

	//阻塞接受請求	
	addr_len = sizeof(struct sockaddr_in);
	if(-1 == (cnfd = accept(skfd, (struct sockaddr *)(&clt_addr), &addr_len))) {
		perror("Accept error:");
		exit(1);
	}
	printf("Connect from %s:%u ...!\n", inet_ntoa(clt_addr.sin_addr), ntohs(clt_addr.sin_port));
	//初次應答	
	if(-1 == write(cnfd, hello, strlen(hello))) {
		perror("Send error:");
		exit(1);
	}

	//接收index	
	if(-1 == read(cnfd, (char*)&header, sizeof(IplImage))) {
		perror("Read index Error:");
		exit(1);
	}
	//用收到的index參數創建一個新的圖像
	frame = cvCreateImageHeader(cvSize(header.width, header.height), header.depth, header.nChannels); 

	//創建imageData接收緩存
	databuff = (char*)malloc(frame->imageSize);
	//圖像數據直接指向接收緩存
	frame->imageData = databuff;	
	//創建窗口
	cvNamedWindow("camera-server", 1);
	
	while(1) {
		imagedataleft = 0;
		while(imagedataleft < frame->imageSize) {
			if((i = read(cnfd, databuff + imagedataleft, BUFFSIZE)) == -1) {
				perror("Read Data Error:");
				exit(1);
			}
			//同步數據流
			if(-1 == write(cnfd, sync, 2)) {
				perror("Sync Error:");
				exit(1);
			}
			imagedataleft += BUFFSIZE;
			if(i < BUFFSIZE) {
				printf("recv finished!!");
				break;
			}
		}

/*這裏主要是因爲IplImage結構體中的imageData指向了NULL*/
//	frame->imageData = &databuff;	
//	if(frame->imageData == NULL) {
//		printf("you think right!!");
//		exit(1);
//	}
		
		cvShowImage("camera-server", frame);
		framerate++;
		if(-1 == write(cnfd, "frame done", 10)) {
			perror("Ack Error:");
			exit(1);
		}
		if(27 == cvWaitKey(1)) break;
	}
	
	free(databuff);
	cvReleaseImage(&frame); 
	cvDestroyWindow("camera-server");
	
	close(cnfd);
	close(skfd);
	exit(0);
}
//cilent客戶端
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>

#include <cv.h>
#include <highgui.h>

#define BUFFSIZE 8*1024

int Cam_Init(CvCapture** Capture, IplImage** Frame) { //攝像頭初始化
	if(!(*Capture = cvCaptureFromCAM(0))) {
		perror("Can not open camera...\n");
		return -1;
	}
	*Frame = cvQueryFrame(*Capture);
	return 0;
}

int main(int argc, char *argv[]) {
	int skfd, i;
	char sync[2] = {0, 0};
	long <span style="font-family: Arial, Helvetica, sans-serif;">imagedataleft</span><span style="font-family: Arial, Helvetica, sans-serif;"> = 0;</span>
	struct sockaddr_in server_addr;
	struct hostent *host;
	int portnumber, nbytes;
	char databuff[1024] = {0}, *imageDataBuff = 0;

	CvCapture* capture = 0;   
	IplImage* frame = 0;
	
	if(3 != argc || 0 > (portnumber = atoi(argv[2]))) {
		printf("Usage:%s hostname portnumber \n");
		exit(1);
	}
	if(NULL == (host=gethostbyname(argv[1]))) {
		perror("Gethostname error:");
		exit(1);
	}
	if(-1 == (skfd=socket(AF_INET,SOCK_STREAM,0))) {
		perror("Socket Error:");
		exit(1);
	}
	
	bzero(&server_addr, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(portnumber);
	server_addr.sin_addr = *((struct in_addr *)host->h_addr);
	
	//請求鏈接
	if(-1 == connect(skfd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr))) {
		perror("Connect Error:");
		exit(1);
	}

	//接受應答
	if(-1 == read(skfd, databuff, 1024)) {
		perror("Recv Error:");
	}
	printf("Date arrived:%s", buf);

	//攝像頭初始化
	Cam_Init(&capture, &frame);	
	
//	printf("%s\n", frame->colorModel);
	//roi和下面一個結構體都爲NULL,因此不必顧慮
//	if(frame->roi !=NULL)
//		printf("%d,%d,%d,%d\n", frame->roi->xOffset, frame->roi->yOffset, frame->roi->width, frame->roi->height);

	//發送index	
	if(-1 == write(skfd, (char*)frame, sizeof(IplImage))) {
		perror("Send index Error:");
		exit(1);
	}

	cvNamedWindow("camera-cilent", 1);

	//發送圖片數據	
	while(1){
		frame = cvQueryFrame(capture);
		<span style="font-family: Arial, Helvetica, sans-serif;">imagedataleft</span> = 0;
		while(dataleft < frame->imageSize) {
			if((i = write(skfd, frame->imageData + dataleft, BUFFSIZE)) == -1) {
				perror("Send imagedata Error:");
				exit(1);
			}
			//同步數據流
			if(-1 == read(skfd, sync, 2)) {
				perror("Sync Error:");
				exit(1);
			}
			<span style="font-family: Arial, Helvetica, sans-serif;">imagedataleft</span> += BUFFSIZE;
			if(i < BUFFSIZE) {
				printf("Send finished!!\n");
				break;
			}
		}
		if(-1 == read(skfd, databuff, 10)) {
			perror("Ack Error");
			exit(1);
		}
		if(!strcmp(buf, "frame done")) {
			perror("Frame Error:");
			exit(1);
		}
		cvShowImage("camera-cilent", frame);
		cvWaitKey(30);  //控制幀率
	}

	cvReleaseImage(&frame);
	cvReleaseCapture(&capture);
	cvDestroyWindow("camera-cilent");
	close(skfd);
	exit(0);
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是C++ Opencv结合socket进行视频传输的示例代码,仅供参考: 发送端代码: ```c++ #include <iostream> #include <opencv2/opencv.hpp> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> using namespace std; using namespace cv; int main() { // 建立socket连接 int sockfd = socket(AF_INET, SOCK_STREAM, 0); sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); servaddr.sin_port = htons(8000); connect(sockfd, (sockaddr*)&servaddr, sizeof(servaddr)); // 读取本地视频文件 VideoCapture cap("test.mp4"); if (!cap.isOpened()) { cout << "Failed to open video file!" << endl; return -1; } while (true) { // 读取视频帧数据 Mat frame; cap >> frame; if (frame.empty()) { break; } // 将视频帧数据转换为字节流数据 vector<uchar> buf; imencode(".jpg", frame, buf); int length = buf.size(); // 发送字节流数据 send(sockfd, (char*)&length, sizeof(length), 0); send(sockfd, (char*)&buf[0], length, 0); } // 关闭连接 cap.release(); close(sockfd); return 0; } ``` 接收端代码: ```c++ #include <iostream> #include <opencv2/opencv.hpp> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> using namespace std; using namespace cv; int main() { // 建立socket连接 int listenfd = socket(AF_INET, SOCK_STREAM, 0); sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(8000); bind(listenfd, (sockaddr*)&servaddr, sizeof(servaddr)); listen(listenfd, 1); // 等待客户端连接 cout << "waiting for connection..." << endl; int connfd = accept(listenfd, (sockaddr*)NULL, NULL); cout << "connected!" << endl; // 创建Opencv窗口 namedWindow("video", WINDOW_NORMAL); while (true) { // 接收字节流数据 int length; if (recv(connfd, (char*)&length, sizeof(length), 0) <= 0) { break; } vector<uchar> buf(length); if (recv(connfd, (char*)&buf[0], length, 0) <= 0) { break; } // 将接收到的字节流数据转换为视频帧数据 Mat frame = imdecode(buf, IMREAD_COLOR); // 显示视频帧数据 imshow("video", frame); if (waitKey(1) == 'q') { break; } } // 关闭连接 destroyAllWindows(); close(connfd); close(listenfd); return 0; } ``` 注意:以上代码仅为示例代码,需要根据自己的实际需求进行修改和优化。同时,需要注意视频帧数据的大小和帧率对于网络传输的影响。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值