1. 程式人生 > >【視訊傳輸】二、Opencv結合socket進行視訊傳輸(TCP協議)

【視訊傳輸】二、Opencv結合socket進行視訊傳輸(TCP協議)

        博文由來筆者突發奇想,做採集4USB攝像頭畫面小實驗時,卻遇到了在電腦上最多隻能同時開啟3個這樣頭痛的問題(個人分析認為是電腦的問題),故出此下策,在客戶端掛1個,伺服器掛3個攝像頭,利用socket進行視訊傳輸,本篇文章是利用的是TCP協議。筆者拙見,歡迎提出批評。

        說明:在此之前筆者也從未接觸過socket程式設計,也不懂TCP/IPUDP等基本知識。為了避免讓讀者感到繁瑣,我將運用小學及初中學習的總分總、承上啟下的寫作技巧進行講解,力圖做到詳略有致,言簡意賅,通俗易懂。如確實還有問題,可以直接回復部落格或圖文並茂的將問題郵件給筆者(),筆者知識有限,但儘量答覆。

        實驗平臺:VS2010 + opencv2.4.10(其他版本搭配也是一樣的做法)

        準備工作:

              1一臺電腦,能跑vsopencv,用來執行伺服器(server)和客戶端(client)程式碼;

              2、一個攝像頭(筆記本攝像頭、USB攝像頭等,網路攝像頭我還沒接觸過,暫不考慮)。

        在同一臺電腦上實驗,即執行伺服器程式,又跑客戶端程式,也就是說通過socket程式設計來實現資料的自發自收,這一步通過了接下來跑伺服器和客戶端分開的實驗就簡單了。兩臺PC之間的視訊傳輸見下一節:【二】Opencv結合socket進行視訊傳輸(TCP協議)

        那我們先在就開始吧,不用等下一曲了。

        實驗1簡單的資料傳輸,分三步。

              建立一個名為servervs空專案,再新建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;
}

               建立一個名為clientvs空專案,再新建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;
}

               同時開啟serverclient這兩個解決方案。先執行server;再執行client,你會發現二者建立起了通訊,並相互之間成功將資料進行了傳輸。

        到此我們已經學會了怎麼使用socket進行傳送接收資料了,這裡有兩個函式send()recv()。注意:127.0.0.1這個IP地址是回送地址(自發自收),每臺電腦的迴環地址都是這個IP,是固定的。很多時候我們組裝電腦時給電腦插網絡卡,就可以在cmd窗口裡面pingIP,若不通則說明網絡卡沒插好。

        實驗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、本部落格例程僅做學習交流用,切勿用於商業用途。