You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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. 服务器端缓存逻辑错误:原代码仅捕获1帧并缓存,没有循环录制帧到缓存容器中
  2. 缓存结构不合理:只用单个vector<uchar> buf存储单帧数据,需要用vector<vector<uchar>>存储所有编码后的帧
  3. 传输逻辑缺失:客户端连接后,服务器没有遍历缓存发送所有帧,仅尝试发送一次就退出
  4. 客户端接收逻辑错误: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;
}

关键修改说明

  1. 服务器端:

    • 用vector<vector<uchar>> frame_cache存储所有编码后的JPG帧,避免原始Mat数据占用过多内存
    • 客户端连接前持续捕获摄像头帧并编码缓存,同时控制帧率
    • 传输时先发送帧总数,再逐个发送帧长度+帧数据,确保客户端能正确解析每帧
    • 修复send的循环发送逻辑,避免因TCP粘包/半包导致数据不完整
  2. 客户端:

    • 先接收帧总数,再循环接收每帧的长度和数据
    • 修复recv的循环接收逻辑,确保完整接收每帧数据
    • 对接收的JPG数据解码后写入视频文件
    • 修正原代码中的运算符优先级错误

内容的提问来源于stack exchange,提问作者konstitycii

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 23:48:11