如何通过socket传输OpenCV Mat数据

3
我正在尝试使用C ++从树莓派向我的Windows PC发送OpenCV Mat,通过TCP传输。 我的代码正在工作并且正在发送和获取帧,但是这些图像是灰度和质量非常差。

客户端(Windows):

#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <fstream>
#include <string.h>
#include <sys/types.h>
#include <winsock2.h>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
#include "WS2tcpip.h"

#define bzero(b,len) (memset((b),'\0',(len)),(void)0)
#define bcopy(b1,b2,len) (memmove((b2),(b1),(len)),(void) 0)

using namespace std;

int main()
{
    int sockfd, newsockfd, portno;
    socklen_t clilen;
    struct sockaddr_in serv_addr, cli_addr;
    int n;

    // IMPORTANT
    WSADATA Data;
    WSAStartup(MAKEWORD(2, 2), &Data);
    ////////////

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        cout << "Error while opening socket"<<endl;
        return -1;
    }

    bzero((char*)&serv_addr,sizeof(serv_addr));
    portno = 5001;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(portno);
    if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        cout << "Error while binding." << endl;
        return -1;
    }
    listen(sockfd, 5);
    clilen = sizeof(cli_addr);
    newsockfd = accept(sockfd, (struct sockaddr *)&cli_addr,&clilen);

    if (newsockfd < 0) {
        cout << "Error while accepting socket" << endl;
        return -1;
    }

    cv::Mat img;
    img = cv::Mat::zeros(480, 640, CV_8UC1);
    int imgSize = img.total() * img.elemSize();
    uchar *iptr = img.data;
    int bytes = 0;

    if (!img.isContinuous()) {
        img = img.clone();
    }

    cout << "Img size: " << imgSize << endl;

    while (1) {
        if ((bytes = recv(newsockfd, (char *)iptr, imgSize, MSG_WAITALL)) == -1) {
            cout << "Error while recv frame" << endl;
            break;
        }
        cv::imshow("VIDEO CLIENT", img);
        cv::waitKey(30);
    }


    // IMPORTANT
    WSACleanup();
    //////////////

    return 0;
}

服务器端(树莓派):
#include <ctime>
#include <iostream>
#include <raspicam/raspicam_cv.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <opencv2/imgproc/imgproc.hpp>
using namespace std;

int main (int argc, char **argv){
 int sockfd,portno,n;
 struct sockaddr_in serv_addr;
 struct hostent *server;

 portno=5001;
 sockfd = socket(AF_INET,SOCK_STREAM,0);
 if(sockfd<0){
  cout << "Error while opening socket."<<endl;
  return -1;
 }else{
  cout << "Socket is now openned."<<endl;
 }
 server = gethostbyname("192.168.1.10");
 if(server ==NULL){
  cout << "Host does not exist."<<endl;
  return -1;
 }else{
  cout << "Valid host!"<<endl;
 }


 bzero((char *) &serv_addr,sizeof(serv_addr));
 serv_addr.sin_family = AF_INET;
 bcopy((char*)server->h_addr,
        (char *)&serv_addr.sin_addr.s_addr,
        server->h_length);
 serv_addr.sin_port = htons(portno);
 if (connect(sockfd,(struct sockaddr *) &serv_addr,sizeof(serv_addr))<0){
  cout << "Error while trying to connect."<<endl;
  return -1;
 }
 else{
  cout << "Connected!"<<endl;
 }
 raspicam::RaspiCam_Cv cam;
 cv::Mat image;
 image = cv::Mat::zeros(480,640,CV_8UC1);
 if (!image.isContinuous()){
  image = image.clone();
 }
 int imageSize = image.total()*image.elemSize();
 int bytes = 0;

 cout << "img size: " << imageSize << endl;

 cam.set(CV_CAP_PROP_FORMAT,CV_8UC1);

 cout << "Opening camera..."<<endl;
 if (!cam.open()){
  cout << "Error while opening camera." << endl;
  return -1;
 }
while(1){
 cout << "Capturing frame..." << endl;
 cam.grab();
 cam.retrieve(image);
 if(!image.empty()){
   if((bytes = send(sockfd,image.data,imageSize,0))<0){
     cout << "Error while sending..";
     break;
   }
   cout << "Frame sent sucessfuly" << endl;
   cout << bytes << " bytes sent." <<endl;
 }else{
  cout << "Frame was empty"<<endl;
  break;
 }
}
cout << "Stopping camera..."<<endl;
cam.release();
return 0;
}

在这里,您可以看到我的客户端如何获取帧。
我不知道是什么导致了这个问题。在捕获帧之后,我是否漏掉了任何东西?

将 cv::Mat 进行序列化可能具有挑战性。您是否考虑过使用轻微压缩的 png 对其进行编码,然后通过网络发送并解压缩 png? - Xvolks
我不会在recv中依赖MSG_WAITALL。更多阅读:https://stackoverflow.com/questions/12214356/winsock2-how-to-open-a-tcp-socket-that-allows-recv-with-msg-waitall - user4581301
1个回答

5
我不建议在计算机之间发送cv :: Mat,特别是在具有不同CPU架构,字节顺序,字长和内存填充的计算机之间发送。将图像转换为无损压缩(或非压缩)格式,如png,并进行发送。您可以使用cv :: encode / cv :: decode在内存中创建图像格式,而无需读取/写入临时文件。请注意:imdecode从图像格式(png,jpeg等)标题中的数据确定图像类型。接收方不需要知道大小或类型。

你的意思是我应该按照以下步骤进行吗?:获取帧 > 将帧保存为PNG格式 > 压缩PNG > 发送PNG > ... - E. Williams
我已经从服务器发送了缓冲区,但是如何将该缓冲区转换为Mat对象?我的客户端代码:recv(newsockfd, (char *)iptr, imgSize, MSG_WAITALL) cv::Mat aux= cv::Mat(480, 640, CV_8UC3, iptr); cv::Mat mat= cv::imdecode(aux, 0); cv::imshow("VIDEO CLIENT", mat); 这显示了一个完全黑色的帧。 - E. Williams
@E.Williams - 你可以直接将数据指针从套接字传递给imdecode。它可以从图像数据中知道大小和格式,因此您不需要先创建cv :: mat。 - Martin Beckett
没有imdecode(uchar,Mat)方法。只有imdecode(InputArray,Mat*)。 - E. Williams
inputarray 应该接受一个字节向量,你可能需要将其转换为 void* 或其他类型。不幸的是,在使用 c++ opencv 时,你经常需要尝试并查看调试器以找出实际发生了什么;-( - Martin Beckett

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接