C++ 摄像头拍照并传输图片 opencv socket

本文详细介绍了如何使用Socket编程实现图像帧的传输,包括客户端与服务器端的代码实现,以及使用OpenCV进行图像捕获的方法。

关于socket编程

socket原理示例文章1
socket原理示例文章2
setsockopt参数简介
opencv4.2.0快速下载链接

还不清楚原理的朋友,建议结合两篇文章一起观看
关于socket编程其它相关函数,例如setsockopt、WSAGetLastError等函数,需要找专门的博客查看。

环境

  1. windows 10
  2. vs ide 2017
  3. opencv 4.2.0

功能描述

  1. camera隔一定时间截取一个图像帧,以当前时间为图片名,保存到相关文件下
  2. 调用client,先传图片名,再传图片
  3. server一直运行于服务端,等待接收图片
  4. 每次传输完成,都将断开连接

server.cpp

#include "Winsock.h"
#include "stdio.h"
#include<stdlib.h>
#include<iostream>
#include<thread>
#pragma   comment(lib, "wsock32.lib")	

#define RECV_PORT 2494  //the 'RECV_PORT' should be the same as the variable in the sockclient.cpp
SOCKET sockserver;
struct sockaddr_in ServerAddr;
SOCKET sockclient;
struct sockaddr_in ClientAddr;

using namespace std;

void TCPRecv()
{
#define BuffLength 800*1024	
#define filename_length 100
#define predix "D:\\vs_source\\repos\\server\\store\\"
	FILE* p = NULL;
	int recv_len = 0;
	char filename[filename_length] = {};
	char rbuff[BuffLength] = {};
	for (int number = 1;; number++)
	{
		memset(rbuff, 0, BuffLength);
		if (1 == number)
		{
			recv_len = recv(sockclient, filename, filename_length, 0);
			if (recv_len <= 0)
			{
				closesocket(sockclient);
				cout << "filename error" << endl;
				break;
			}
			string str1 = predix + string(filename);
			fopen_s(&p, str1.c_str(), "wb");
		}
		else
		{
			recv_len = recv(sockclient, rbuff, BuffLength, 0);
			if (recv_len <= 0)
			{
				closesocket(sockclient);
				cout << "connection finish" << endl;
				fclose(p);
				break;
			}
			fwrite(rbuff, recv_len, 1, p);
			fflush(p);
		}
	}
	return;
}

//initialize winsock  
DWORD StartSock()     //load winsock
{
	WSADATA WSAData;
	//BOOL bReuseaddr = TRUE;
	//BOOL bDontLinger = FALSE;
	//setsockopt(sockclient, SOL_SOCKET, SO_DONTLINGER, (const char*)&bDontLinger, sizeof(BOOL));
	//setsockopt(sockclient, SOL_SOCKET, SO_REUSEADDR, (const char*)&bReuseaddr, sizeof(BOOL));
	if (WSAStartup(MAKEWORD(2, 2), &WSAData) != 0)	//WSADATA: which store information about socket
	{
		cout << "socket init fail!" << endl;
		return  -1;
	}
	sockserver = socket(AF_INET, SOCK_STREAM, 0); 	//structure SOCKET,load winsock,SOCK_STREAM stand for TCP protocol
	if (sockserver == SOCKET_ERROR)
	{
		printf("sockserver create fail ! \n");
		WSACleanup();				//release winsock
		return -1;
	}
	ServerAddr.sin_family = AF_INET;		// sockaddr_in is one kind of structure,serveraddr is the same as sockaddr_in
	ServerAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	ServerAddr.sin_port = htons(RECV_PORT);
	if (SOCKET_ERROR == ::bind(sockserver, (struct  sockaddr  FAR*) & ServerAddr, sizeof(ServerAddr)))
	{                     //bind socket with addr
		printf("bind is the error");
		return -1;
	}
	if (listen(sockserver, SOMAXCONN) < 0)
	{
		printf("Listen error");
		return -1;
	}
	int Addrlen = sizeof(ClientAddr);
	//the accept won't return until the successfully monitor of clientsocket
	/*
	the current time photo transmission can not be triggered until the end of 'accept' function
	*/
	for (int i = 1;; i++)
	{
		sockclient = accept(sockserver, (struct sockaddr FAR*) & ClientAddr, &Addrlen);
		cout << "connection: " << i << endl;
		thread T(TCPRecv);
		T.join();
	}
}

