基于Socket和OpenCV的实时视频传输(On Linux)

本文介绍如何在Linux环境下使用Socket和OpenCV实现图像的实时采集、传输与显示。通过具体的代码示例,详细展示了客户端与服务器端的实现过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上一篇介绍了在Windows上实现基于Socket和openCV的实时视频传输,这一篇将继续讲解在Linux上的实现。


环境:

Server: Ubuntu 14.04 LTS + OpenCV2.4.10 

Client:: Ubuntu 14.04 LTS + OpenCV2.4.10 


我采用的仍是TCP协议的通信,Linux上的实现和Windows大同小异

Linux中OpenCV的编译安装可以参考 http://blog.youkuaiyun.com/pengz0807/article/details/49915573

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、关闭监听; 


我把图像的发送和接收分别封装在了两个类中:

采集与发送:

SocketMatTransmissionClient.h

/*M///////////////////////////////////////////////////////////////////////////////////////
//
//  基于OpenCV和Socket的图像传输(发送)
//	
//	By 彭曾 , at CUST, 2016.08.07
//
//	website: www.pengz0807.com  email: pengz0807@163.com 
//	
//M*/

#ifndef __SOCKETMATTRANSMISSIONCLIENT_H__
#define __SOCKETMATTRANSMISSIONCLIENT_H__

#include "opencv2/opencv.hpp"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

using namespace cv;

//待传输图像默认大小为 640*480,可修改
#define IMG_WIDTH 640	// 需传输图像的宽
#define IMG_HEIGHT 480	// 需传输图像的高
#define PACKAGE_NUM 2
//默认格式为CV_8UC3
#define BUFFER_SIZE IMG_WIDTH*IMG_HEIGHT*3/PACKAGE_NUM

struct sentbuf
{
	char buf[BUFFER_SIZE];
	int flag;
};

class SocketMatTransmissionClient
{
public:
	SocketMatTransmissionClient(void);
	~SocketMatTransmissionClient(void);

private:
	int 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


SocketMatTransmissionClient.cpp

/*M///////////////////////////////////////////////////////////////////////////////////////
//
//  基于OpenCV和Socket的图像传输(发送)
//	
//	By 彭曾 , at CUST, 2016.08.07 
//
//	website: www.pengz0807.com  email: pengz0807@163.com 
//	
//M*/


#include "SocketMatTransmissionClient.h"

SocketMatTransmissionClient::SocketMatTransmissionClient(void)
{
}


SocketMatTransmissionClient::~SocketMatTransmissionClient(void)
{
}


int SocketMatTransmissionClient::socketConnect(const char* IP, int PORT)
{
	struct sockaddr_in    servaddr;

	if ((sockClient = socket(AF_INET, SOCK_STREAM, 0)) < 0) 
	{
		printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
		return -1;
	}

	memset(&servaddr, 0, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(PORT);
	if (inet_pton(AF_INET, IP, &servaddr.sin_addr) <= 0) 
	{
		printf("inet_pton error for %s\n", IP);
		return -1;
	}

	if (connect(sockClient, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) 
	{
		printf("connect error: %s(errno: %d)\n", strerror(errno), errno);
		return -1;
	}
	else 
	{
		printf("connect successful!\n");
	}
}


void SocketMatTransmissionClient::socketDisconnect(void)
{
	close(sockClient);
}

int SocketMatTransmissionClient::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 < PACKAGE_NUM; k++) 
	{
		int num1 = IMG_HEIGHT / PACKAGE_NUM * k;
		for (int i = 0; i < IMG_HEIGHT / PACKAGE_NUM; 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 == PACKAGE_NUM - 1)
			data.flag = 2;
		else
			data.flag = 1;

		if (send(sockClient, (char *)(&data), sizeof(data), 0) < 0)
		{
			printf("send image error: %s(errno: %d)\n", strerror(errno), errno);
			return -1;
		}
	}
}


接收与显示:

SocketMatTransmissionServer.h

/*M///////////////////////////////////////////////////////////////////////////////////////
//
//  基于OpenCV和Socket的图像传输(接收)
//	
//	By 彭曾 , at CUST, 2016.08.07
//
//	website: www.pengz0807.com  email: pengz0807@163.com 
//	
//M*/

#ifndef __SOCKETMATTRANSMISSIONSEVER_H__
#define __SOCKETMATTRANSMISSIONSEVER_H__

#include "opencv2/opencv.hpp"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

using namespace cv;

#define PACKAGE_NUM 2

#define IMG_WIDTH 640
#define IMG_HEIGHT 480

#define BLOCKSIZE IMG_WIDTH*IMG_HEIGHT*3/PACKAGE_NUM

struct recvBuf
{
	char buf[BLOCKSIZE];
	int flag;
};


class SocketMatTransmissionServer
{
public:
	SocketMatTransmissionServer(void);
	~SocketMatTransmissionServer(void);
	int sockConn;
private:
	struct recvBuf data;

	int needRecv;
	int count;

public:

	// 打开socket连接
	// params :	PORT	传输端口
	// return : -1		连接失败
	//			1		连接成功
	int socketConnect(int PORT);


	// 传输图像
	// params : image	待接收图像
	//		image	待接收图像
	// return : -1		接收失败
	//			1		接收成功
	int receive(cv::Mat& image);


