【視訊傳輸】二、Opencv結合socket進行視訊傳輸(TCP協議)
博文由來:筆者突發奇想,做採集4個USB攝像頭畫面小實驗時,卻遇到了在電腦上最多隻能同時開啟3個這樣頭痛的問題(個人分析認為是電腦的問題),故出此下策,在客戶端掛1個,伺服器掛3個攝像頭,利用socket進行視訊傳輸,本篇文章是利用的是TCP協議。筆者拙見,歡迎提出批評。
說明:在此之前筆者也從未接觸過socket程式設計,也不懂TCP/IP、UDP等基本知識。為了避免讓讀者感到繁瑣,我將運用小學及初中學習的總分總、承上啟下的寫作技巧進行講解,力圖做到詳略有致,言簡意賅,通俗易懂。如確實還有問題,可以直接回復部落格或圖文並茂的將問題郵件給筆者(),筆者知識有限,但儘量答覆。
實驗平臺:VS2010 + opencv2.4.10(其他版本搭配也是一樣的做法)
準備工作:
1、一臺電腦,能跑vs和opencv,用來執行伺服器(server)和客戶端(client)程式碼;
2、一個攝像頭(筆記本攝像頭、USB攝像頭等,網路攝像頭我還沒接觸過,暫不考慮)。
在同一臺電腦上實驗,即執行伺服器程式,又跑客戶端程式,也就是說通過socket程式設計來實現資料的自發自收,這一步通過了接下來跑伺服器和客戶端分開的實驗就簡單了。兩臺PC之間的視訊傳輸見下一節:【二】Opencv結合socket進行視訊傳輸(TCP協議)
那我們先在就開始吧,不用等下一曲了。
實驗1、簡單的資料傳輸,分三步。
①建立一個名為server的vs空專案,再新建demo.cpp原始檔,將下面的程式碼拷貝到demo.cpp中,編譯好後關閉vs。
#include "stdafx.h" #include <stdio.h> #include <winsock2.h> #pragma comment(lib,"ws2_32.lib") int main(void) { //初始化WSA WORD sockVersion = MAKEWORD(2,2); WSADATA wsaData; if(WSAStartup(sockVersion, &wsaData)!=0) { return 0; } //建立套接字 SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(slisten == INVALID_SOCKET) { printf("socket error !"); return 0; } //繫結IP和埠 sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(8888); sin.sin_addr.S_un.S_addr = INADDR_ANY; if(bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR) { printf("bind error !"); } //開始監聽 if(listen(slisten, 5) == SOCKET_ERROR) { printf("listen error !"); return 0; } //迴圈接收資料 SOCKET sClient; sockaddr_in remoteAddr; int nAddrlen = sizeof(remoteAddr); char revData[255]; while (true) { printf("等待連線...\n"); sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen); if(sClient == INVALID_SOCKET) { printf("accept error !"); continue; } printf("接受到一個連線:%s \r\n", inet_ntoa(remoteAddr.sin_addr)); //接收資料 int ret = recv(sClient, revData, 255, 0); if(ret > 0) { revData[ret] = 0x00; printf(revData); } //傳送資料 char * sendData = "你好,TCP客戶端!\n"; send(sClient, sendData, strlen(sendData), 0); closesocket(sClient); } closesocket(slisten); WSACleanup(); return 0; }
②建立一個名為client的vs空專案,再新建demo.cpp原始檔,將下面的程式碼拷貝到demo.cpp中,編譯好後關閉vs。
#include "stdafx.h"
#include <WINSOCK2.H>
#include <STDIO.H>
#pragma comment(lib,"ws2_32.lib")
int main(int argc, char* argv[])
{
WORD sockVersion = MAKEWORD(2,2);
WSADATA data;
if(WSAStartup(sockVersion, &data) != 0)
{
return 0;
}
SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sclient == INVALID_SOCKET)
{
printf("invalid socket !");
return 0;
}
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(8888);
serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
{
printf("connect error !");
closesocket(sclient);
return 0;
}
char * sendData = "你好,TCP服務端,我是客戶端!\n";
send(sclient, sendData, strlen(sendData), 0);
char recData[255];
int ret = recv(sclient, recData, 255, 0);
if(ret > 0)
{
recData[ret] = 0x00;
printf(recData);
}
closesocket(sclient);
WSACleanup();
return 0;
}
③同時開啟server和client這兩個解決方案。先執行server;再執行client,你會發現二者建立起了通訊,並相互之間成功將資料進行了傳輸。
到此我們已經學會了怎麼使用socket進行傳送接收資料了,這裡有兩個函式send()和recv()。注意:127.0.0.1這個IP地址是回送地址(自發自收),每臺電腦的迴環地址都是這個IP,是固定的。很多時候我們組裝電腦時給電腦插網絡卡,就可以在cmd窗口裡面ping此IP,若不通則說明網絡卡沒插好。
實驗2、簡單的圖片傳輸,分三步。
接下來我們就在實驗1的基礎上新增入opencv程式碼,讀取圖片,為儘量簡單,我們將影象灰度化。設定好opencv的include和lib路徑及依賴項後,就可以開始動手敲程式碼了。
①先看client端,思路是將讀出的影象資料放到一個數組裡面,再用send()傳送出去,程式碼如下:
#include "stdafx.h"
#include <WINSOCK2.H>
#include <iostream>
#include <stdio.h>
#include <cv.h>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#pragma comment(lib,"ws2_32.lib")
int main(int argc, char* argv[])
{
WORD sockVersion = MAKEWORD(2,2);
WSADATA data;
if(WSAStartup(sockVersion, &data) != 0)
{
return 0;
}
SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sclient == INVALID_SOCKET)
{
printf("invalid socket !\n");
return 0;
}
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(8888);
serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
{
printf("connect error !\n");
closesocket(sclient);
return 0;
}
//讀取影象併發送
IplImage *image_src = cvLoadImage("wall.jpg");//載入圖片
IplImage *image_dst = cvCreateImage(cvSize(640, 480), 8, 1);//放置灰度影象
int i, j;
char sendData[1000000] = "";
cvNamedWindow("client", 1);
cvCvtColor(image_src, image_dst, CV_RGB2GRAY);
for(i = 0; i < image_dst->height; i++)
{
for (j = 0; j < image_dst->width; j++)
{
sendData[image_dst->width * i + j] = ((char *)(image_dst->imageData + i * image_dst->widthStep))[j];
}
}
cvShowImage("client", image_dst);
cvWaitKey(0);//任意鍵傳送
send(sclient, sendData, 1000000, 0);//傳送
cvDestroyWindow("client");
closesocket(sclient);
WSACleanup();
return 0;
}
②server端,思路是將recv()接收到的資料放到一個數組裡面,再還原為影象資料,程式碼如下:
#include "stdafx.h"
#include <stdio.h>
#include <winsock2.h>
#include <cv.h>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#pragma comment(lib,"ws2_32.lib")
int main(int argc, char* argv[])
{
//初始化WSA
WORD sockVersion = MAKEWORD(2,2);
WSADATA wsaData;
if(WSAStartup(sockVersion, &wsaData)!=0)
{
return 0;
}
//建立套接字
SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(slisten == INVALID_SOCKET)
{
printf("socket error !");
return 0;
}
//繫結IP和埠
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(8888);//埠8888
sin.sin_addr.S_un.S_addr = INADDR_ANY;
if(bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
{
printf("bind error !");
}
//開始監聽
if(listen(slisten, 5) == SOCKET_ERROR)
{
printf("listen error !");
return 0;
}
//迴圈接收資料
SOCKET sClient;
sockaddr_in remoteAddr;
int nAddrlen = sizeof(remoteAddr);
printf("等待連線...\n");
do
{
sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
}while(sClient == INVALID_SOCKET);
printf("接受到一個連線:%s \r\n", inet_ntoa(remoteAddr.sin_addr));
char revData[1000000] = "";
IplImage *image_src = cvCreateImage(cvSize(640, 480), 8, 1);//接收灰度影象
int i, j;
int ret;
cvNamedWindow("server", 1);
while(true)
{
//接收資料
ret = recv(sClient, revData, 1000000, 0);
if(ret > 0)
{
revData[ret] = 0x00;
for(i = 0; i < image_src->height; i++)
{
for (j = 0; j < image_src->width; j++)
{
((char *)(image_src->imageData + i * image_src->widthStep))[j] = revData[image_src->width * i + j];
}
}
ret = 0;
}
cvShowImage("server", image_src);
cvWaitKey(1);
}
cvDestroyWindow("server");
closesocket(slisten);
WSACleanup();
return 0;
}
③ 先執行server,再執行client。在client端,按任意鍵即可傳送資料,而server端馬上會收到資料並還原為影象顯示出來。
經過上述的實驗及講解,可以對使用socket的程式設計有一定的瞭解。如果能夠啟發讀者想去探索,並開發些好玩的東西出來,那自然是一件幸事,但這裡就不深入了。
特別注意:
1、本部落格例程僅做學習交流用,切勿用於商業用途。