void main()
{
	if (StartSock() == -1)
		return;
	system("pause");
}
关于StarSock()

使用socket需要加载winsock库

	if (WSAStartup(MAKEWORD(2, 2), &WSAData) != 0)	//WSADATA: which store information about socket
	{
		cout << "socket init fail!" << endl;
		return  -1;
	}

通过socket()获取套接字,失败则WSACleanup,退出程序

	sockserver = socket(AF_INET, SOCK_STREAM, 0); 	//structure SOCKET,load winsock,SOCK_STREAM stand for TCP protocol
	if (sockserver == SOCKET_ERROR)
	{
		printf("sockserver create fail ! \n");
		WSACleanup();				//release winsock
		return -1;
	}

设置sockserver的参数,AF_INET指IPV4,INADDR_ANY指任何地址,RECV_PORT指绑定的端口。

	ServerAddr.sin_family = AF_INET;		// sockaddr_in is one kind of structure,serveraddr is the same as sockaddr_in
	ServerAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	ServerAddr.sin_port = htons(RECV_PORT);
	if (SOCKET_ERROR == ::bind(sockserver, (struct  sockaddr  FAR*) & ServerAddr, sizeof(ServerAddr)))
	{                     //bind socket with addr
		printf("bind is the error");
		return -1;
	}

绑定端口后,开始监听。SOMAXCONN代表最大数目,对于单对单通信没有影响。

	if (listen(sockserver, SOMAXCONN) < 0)
	{
		printf("Listen error");
		return -1;
	}

初始化后,for循环传输图片

	int Addrlen = sizeof(ClientAddr);
	//the accept won't return until the successfully monitor of clientsocket
	/*
	the current time photo transmission can not be triggered until the end of 'accept' function
	*/
	for (int i = 1;; i++)
	{
		sockclient = accept(sockserver, (struct sockaddr FAR*) & ClientAddr, &Addrlen);
		cout << "connection: " << i << endl;
		thread T(TCPRecv);
		T.join();
	}
关于TCPRecv()

define参数,方便设置。

#define BuffLength 800*1024
#define filename_length 100
#define predix "C:\\Users\\DELL\\source\\repos\\server\\store\\"

第一次recv文件名,并创建文件。之后则传输文件

		if (1 == number)
		{
			recv_len = recv(sockclient, filename, filename_length, 0);
			if (recv_len <= 0)
			{
				closesocket(sockclient);
				cout << "filename error" << endl;
				break;
			}
			string str1 = predix + string(filename);
			fopen_s(&p, str1.c_str(), "wb");
		}
		else
		{
			recv_len = recv(sockclient, rbuff, BuffLength, 0);
			if (recv_len <= 0)
			{
				closesocket(sockclient);
				cout << "connection finish" << endl;
				fclose(p);
				break;
			}
			fwrite(rbuff, recv_len, 1, p);
			fflush(p);
		}

camera.cpp

#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui_c.h>
#include <time.h>
#include <Windows.h>
#include <iostream>
#include "sockclient.h"

using namespace std;
using namespace cv;

//temp file of the same as .SLN in the temp folder of the project directory
string writePath = "../store/";


void camera()
{
	VideoCapture capture(0);		//0 stand for original camera, but external uses 1, 2, 3, ……
	time_t times;
	struct tm nowtime;
	namedWindow("hello", CV_WINDOW_AUTOSIZE);
	char name[64];
	while (1) 
	{
		Sleep(1000);		//in milliseconds
		Mat frame;
		capture >> frame;
		time(&times);
		localtime_s(&nowtime, &times);
		if (nowtime.tm_sec % 5 == 0) 
		{
			strftime(name, sizeof(name) - 1, "%Y-%m-%d-%H-%M-%S", &nowtime);
			imwrite(writePath + name + ".jpg", frame);
			//Sleep(2000);
			string str = name + string(".jpg");
			char* filename = (char*)str.data();
			client(filename);
		}
		imshow("hello", frame);
		waitKey(10);
	}
}