	// 断开socket连接
	void socketDisconnect(void);
};

#endif

SocketMatTransmissionServer.cpp

/*M///////////////////////////////////////////////////////////////////////////////////////
//
//  基于OpenCV和Socket的图像传输(接收)
//	
//	By 彭曾 , at CUST, 2016.08.07 
//
//	website: www.pengz0807.com  email: pengz0807@163.com 
//	
//M*/


#include "SocketMatTransmissionServer.h"

SocketMatTransmissionServer::SocketMatTransmissionServer(void)
{
}


SocketMatTransmissionServer::~SocketMatTransmissionServer(void)
{
}


int SocketMatTransmissionServer::socketConnect(int PORT)
{
	int server_sockfd = socket(AF_INET,SOCK_STREAM, 0);

	struct sockaddr_in server_sockaddr;
	server_sockaddr.sin_family = AF_INET;
	server_sockaddr.sin_port = htons(PORT);
	server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);

	if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1)
	{
		perror("bind");
		return -1;
	}

	if(listen(server_sockfd,5) == -1)
	{
		perror("listen");
		return -1;
	}

	struct sockaddr_in client_addr;
	socklen_t length = sizeof(client_addr);

	sockConn = accept(server_sockfd, (struct sockaddr*)&client_addr, &length);
	if(sockConn<0)
	{
		perror("connect");
		return -1;
	}
	else
	{
		printf("connect successful!\n");
		return 1;
	}
	
	close(server_sockfd);
}


void SocketMatTransmissionServer::socketDisconnect(void)
{
	close(sockConn);
}

int SocketMatTransmissionServer::receive(cv::Mat& image)
{
	int returnflag = 0;
	cv::Mat img(IMG_HEIGHT, IMG_WIDTH, CV_8UC3, cv::Scalar(0));
	needRecv = sizeof(recvBuf);
	count = 0;
	memset(&data,0,sizeof(data));

	for (int i = 0; i < PACKAGE_NUM; 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");
				break;
			}
			pos += len0;
		}

		count = count + data.flag;

		int num1 = IMG_HEIGHT / PACKAGE_NUM * i;
		for (int j = 0; j < IMG_HEIGHT / PACKAGE_NUM; 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 == PACKAGE_NUM + 1)
			{
				image = img;
				returnflag = 1;
				count = 0;
			}
			else
			{
				count = 0;
				i = 0;
			}
		}
	}
	if(returnflag == 1)
		return 1;
	else
		return -1;
}


示例代码:

图像的采集与发送:

SocketClientMat.cpp

#include "SocketMatTransmissionClient.h"

int main()
{
	SocketMatTransmissionClient socketMat;
	if (socketMat.socketConnect("127.0.0.1", 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;
}

接收与显示:

SocketServerMat.cpp

#include "SocketMatTransmissionServer.h"

int main()
{
	SocketMatTransmissionServer 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;
}
### 双摄像头存储空间不足问题分析 当尝试通过 OpenCV 或其他工具打开多个摄像头时,可能会遇到 `No space left on device` 的错误提示。这一错误通常并不是指物理磁盘上的存储空间不足,而是由于操作系统对硬件资源分配的限制所致。 #### 错误原因解析 该错误可能由以下几个因素引起: 1. **带宽限制**:USB 总线可能存在带宽瓶颈,尤其是在同时运行高分辨率或高速率视频流的情况下[^1]。 2. **驱动程序冲突**:某些情况下,Linux 系统中的 v4l2 驱动可能出现配置不当的情况,导致无法正常处理多路视频流[^2]。 3. **文件描述符耗尽**:如果系统中打开了过多的文件句柄或其他资源,则可能导致此错误发生[^3]。 --- ### 解决方案 以下是几种常见的解决方案: #### 1. 调整 USB 设备连接方式 将两个摄像头分别插到不同的 USB 控制器上(而非同一个控制器)。可以通过以下命令查看当前系统的 USB 连接情况并确认是否存在共享总线的问题: ```bash lsusb -t ``` 如果发现两台设备共用了同一根 USB 总线,可以考虑更换主板上的 USB 接口或者使用外置集线器来分流流量。 #### 2. 修改摄像机参数降低数据量 减少每一路视频的数据传输需求能够有效缓解带宽压力。具体做法包括但不限于调整帧速率 (FPS) 图像尺寸大小。例如,在 Python 中设置如下属性即可实现降级操作: ```python import cv2 cap0 = cv2.VideoCapture(0) cap1 = cv2.VideoCapture(1) # 设置分辨率为 VGA (640x480) cap0.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap0.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) cap1.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap1.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) # 减少帧数至合理范围比如 15 fps cap0.set(cv2.CAP_PROP_FPS, 15) cap1.set(cv2.CAP_PROP_FPS, 15) ``` #### 3. 增加系统可用缓冲区数量 有时增加内核允许的最大 socket 缓冲区大小也能帮助解决问题。编辑 `/etc/sysctl.conf` 文件加入下面几行内容后再重启机器生效: ```conf net.core.wmem_max=26214400 net.core.rmem_max=26214400 fs.file-max=100000 ``` 上述指令提高了网络栈内存上限以及全局可开启文件数目配额。 #### 4. 使用 GStreamer 替代传统 VideoCapture 方法 对于复杂场景下的多媒体采集任务来说,GStreamer 提供了更灵活强大的功能支持。它允许开发者精细控制整个媒体流水线行为模式从而规避潜在性能陷阱。一个简单的例子展示如何利用 gst-launch 工具读取来自不同源路径的画面素材: ```bash gst-launch-1.0 multifilesrc location="video%d.avi" index=0 ! decodebin ! autovideosink & gst-launch-1.0 v4l2src device=/dev/video0 ! videoconvert ! ximagesink & gst-launch-1.0 v4l2src device=/dev/video1 ! videoconvert ! ximagesink ``` --- ### 结论 综上所述,“No space left on device”的报错往往源于多种深层次的技术细节叠加影响所造成的结果。针对此类现象采取合理的优化措施可以从根源上去除障碍达成预期目标效果[^2]。
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值