目前由于项目的需要,实现了基于Socket和OpenCV的实时视频传输。
由一台PC(Client客户端)采集摄像头图像后经Socket传输到另一台PC(Server服务器)再显示出来。
这一篇介绍在Windows上的实现,在下一篇讲解在Linux上的实现。
环境:
Server: Windows 10 + OpenCV2.4.10
Client:: Windows 10 + OpenCV2.4.10
我采用的是TCP协议的通信。
TCP协议通信的一般步骤是:
客户端:
1、创建一个socket,用函数socket(); 2、设置socket属性,用函数setsockopt();* 可选 3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选 4、设置要连接的对方的IP地址和端口等属性; 5、连接服务器,用函数connect(); 6、收发数据,用函数send()和recv(),或者read()和write(); 7、关闭网络连接;
服务器端:
1、创建一个socket,用函数socket(); 2、设置socket属性,用函数setsockopt(); * 可选 3、绑定IP地址、端口等信息到socket上,用函数bind(); 4、开启监听,用函数listen(); 5、接收客户端上来的连接,用函数accept(); 6、收发数据,用函数send()和recv(),或者read()和write(); 7、关闭网络连接; 8、关闭监听;
我把图像的发送和接收分别封装在了两个类中:
采集与发送:
WinsockMatTransmissionClient.h
/*M/// // // 基于OpenCV和Winsock的图像传输(发送) // // By 彭曾 , at CUST, 2016.08.06 // // website: www.pengz0807.com email: pengz0807@163.com // //M*/ #ifndef __WINSOCKMATTRANSMISSIONCLIENT_H__ #define __WINSOCKMATTRANSMISSIONCLIENT_H__ #include "opencv2/opencv.hpp" #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/core/core.hpp" #include <stdio.h> #include <Winsock2.h> #pragma comment(lib,"WS2_32.lib") //待传输图像默认大小为 640*480,可修改 #define IMG_WIDTH 640 // 需传输图像的宽 #define IMG_HEIGHT 480 // 需传输图像的高 //默认格式为CV_8UC3 #define BUFFER_SIZE IMG_WIDTH*IMG_HEIGHT*3/32 struct sentbuf { char buf[BUFFER_SIZE]; int flag; }; class WinsockMatTransmissionClient { public: WinsockMatTransmissionClient(void); ~WinsockMatTransmissionClient(void); private: SOCKET sockClient; struct sentbuf data; public: // 打开socket连接 // params : IP 服务器的ip地址 // PORT 传输端口 // return : -1 连接失败 // 1 连接成功 int socketConnect(const char* IP, int PORT); // 传输图像 // params : image 待传输图像 // return : -1 传输失败 // 1 传输成功 int transmit(cv::Mat image); // 断开socket连接 void socketDisconnect(void); }; #endif
WinsockMatTransmissionClient.cpp
/*M/// // // 基于OpenCV和Winsock的图像传输(发送) // // By 彭曾 , at CUST, 2016.08.06 // // website: www.pengz0807.com email: pengz0807@163.com // //M*/ #include "WinsockMatTransmissionClient.h" WinsockMatTransmissionClient::WinsockMatTransmissionClient(void) { } WinsockMatTransmissionClient::~WinsockMatTransmissionClient(void) { } int WinsockMatTransmissionClient::socketConnect(const char* IP, int PORT) { WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD( 1, 1 ); err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { return -1; } if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) { WSACleanup( ); return -1; } err = (sockClient = socket(AF_INET,SOCK_STREAM,0)); if (err < 0) { printf("create socket error: %s(errno: %d)\n\n", strerror(errno), errno); return -1; } else { printf("create socket successful!\nnow connect ...\n\n"); } SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr=inet_addr(IP); addrSrv.sin_family=AF_INET; addrSrv.sin_port=htons(PORT); err = connect(sockClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR)); if (err < 0) { printf("connect error: %s(errno: %d)\n\n", strerror(errno), errno); return -1; } else { printf("connect successful!\n\n"); return 1; } } void WinsockMatTransmissionClient::socketDisconnect(void) { closesocket(sockClient); WSACleanup(); } int WinsockMatTransmissionClient::transmit(cv::Mat image) { if (image.empty()) { printf("empty image\n\n"); return -1; } if(image.cols != IMG_WIDTH || image.rows != IMG_HEIGHT || image.type() != CV_8UC3) { printf("the image must satisfy : cols == IMG_WIDTH(%d) rows == IMG_HEIGHT(%d) type == CV_8UC3\n\n", IMG_WIDTH, IMG_HEIGHT); return -1; } for(int k = 0; k < 32; k++) { int num1 = IMG_HEIGHT / 32 * k; for (int i = 0; i < IMG_HEIGHT / 32; i++) { int num2 = i * IMG_WIDTH * 3; uchar* ucdata = image.ptr<uchar>(i + num1); for (int j = 0; j < IMG_WIDTH * 3; j++) { data.buf[num2 + j] = ucdata[j]; } } if(k == 31) data.flag = 2; else data.flag = 1; if (send(sockClient, (char *)(&data), sizeof(data), 0) < 0) { printf("send msg error: %s(errno: %d)\n", strerror(errno), errno); return -1; } } } 图像的接收与显示:WinsockMatTransmissionServer.h
/*M/// // // 基于OpenCV和Winsock的图像传输(接收) // // By 彭曾 , at CUST, 2016.08.06 // // website: www.pengz0807.com email: pengz0807@163.com // //M*/ #ifndef __WINSOCKMATTRANSMISSIONSEVER_H__ #define __WINSOCKMATTRANSMISSIONSEVER_H__ #include "opencv2/opencv.hpp" #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/core/core.hpp" #include <stdio.h> #include <Winsock2.h> #pragma comment(lib,"WS2_32.lib") //待传输图像默认大小为 640*480,可修改 #define IMG_WIDTH 640 // 需传输图像的宽 #define IMG_HEIGHT 480 // 需传输图像的高 //默认格式为CV_8UC3 #define BUFFER_SIZE IMG_WIDTH*IMG_HEIGHT*3/32 struct recvbuf { char buf[BUFFER_SIZE]; int flag; }; class WinsockMatTransmissionServer { public: WinsockMatTransmissionServer(void); ~WinsockMatTransmissionServer(void); private: SOCKET sockConn; struct recvbuf data; public: // 打开socket连接 // params : PORT 传输端口 // return : -1 连接失败 // 1 连接成功 int socketConnect(int PORT); // 传输图像 // params : image 待接收图像 // return : -1 接收失败 // 1 接收成功 int receive(cv::Mat& image); // 断开socket连接 void socketDisconnect(void); }; #endifWinsockMatTransmissionServer.cpp /*M/// // // 基于OpenCV和Winsock的图像传输(接收) // // By 彭曾 , at CUST, 2016.08.06 // // website: www.pengz0807.com email: pengz0807@163.com // //M*/ #include "WinsockMatTransmissionServer.h" WinsockMatTransmissionServer::WinsockMatTransmissionServer(void) { } WinsockMatTransmissionServer::~WinsockMatTransmissionServer(void) { } int WinsockMatTransmissionServer::socketConnect(int PORT) { WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD(1, 1); err = WSAStartup(wVersionRequested, &wsaData); if (err != 0) { return -1; } if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) { WSACleanup(); return -1; } SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0); SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY); addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(PORT); bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); listen(sockSrv, 5); SOCKADDR_IN addrClient; int len = sizeof(SOCKADDR); sockConn = accept(sockSrv, (SOCKADDR*)&addrClient, &len); int nRecvBuf = 1024 * 1024 * 10; setsockopt(sockConn, SOL_SOCKET, SO_RCVBUF, (const char*)&nRecvBuf, sizeof(int)); } void WinsockMatTransmissionServer::socketDisconnect(void) { closesocket(sockConn); } int WinsockMatTransmissionServer::receive(cv::Mat& image) { cv::Mat img(IMG_HEIGHT,IMG_WIDTH,CV_8UC3,cv::Scalar(0)); int needRecv = sizeof(recvbuf); int count = 0; while (1) { for (int i = 0; i < 32; i++) { int pos = 0; int len0 = 0; while (pos < needRecv) { len0 = recv(sockConn, (char*)(&data) + pos, needRecv - pos, 0); if (len0 < 0) { printf("Server Recieve Data Failed!\n"); return -1; } pos += len0; } count = count + data.flag; int num1 = IMG_HEIGHT / 32 * i; for (int j = 0; j < IMG_HEIGHT / 32; j++) { int num2 = j * IMG_WIDTH * 3; uchar* ucdata = img.ptr<uchar>(j + num1); for (int k = 0; k < IMG_WIDTH * 3; k++) { ucdata[k] = data.buf[num2 + k]; } } if (data.flag == 2) { if (count == 33) { image = img; return 1; count = 0; } else { count = 0; i = 0; } } } } } 示例代码:
采集与发送:
SocketClientMat.cpp
/*M/// // // 基于OpenCV和Winsock的图像传输(发送) // // By 彭曾 , at CUST, 2016.08.06 // // website: www.pengz0807.com email: pengz0807@163.com // //M*/ #include "WinsockMatTransmissionClient.h" int main() { WinsockMatTransmissionClient socketMat; if (socketMat.socketConnect("192.168.1.101", 6666) < 0) { return 0; } cv::VideoCapture capture(0); cv::Mat image; while (1) { if (!capture.isOpened()) return 0; capture >> image; if (image.empty()) return 0; socketMat.transmit(image); } socketMat.socketDisconnect(); return 0; } 接收与显示:WinsockServerMat.cpp
/*M/// // // 基于OpenCV和Winsock的图像传输(接收) // // By 彭曾 , at CUST, 2016.08.06 // // website: www.pengz0807.com email: pengz0807@163.com // //M*/ #include "WinsockMatTransmissionServer.h" int main() { WinsockMatTransmissionServer socketMat; if (socketMat.socketConnect(6666) < 0) { return 0; } cv::Mat image; while (1) { if(socketMat.receive(image) > 0) { cv::imshow("",image); cv::waitKey(30); } } socketMat.socketDisconnect(); return 0; } 将在下一篇文章中讲解Linux中基于Socket和OpenCV的实时视频传输的实现。