int main(int argc, char** argv) {
	camera();
}
camera()

使用了opencv库,感兴趣的朋友需要先下载并配置opencv。
快速下载链接:opencv4.2.0
在程序中设定了5秒拍一次,可以自行设置

		if (nowtime.tm_sec % 5 == 0) 

程序在局域网上的两台主机上测试,如果传输图片不全,可能是网络问题,可以通过Sleep()给予server端足够时间。若无法解决,则可通过WSASetLastError()、WSAGetLastError()两个函数获取错误信息,具体请search。

			//Sleep(2000);

在opencv库中,以下两个函数是配套使用的
waitKey的作用是刷新图像帧,否则imshow的窗口无法显示,但可以正常传输

		imshow("hello", frame);
		waitKey(10);
sockclient.cpp()
#include "Winsock.h"
#include "stdio.h" 
#include <iostream>
#include<thread>
#pragma comment(lib, "wsock32.lib")

#define RECV_PORT 2494		//set-up server port
#define ServerIPAddr 16		//one more than what you see
#define predix "F:\\vs_repos\\GetCameraImageFrame\\store\\"

SOCKET Server;
sockaddr_in ServerAddr;
char server_IP[ServerIPAddr] = "192.168.1.4";     //server_IP		
//char server_IP[ServerIPAddr] = "127.0.0.1"; 

using namespace std;

DWORD StartSock()			//start-up socket project 
{
	WSADATA WSAData;
	if (WSAStartup(MAKEWORD(2, 2), &WSAData) != 0)	// load  winsock2.0
		return -1;
	return 1;
}

//create socket
DWORD CreateSocket()
{	//the socket function will return a socket object;	SOCK_STREAM realted TCP
	Server = socket(AF_INET, SOCK_STREAM, 0);
	if (Server == SOCKET_ERROR)
	{
		WSACleanup();
		return -1;
	}
	return 1;
}

DWORD CallServer() //request connection
{
	//set address structure
	ServerAddr.sin_family = AF_INET;		//Ipv4
	ServerAddr.sin_addr.s_addr = inet_addr(server_IP);
	ServerAddr.sin_port = htons(RECV_PORT);
	if (connect(Server, (struct  sockaddr*)&ServerAddr, sizeof(ServerAddr)) == SOCKET_ERROR)
	{
		WSACleanup();
		closesocket(Server);
		return -1;
	}
	return 1;
}

int SendFile(char* filename)
{
#define BuffLen 800*1024
	FILE * pFile;
	char sbuff[BuffLen] = {};
	string str1 = predix + string(filename);
	fopen_s(&pFile, str1.c_str(), "rb+");
	int DataLength;
	if (pFile)
	{
		int j = send(Server, filename, strlen(filename) + 1, 1);
		Sleep(100);
		while (true)
		{
			memset(sbuff, 0, BuffLen);
			DataLength = fread(sbuff, 1, BuffLen, pFile);
			cout << DataLength << endl;
			if (DataLength < BuffLen)	//based on the principle: logical data can be storaged continuously
			{
				int i = send(Server, sbuff, DataLength + 1, 1);
				break;
			}
			else
			{
				send(Server, sbuff, DataLength, 1);
			}
		}
		return 1;
	}
	else
	{
		WSACleanup();
		closesocket(Server);
		return -1;
	}
}

int client(char* filename)		//the function will send the photo file in the way of data stream
{
	if (-1 == StartSock())
		return -1;
	if (-1 == CreateSocket())
		return -1;
	if (-1 == CallServer())
		return -1;
	if (-1 == SendFile(filename))
		return -1;
	Sleep(1000);
	WSACleanup();
	closesocket(Server);
}

