OpenCV C++客户端-服务器视频传输问题:仅传单帧需优化
问题描述
我是OpenCV新手,需要开发一套客户端-服务器程序,要求客户端连接前,服务器端将摄像头视频录制到缓存;客户端连接后,通过TCP/IP传输缓存中的全部视频帧。目前编写的代码仅能传输1帧,需要调整缓存结构或修改基于send/recv的传输逻辑来实现需求。
原服务器端代码
#include <unistd.h> #include <arpa/inet.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <sys/socket.h> #include <string> #include <vector> #include <opencv2/opencv.hpp> #include <fcntl.h> #include <opencv2/highgui/highgui.hpp> #include <opencv2/core/core.hpp> using namespace std; using namespace cv; int c,new_socket; std::vector<uchar> arrak; int main() { int sock,listener; std::vector<uchar> buf; std::vector<int> params(2); params[0] = cv::IMWRITE_PNG_COMPRESSION; params[1] = 10; struct sockaddr_in addr,client; sock = socket(AF_INET, SOCK_STREAM, 0);// подключение по сокету if(sock < 0){ perror("socket"); exit(1); } fcntl(sock, F_SETFL, O_NONBLOCK); memset(&addr,0,sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(3425); addr.sin_addr.s_addr = htonl(INADDR_ANY);// подключается к любому ip if(bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { // связывание сокетов perror("[server] binding faild!"); exit(2); } Mat frame; int bbytee; int imgSize = frame.total() * frame.elemSize(); // int imgSize = frame.cols*frame.rows*3; // узнаем размер изображения на три канала frame = Mat::zeros(1080, 1920, CV_8UC3); // заполнение нулями массива frame listen(sock, 1); // прослушивание подключений while (true){ c = sizeof(struct sockaddr_in); // размер структуры puts("Waiting for incoming connections..."); new_socket = accept(sock, (struct sockaddr *)&client,(socklen_t*)&c); if(new_socket==-1) //принятие подключения и создания по него нового сокета { puts("Connection NO NO NO NO accepted"); cout << "new_socket = " << new_socket << "\n"; cout << "before open the cam" << endl; cout << "IDET ZAPIS!" << endl; VideoCapture cap(2); // захват видеокамеры if(!cap.isOpened()) { // проверка условий подключения cout<< "Could not open the camera" << endl; close(sock); return -1; } int cnt=0; cap >> frame;// передача кадра с cap в frame /*if (frame.isContinuous()) { // array.assign(mat.datastart, mat.dataend); // <- has problems for sub-matrix like mat = big_mat.row(i) arrak.assign(frame.data, frame.data + frame.total()*frame.channels()); } else { for (int i = 0; i < frame.rows; ++i) { arrak.insert(arrak.end(), frame.ptr<uchar>(i), frame.ptr<uchar>(i)+frame.cols*frame.channels()); } } cout<<"VECTOR: "<<arrak[1]<<endl; */ if(frame.empty()) { cerr<<"[client] VideoCapture(0) error!"<<endl; } cv::imencode(".jpg", frame, buf, params); cout<< ++cnt << ":"<< frame.isContinuous()<<"," <<frame.size()<<endl;; } else { while (true){ int p=sizeof(frame)/sizeof(frame.data[0]); printf("BUFF 1: %d\n",buf[1]); printf("BUFF 2: %d\n",buf[2]); break; //cout <<"CONNECT ! CONNECT ! CONNECT !"; cout << " Frame size :"<<frame.size()<<endl; if( (bbytee = send(new_socket, frame.data, imgSize, 0)) ==-1) {//передача данных в сокет cerr<< "bytes = " << bbytee << endl; break; } } break; } } cout<< "KONESSSSSSSSSS "<<endl; if (frame.isContinuous()) { // array.assign(mat.datastart, mat.dataend); // <- has problems for sub-matrix like mat = big_mat.row(i) arrak.assign(frame.data, frame.data + frame.total()*frame.channels()); } else { for (int i = 0; i < frame.rows; ++i) { arrak.insert(arrak.end(), frame.ptr<uchar>(i), frame.ptr<uchar>(i)+frame.cols*frame.channels()); } } cout<<"VECTOR: "<<arrak[1]<<endl; cout <<"END"<<endl; close(sock);//закрытие сокета return 0; }
原客户端代码
#include <unistd.h> #include <arpa/inet.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <sys/socket.h> #include <string> #include <vector> #include <opencv2/opencv.hpp> using namespace std; using namespace cv; int main() { int sock; struct sockaddr_in addr; sock = socket(AF_INET, SOCK_STREAM, 0);//открытие сокета if(sock < 0){//проверка на открытие perror("socket"); exit(1); } memset(&addr,0,sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(3425); addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_addr.s_addr = inet_addr("192.168.3.11");// указано IP платы if(connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0){//подключение к плате perror("connect"); exit(2); } while(true){ int num_of_recv_bytes; VideoWriter outputVideo; Size S = Size((int) 1920,(int) 1080); outputVideo.open("receive.avi", VideoWriter::fourcc('H','2','6','4'), 25, S, true); int imgSize = 1080*1920*3; Mat frame = Mat::zeros(1080, 1920, CV_8UC3);//заполнение нулями uchar *iptr = frame.data; int key; int cnt=0; while(1){ cout << ++cnt<<endl; while(key != 'q') { if( num_of_recv_bytes = recv(sock, iptr, imgSize, MSG_WAITALL) == -1 ) {// чтение данных из сокета cerr << "recv failed, received bytes = " << num_of_recv_bytes << endl; } outputVideo<< frame; if (key = waitKey(100) >= 0) break; } outputVideo.release(); close(sock);// закрытие сокета break; } return 0; } }
问题分析
- 服务器端缓存逻辑错误:原代码仅捕获1帧并缓存,没有循环录制帧到缓存容器中
- 缓存结构不合理:只用单个
vector<uchar> buf存储单帧数据,需要用vector<vector<uchar>>存储所有编码后的帧 - 传输逻辑缺失:客户端连接后,服务器没有遍历缓存发送所有帧,仅尝试发送一次就退出
- 客户端接收逻辑错误:
num_of_recv_bytes = recv(...) == -1存在运算符优先级问题,会将布尔值赋值给变量;且没有处理多帧的接收与解码
修改后的代码
服务器端代码(核心修改:多帧缓存+批量传输)
#include <unistd.h> #include <arpa/inet.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <string> #include <vector> #include <opencv2/opencv.hpp> #include <fcntl.h> #include <chrono> #include <thread> using namespace std; using namespace cv; // 存储所有编码后的帧 vector<vector<uchar>> frame_cache; // 编码参数(JPG压缩,质量80) vector<int> encode_params = {IMWRITE_JPEG_QUALITY, 80}; int main() { int sock, new_socket; struct sockaddr_in addr, client; socklen_t c = sizeof(struct sockaddr_in); // 创建TCP套接字并设置非阻塞 sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { perror("socket creation failed"); exit(1); } fcntl(sock, F_SETFL, O_NONBLOCK); // 绑定地址 memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(3425); addr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("bind failed"); close(sock); exit(2); } // 监听连接 if (listen(sock, 1) < 0) { perror("listen failed"); close(sock); exit(3); } // 启动摄像头录制缓存(客户端连接前持续录制) VideoCapture cap(2); if (!cap.isOpened()) { cerr << "Could not open camera" << endl; close(sock); return -1; } cap.set(CAP_PROP_FRAME_WIDTH, 1920); cap.set(CAP_PROP_FRAME_HEIGHT, 1080); cout << "Recording to cache, waiting for client connection..." << endl; Mat frame; while (true) { // 尝试接受客户端连接 new_socket = accept(sock, (struct sockaddr *)&client, &c); if (new_socket != -1) { cout << "Client connected, starting to send cached frames..." << endl; break; } // 捕获帧并编码缓存 if (cap.read(frame) && !frame.empty()) { vector<uchar> encoded_frame; imencode(".jpg", frame, encoded_frame, encode_params); frame_cache.push_back(encoded_frame); // 控制帧率(约25帧/秒) this_thread::sleep_for(chrono::milliseconds(40)); } } // 发送缓存的所有帧:先发送帧总数,再逐个发送帧长度+帧数据 int frame_count = frame_cache.size(); send(new_socket, &frame_count, sizeof(frame_count), 0); cout << "Total cached frames to send: " << frame_count << endl; for (auto& encoded_frame : frame_cache) { // 发送当前帧的字节长度 int frame_size = encoded_frame.size(); send(new_socket, &frame_size, sizeof(frame_size), 0); // 发送帧数据(循环确保全部发送) int bytes_sent = 0; while (bytes_sent < frame_size) { int ret = send(new_socket, encoded_frame.data() + bytes_sent, frame_size - bytes_sent, 0); if (ret == -1) { perror("send failed"); close(new_socket); close(sock); return -1; } bytes_sent += ret; } } cout << "All cached frames sent successfully" << endl; close(new_socket); close(sock); return 0; }
客户端代码(核心修改:多帧接收+解码写入)
#include <unistd.h> #include <arpa/inet.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <string> #include <vector> #include <opencv2/opencv.hpp> using namespace std; using namespace cv; int main() { int sock; struct sockaddr_in addr; // 创建TCP套接字 sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { perror("socket creation failed"); exit(1); } // 设置服务器地址 memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(3425); if (inet_pton(AF_INET, "192.168.3.11", &addr.sin_addr) <= 0) { perror("invalid server address"); close(sock); exit(2); } // 连接服务器 if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("connect failed"); close(sock); exit(3); } // 初始化视频写入器 Size frame_size(1920, 1080); VideoWriter output_video("receive.avi", VideoWriter::fourcc('H','2','6','4'), 25, frame_size, true); if (!output_video.isOpened()) { cerr << "Could not open video writer" << endl; close(sock); return -1; } // 接收帧总数 int frame_count; recv(sock, &frame_count, sizeof(frame_count), MSG_WAITALL); cout << "Receiving " << frame_count << " frames..." << endl; for (int i = 0; i < frame_count; ++i) { // 接收当前帧的字节长度 int frame_size_bytes; recv(sock, &frame_size_bytes, sizeof(frame_size_bytes), MSG_WAITALL); // 接收帧数据 vector<uchar> encoded_frame(frame_size_bytes); int bytes_received = 0; while (bytes_received < frame_size_bytes) { int ret = recv(sock, encoded_frame.data() + bytes_received, frame_size_bytes - bytes_received, 0); if (ret == -1) { perror("recv failed"); output_video.release(); close(sock); return -1; } bytes_received += ret; } // 解码帧并写入视频 Mat frame = imdecode(encoded_frame, IMREAD_COLOR); if (!frame.empty()) { output_video << frame; cout << "Received frame " << i+1 << "/" << frame_count << endl; } } cout << "All frames received, saved to receive.avi" << endl; output_video.release(); close(sock); return 0; }
关键修改说明
服务器端:
- 用
vector<vector<uchar>> frame_cache存储所有编码后的JPG帧,避免原始Mat数据占用过多内存 - 客户端连接前持续捕获摄像头帧并编码缓存,同时控制帧率
- 传输时先发送帧总数,再逐个发送帧长度+帧数据,确保客户端能正确解析每帧
- 修复
send的循环发送逻辑,避免因TCP粘包/半包导致数据不完整
- 用
客户端:
- 先接收帧总数,再循环接收每帧的长度和数据
- 修复
recv的循环接收逻辑,确保完整接收每帧数据 - 对接收的JPG数据解码后写入视频文件
- 修正原代码中的运算符优先级错误
内容的提问来源于stack exchange,提问作者konstitycii
相关产品推荐
相关产品推荐

