使用命名(fifo)管道在python和c++之间传输数组(图像)

exc*_*npt 3 c++ python linux ipc pipe

我需要通过命名的 FIFO 管道从 python 进程发送一个数组(代表一个图像)到一个 c++ 进程,然后以另一种方式返回(在 Linux 系统上)。

在两个 Python 进程之间使用命名管道时,以下代码效果很好。它使用 numpy 的 tostring() 和 fromstring() 函数:

通过命名管道发送帧 (Python)

import cv2
import numpy as np
from time import sleep

##########################################################

FIFO_Images = "./../pipes/images.fifo"
videoName = "./../../videos/videoName.avi"
delim = "break"

##########################################################


def sendImage(h, w, d, pixelarray):
    imageString = pixelarray.tostring()
    with open(FIFO_Images, "w") as f:
        f.write(str(h)+ delim + str(w)+ delim + str(d) + delim + imageString)
    sleep(.01)  
    return 

##########################################################

cap = cv2.VideoCapture(videoName)

while(cap.isOpened()):
    ret, frame_rgb = cap.read() 
    h, w, d = frame_rgb.shape
    sendImage(h, w, d, frame_rgb)

cap.release()
cv2.destroyAllWindows()
Run Code Online (Sandbox Code Playgroud)

通过命名管道读取帧 (Python)

import cv2
import numpy as np

##########################################################

FIFO_Images = "./../pipes/images.fifo"
delim = "break"

##########################################################

def getFrame():
    with open(FIFO_Images, "r") as f:
        data = f.read().split(delim)

        #parse incoming string, which has format (height, width, depth, imageData)        
        h=int(data[0])
        w=int(data[1])
        d=int(data[2])
        imageString = data[3]

        #convert array string into numpy array
        array = np.fromstring(imageString, dtype=np.uint8)

        #reshape numpy array into the required dimensions
        frame = array.reshape((h,w,d))

        return frame  

##########################################################

while(True):

    frame = getFrame()

    cv2.imshow('frame', frame)
    cv2.waitKey(1) & 0xFF 
Run Code Online (Sandbox Code Playgroud)

但是,我无法弄清楚如何从 cpp 端的管道读取整个图像,因为它自动将“\n”作为读取的分隔符。

我的解决方法是做对A base64编码“的ToString()”的形象,然后把那个在管道。这有效,但另一张幻灯片上的 base64 解码对于实时应用程序来说太慢了(每帧约 0.2 秒)。代码:

通过命名管道发送 base64 编码的图像 (Python)

import cv2
import numpy as np
from time import time
from time import sleep
import base64

##########################################################

FIFO_Images = "./../pipes/images.fifo"
videoName = "./../../videos/videoName.avi"

delim = ";;"

##########################################################

def sendImage(h, w, d, pixelarray):


    flat = pixelarray.flatten()

    imageString = base64.b64encode(pixelarray.tostring())
    fullString = str(h)+ delim + str(w)+ delim + str(d)+ delim + imageString + delim + "\n"


    with open(FIFO_Images, "w") as f:
        f.write(fullString)

    return 

##########################################################

cap = cv2.VideoCapture(videoName)
count = 0

while(cap.isOpened()):
    ret, frame_rgb = cap.read()
    h, w, d = frame_rgb.shape

    frame_gbr = cv2.cvtColor(frame_rgb, cv2.COLOR_RGB2BGR)

    sendImage(h, w, d, frame_rgb)


cap.release()
cv2.destroyAllWindows()
Run Code Online (Sandbox Code Playgroud)

通过命名管道读取 base64 编码的图像 (C++)

#include "opencv2/opencv.hpp"

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <unistd.h>
#include <fcntl.h>

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <linux/stat.h>
#include <ctime>

using namespace std;
using namespace cv;

#define FIFO_FILE "./../../../pipes/images.fifo"
#define MAX_BUF 10000000

FILE *fp;
char readbuf[MAX_BUF + 1];  //add 1 to the expected size to accomodate the mysterious "extra byte", which I think signals the end of the line. 


/************************BASE64 Decoding*********************************************/
std::string base64_encode(unsigned char const* , unsigned int len);
std::string base64_decode(std::string const& s);

static const std::string base64_chars = 
             "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
             "abcdefghijklmnopqrstuvwxyz"
             "0123456789+/";


static inline bool is_base64(unsigned char c) {
  return (isalnum(c) || (c == '+') || (c == '/'));
}

std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) {
  std::string ret;
  int i = 0;
  int j = 0;
  unsigned char char_array_3[3];
  unsigned char char_array_4[4];

  while (in_len--) {
    char_array_3[i++] = *(bytes_to_encode++);
    if (i == 3) {
      char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
      char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
      char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
      char_array_4[3] = char_array_3[2] & 0x3f;

      for(i = 0; (i <4) ; i++)
        ret += base64_chars[char_array_4[i]];
      i = 0;
    }
  }

  if (i)
  {
    for(j = i; j < 3; j++)
      char_array_3[j] = '\0';

    char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
    char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
    char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
    char_array_4[3] = char_array_3[2] & 0x3f;

    for (j = 0; (j < i + 1); j++)
      ret += base64_chars[char_array_4[j]];

    while((i++ < 3))
      ret += '=';

  }

  return ret;

}