初始化过程与server相似,client需要指定server的ip地址与端口

	ServerAddr.sin_family = AF_INET;		//Ipv4
	ServerAddr.sin_addr.s_addr = inet_addr(server_IP);
	ServerAddr.sin_port = htons(RECV_PORT);
	if (connect(Server, (struct  sockaddr*)&ServerAddr, sizeof(ServerAddr)) == SOCKET_ERROR)
	{
		WSACleanup();
		closesocket(Server);
		return -1;
	}

第一个是局域网ip,第二个为本机测试

char server_IP[ServerIPAddr] = "192.168.1.4";     //server_IP		
//char server_IP[ServerIPAddr] = "127.0.0.1"; 
SendFile()

逻辑与server端的逻辑对应,传输文件名时需间隔一定时间,再传输文件,防止两次传输间隔太短,server端将文件名与文件内容一起recv

		int j = send(Server, filename, strlen(filename) + 1, 1);
		Sleep(100);

因为send函数实质是将数据搬到本机的缓冲区,传输的过程是由系统完成的。
因此传输完成后,需要等待一定时间,防止传输不全。
需要释放winsock库,关闭sock client。才能进行下一次传输,否则server端可能还没断开本次连接,从而之后的连接一直呆在listen的队列中。

	Sleep(1000);
	WSACleanup();
	closesocket(Server);

未解决问题

本次学习过程遇到一些问题如下

  1. 无法查看或者打开pdb文件,本次编写程序较为简单,该问题暂未对程序运行造成影响,试过网上许多解决方案,均以失败告终,不了了之。
    在这里插入图片描述
  2. 本机传输大小与缓冲的大小有关
    但BuffLen无法设置过大,否则会出现栈溢出的问题。
    另外,为何本机传输文件大小会与此有关?
    博主在局域网测试时,文件大小只会影响传输时间,没有出现该问题。
#define BuffLen 800*1024
  1. recv与server的传输长度问题,即第三个参数
send(Server, sbuff, DataLength + 1, 1);

本机模式运行正确,在局域网测试有字节数错误;
在局域网调好后,本机则错误了。

参考

https://blog.csdn.net/sunxiaolei/article/details/8674401
https://blog.csdn.net/qq_27923041/article/details/83857964
https://www.cnblogs.com/fuchongjundream/p/3914696.html

敬告:该系列的课程在抓紧录制更新中,敬请大家关注。敬告:本课程项目仅供学习参考,请不要直接商用,概不负责任何法律责任。 该系列的课程涉及:FFmpeg,WebRTC,SRS,Nginx,Darwin,Live555,等。包括:音视频、流媒体、直播、Android、视频监控28181、等。 我将带领大家一起来学习使用FFmpeg开发视频监控项目,动手操练。具体内容包括: 一、视频监控的架构和流程二、FFmpeg4.3+SDL2+Qt5开发环境的搭建三、FFmpeg的SDK编程回顾总结操练四、SDL2.0的编程回顾总结操练五、颜色空间转换RGB和YUV的原理与实战六、Qt5+FFmpeg本地摄像头采集预览实战七、代码封装:摄像头h264/5编码存储八、Qt5+FFmpeg单路网络摄像头采集预览九、Qt5+FFmpeg单路网络摄像头采集预览录制会看十、onvif与GB/T-28181的简介  音视频与流媒体是一门很复杂的技术,涉及的概念、原理、理论非常多,很多初学者不学 基础理论,而是直接做项目,往往会看到c/c++的代码时一头雾水,不知道代码到底是什么意思,这是为什么呢?   因为没有学习音视频和流媒体的基础理论,就比如学习英语,不学习基本单词,而是天天听英语新闻,总也听不懂。 所以呢,一定要认真学习基础理论,然后再学习播放器、转码器、非编、流媒体直播、视频监控、等等。   梅老师从事音视频与流媒体行业18年;曾在永新视博、中科大洋、百度、美国Harris广播事业部等公司就职,经验丰富;曾亲手主导广电直播全套项目,精通h.264/h.265/aac,曾亲自参与百度app上的网页播放器等实战产品。  目前全身心自主创业,主要聚焦音视频+流媒体行业,精通音视频加密、流媒体在线转码快编等热门产品。  
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值