這是半年前做機器人項目時候想到的。當時的整個設想是這樣:以樹莓派爲主控的機器人用攝像頭獲取圖像,然後傳到我的電腦上。因爲想進行機器人的物體識別,所以把攝像頭採集到的圖像傳上來比較直觀,也便於操作。但最後整個項目還是以失敗告終,畢竟自己的水平不夠。。。不久前無意看到了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);
}