std::string base64_decode(std::string const& encoded_string) {
  int in_len = encoded_string.size();
  int i = 0;
  int j = 0;
  int in_ = 0;
  unsigned char char_array_4[4], char_array_3[3];
  std::string ret;

  while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
    char_array_4[i++] = encoded_string[in_]; in_++;
    if (i ==4) {
      for (i = 0; i <4; i++)
        char_array_4[i] = base64_chars.find(char_array_4[i]);

      char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
      char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
      char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

      for (i = 0; (i < 3); i++)
        ret += char_array_3[i];
      i = 0;
    }
  }

  if (i) {
    for (j = i; j <4; j++)
      char_array_4[j] = 0;

    for (j = 0; j <4; j++)
      char_array_4[j] = base64_chars.find(char_array_4[j]);

    char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
    char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
    char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

    for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
  }

  return ret;
}

/*********************************************************************/

int stringToInt(string str)
{
    int num;
    if (!(istringstream(str) >> num)) num = 0;

    return num;
}

/*********************************************************************/
bool timerOn = 0;
clock_t timerStart;

void Timer(string process)
{

    if (!timerOn)
    {
        timerStart = clock();
        timerOn = true;
    }

    else if (timerOn)
    {
        double duration = (clock() - timerStart) / (double) CLOCKS_PER_SEC; 
        cout << "Time to complete: ";
        printf("%.2f", duration);
        cout << ": " << process << endl;
        timerOn = false;
    }
}

/*********************************************************************/

void getFrame()
{
    string fullString;
    string delimiter = ";;";
    size_t pos = 0;
    string token;

    int h;
    int w;
    int d;
    string imgString;

    int fifo;
    bool cont(true);

    /***************************
    Read from the pipe
    www.tldp.org/LDP/lpg/node18.html
    ***************************/
    Timer("Read from pipe");
    fp = fopen(FIFO_FILE, "r");
    fgets(readbuf, MAX_BUF + 1, fp); // Stops when MAX_BUF characters are read, the newline character ("\n") is read, or the EOF (end of file) is reached
    string line(readbuf);
    fclose(fp);
    Timer("Read from pipe");

    //////parse the string into components

    Timer("Parse string");
    int counter = 0;
    while ((pos = line.find(delimiter)) != string::npos)
    {
        token = line.substr(0,pos);

        if (counter == 0)
        {
            h = stringToInt(token);
        }
        else if (counter == 1)
        {
            w = stringToInt(token);
        }
        else if (counter == 2)
        {
            d = stringToInt(token);
        }
        else if (counter == 3)
        {
            imgString = token;
            //cout << imgString[0] << endl;
        }
        else
        {
            cout << "ERROR: Too many paramaters passed" << endl;
            return;
        }

        line.erase(0, pos + delimiter.length());

        counter ++;
    }

    if (counter == 3)
    {
        imgString = token;
    }

    if (counter < 3)
    {
        cout << "ERROR: Not enough paramaters passed: " << counter << endl;
        //return;
    }

    Timer("Parse string");



    /***************************
    Convert from Base64
    ***************************/      
    Timer("Decode Base64");
    std::string decoded = base64_decode(imgString);
    Timer("Decode Base64");

    /***************************
    Convert to vector of ints
    ***************************/
    Timer("Convert to vector of ints");   
    std::vector<uchar> imgVector;   
    for (int i = 0; i < decoded.length(); i = i+1) // + 4)
    {
        int temp =  (char(decoded[i]));
        imgVector.push_back(temp);
    }
    Timer("Convert to vector of ints");

    //////convert the vector into a matrix
    Mat frame = Mat(imgVector).reshape(d, h);   

    namedWindow("Frame", WINDOW_AUTOSIZE);
    imshow("Frame", frame);

    waitKey(1); 
}


int main()
{

    /* Create the FIFO if it does not exist */
    umask(0);
    mknod(FIFO_FILE, S_IFIFO|0666, 0);

    while(1)
    {
        getFrame();

    }

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

必须有一种更有效的方法来实现这一点。任何人都可以提出建议吗?虽然我很高兴听到关于其他方法的建议来实现这一点,但我现在只能使用命名管道

Mes*_*ssa 5

这太复杂了。如果您需要发送二进制数据,请先发送它们的长度,然后是换行符 ( \n),然后是数据(原始数据,无 base64)。通过读取一行,解析数字然后只读取给定长度的数据块来在另一侧接收它。

示例- 在 Python 中将二进制数据写入 FIFO(或文件):

#!/usr/bin/env python3

import os

fifo_name = 'fifo'

def main():
    data = b'blob\n\x00 123'
    try:
        os.mkfifo(fifo_name)
    except FileExistsError:
        pass
    with open(fifo_name, 'wb') as f:
        # b for binary mode
        f.write('{}\n'.format(len(data)).encode())
        f.write(data)

if __name__ == '__main__':
    main()
Run Code Online (Sandbox Code Playgroud)

在 C++ 中从 FIFO 读取二进制数据:

#include <iostream>
#include <fstream>
#include <string>
#include <vector>

#include <sys/stat.h>

int main(int argc, char *argv[]) {
    const char *fifo_name = "fifo";
    mknod(fifo_name, S_IFIFO | 0666, 0);
    std::ifstream f(fifo_name);
    std::string line;
    getline(f, line);
    auto data_size = std::stoi(line);
    std::cout << "Size: " << data_size << std::endl;
    std::string data;
    {
        std::vector<char> buf(data_size);
        f.read(buf.data(), data_size);
        // write to vector data is valid since C++11
        data.assign(buf.data(), buf.size());
    }
    if (!f.good()) {
        std::cerr << "Read failed" << std::endl;
    }
    std::cout << "Data size: " << data.size() << " content: " << data << std::endl;
}
Run Code Online (Sandbox Code Playgroud)