我正在尝试从子进程发送图像(通过从父进程调用popen
生成)到父进程。
图像是灰度png
图像。它使用OpenCV库打开,并使用相同库的imencode
函数进行编码。因此,得到的编码数据存储在std::vector
类型的uchar
结构中,即下面代码中的buf
向量。
首先,孩子发送父母所需的以下图像信息:
buf
向量的大小:需要这条信息,以便父母将分配一个相同大小的缓冲区,在那里写下它将从孩子那里收到的图像信息。分配如下进行(在这种情况下,buf
是用于接收数据的数组,而不是包含编码数据的向量):
u_char *buf = (u_char*)malloc(val*sizeof(u_char));
这些数据由孩子使用cout
在标准输出上写入,并由父母使用fgets
系统调用读取。
这些信息被正确发送和接收,因此直到现在都没有问题。
子进程使用buf
系统调用将编码数据(即向量write
中包含的数据)写入标准输出,而父进程使用popen
返回的文件描述符来读取数据。使用read
系统调用读取数据。
数据写入和读取是在循环内部的4096
字节块中执行的。写作行如下:
written += write(STDOUT_FILENO, buf.data()+written, s);
其中STDOUT_FILENO
告诉我在标准输出上写。 buf.data()
返回指向矢量结构内部使用的数组中第一个元素的指针。 written
存储到目前为止已写入的字节数,并将其用作索引。 s
是4096
每次尝试发送的字节数(write
)。 write
返回实际写入的字节数,用于更新written
。
数据读取非常相似,它由以下行执行:
bytes_read = read(fileno(fp), buf+total_bytes, bytes2Copy);
fileno(fp)
告诉从哪里读取数据(fp
是popen
返回的文件描述符)。 buf
是存储接收数据的数组,total_bytes
是到目前为止读取的字节数,因此它被用作索引。 bytes2Copy
是预期接收的字节数:它是BUFLEN
(即4096
)或最后一个数据块枯萎的剩余数据(如果例如总字节数为5000
则在4096
字节的1个块之后另一个5000-4096
块是预期)。
考虑这个例子。以下是使用popen
启动子进程的过程
#include <stdlib.h>
#include <unistd.h>//read
#include "opencv2/opencv.hpp"
#include <iostream>
#define BUFLEN 4096
int main(int argc, char *argv[])
{
//file descriptor to the child process
FILE *fp;
cv::Mat frame;
char temp[10];
size_t bytes_read_tihs_loop = 0;
size_t total_bytes_read = 0;
//launch the child process with popen
if ((fp = popen("/path/to/child", "r")) == NULL)
{
//error
return 1;
}
//read the number of btyes of encoded image data
fgets(temp, 10, fp);
//convert the string to int
size_t bytesToRead = atoi((char*)temp);
//allocate memory where to store encoded iamge data that will be received
u_char *buf = (u_char*)malloc(bytesToRead*sizeof(u_char));
//some prints
std::cout<<bytesToRead<<std::endl;
//initialize the number of bytes read to 0
bytes_read_tihs_loop=0;
int bytes2Copy;
printf ("bytesToRead: %ld\n",bytesToRead);
bytes2Copy = BUFLEN;
while(total_bytes_read<bytesToRead &&
(bytes_read_tihs_loop = read(fileno(fp), buf+total_bytes_read, bytes2Copy))
)
{
//bytes to be read at this iteration: either 4096 or the remaining (bytesToRead-total)
bytes2Copy = BUFLEN < (bytesToRead-total_bytes_read) ? BUFLEN : (bytesToRead-total_bytes_read);
printf("%d btytes to copy\n", bytes2Copy);
//read the bytes
printf("%ld bytes read\n", bytes_read_tihs_loop);
//update the number of bytes read
total_bytes_read += bytes_read_tihs_loop;
printf("%lu total bytes read\n\n", total_bytes_read);
}
printf("%lu bytes received over %lu expected\n", total_bytes_read, bytesToRead);
printf("%lu final bytes read\n", total_bytes_read);
pclose(fp);
cv::namedWindow( "win", cv::WINDOW_AUTOSIZE );
frame = cv::imdecode(cv::Mat(1,total_bytes_read,0, buf), 0);
cv::imshow("win", frame);
return 0;
}
并且上面打开的过程对应于以下内容:
#include <unistd.h> //STDOUT_FILENO
#include "opencv2/opencv.hpp"
#include <iostream>
using namespace std;
using namespace cv;
#define BUFLEN 4096
int main(int argc, char *argv[])
{
Mat frame;
std::vector<uchar> buf;
//read image as grayscale
frame = imread("test.png",0);
//encode image and put data into the vector buf
imencode(".png",frame, buf);
//send the total size of vector to parent
cout<<buf.size()<<endl;
unsigned int written= 0;
int i = 0;
size_t toWrite = 0;
//send until all bytes have been sent
while (written<buf.size())
{
//send the current block of data
toWrite = BUFLEN < (buf.size()-written) ? BUFLEN : (buf.size()-written);
written += write(STDOUT_FILENO, buf.data()+written, toWrite);
i++;
}
return 0;
}
孩子读取图像,对其进行编码并首先将尺寸(大小,#,#,#)发送给父母,然后发送编码图像数据。
父母首先读取尺寸(没有概率),然后它开始读取数据。在每次迭代时读取数据4096
字节。但是当缺少少于4096
的字节时,它会尝试只读取丢失的字节:在我的情况下,最后一步应该读取1027
字节(115715%4096
),但不是读取所有字节,而是只读取15。
我在最后两次迭代中打印的内容是:
4096 btytes to copy
1034 bytes read
111626 total bytes read
111626 bytes received over 115715 expected
111626 final bytes read
OpenCV(4.0.0-pre) Error: Assertion failed (size.width>0 && size.height>0) in imshow, file /path/window.cpp, line 356
terminate called after throwing an instance of 'cv::Exception'
what(): OpenCV(4.0.0-pre) /path/window.cpp:356: error: (-215:Assertion failed) size.width>0 && size.height>0 in function 'imshow'
Aborted (core dumped)
为什么read
没有读取所有丢失的字节?
我可能还有关于如何解码图像的错误,所以任何帮助也会受到赞赏。
编辑
在我看来,与一些建议相反,问题与\n
或\r
或\0
的存在无关。
实际上,当我打印以整数形式接收的数据时,使用以下行:
for (int ii=0; ii<val; ii++)
{
std::cout<<(int)buf[ii]<< " ";
}
我在数据中间看到qazxsw poi,qazxsw poi和qazxsw poi值(上面提到的字符的ASCII值),所以这让我觉得它不是问题。
0
这不可行。
10
例程被缓冲。缓冲区由实现控制。 13
将从文件中读取未知数量的字节并将其放入缓冲区。低级文件IO永远不会再看到这些字节。
您永远不会对两种样式的IO使用相同的文件。要么使用fgets(temp, 10, fp);
...
read(fileno(fp), ...)
做任何事情,要么用低级IO做任何事情。第一个选项是迄今为止最简单的选择,你只需用stdio
替换fgets(temp, 10, fp);
。
如果对于一些不明智的原因只知道黑暗的邪恶力量你想保留两种风格的IO,你可以通过在做任何其他事情之前调用stdio
来尝试。我从来没有这样做,也不能保证这种方法,但他们说它应该有用。我没有看到使用它的一个原因。
在一个可能不相关的注释中,您的读取循环在其终止条件中有一些逻辑,这些逻辑不容易理解并且可能无效。读取文件的常规方法大致如下:
read
更正确的方法是再次尝试fread
,所以修改后的条件可能看起来像
setvbuf(fp, NULL, _IOLBF, 0)
但此时可读性开始受损,您可能希望将其拆分为单独的语句。
您正在将二进制数据写入标准输出,这是期待文本。可以添加或删除换行符( left = data_size;
total = 0;
while (left > 0 &&
(got=read(file, buf+total, min(chunk_size, left))) > 0) {
left -= got;
total += got;
}
if (got == 0) ... // reached the end of file
else if (got < 0) ... // encountered an error
)和/或返回字符(got < 0 && errno == EINTR
),具体取决于文本文件中行尾的系统编码。由于您缺少字符,系统似乎正在删除这两个字符中的一个。
您需要将数据写入以二进制模式打开的文件,并且应该以二进制文件读取文件。
更新的答案
我不是C ++的世界上最好的,但是这有效并且会给你一个合理的起点。
while (left > 0 &&
(((got=read(file, buf+total, min(chunk_size, left))) > 0) ||
(got < 0 && errno == EINTR))) {
\n
\r
parent.cpp
原始答案
有几个问题:
从子进程写入数据的while循环不正确:
#include <stdlib.h>
#include <unistd.h>
#include <iostream>
#include "opencv2/opencv.hpp"
int main(int argc, char *argv[])
{
// File descriptor to the child process
FILE *fp;
// Launch the child process with popen
if ((fp = popen("./child", "r")) == NULL)
{
return 1;
}
// Read the number of bytes of encoded image data
std::size_t filesize;
fread(&filesize, sizeof(filesize), 1, fp);
std::cout << "Filesize: " << filesize << std::endl;
// Allocate memory to store encoded image data that will be received
std::vector<uint8_t> buffer(filesize);
int bufferoffset = 0;
int bytesremaining = filesize;
while(bytesremaining>0)
{
std::cout << "Attempting to read: " << bytesremaining << std::endl;
int bytesread = fread(&buffer[bufferoffset],1,bytesremaining,fp);
bufferoffset += bytesread;
bytesremaining -= bytesread;
std::cout << "Bytesread/remaining: " << bytesread << "/" << bytesremaining << std::endl;
}
pclose(fp);
// Display that image
cv::Mat frame;
frame = cv::imdecode(buffer, -CV_LOAD_IMAGE_ANYDEPTH);
cv::imshow("win", frame);
cv::waitKey(0);
}
想象一下你的图像是4097字节。您将在循环中第一次写入4096个字节,然后在缓冲区中只剩下1个字节时尝试在第二个传递中写入4096(即child.cpp
)字节。
你应该写出4096中较小者和缓冲区中剩余的字节。
发送文件的宽度和高度毫无意义,它们已经在您发送的PNG文件中编码。
没有必要在孩子中调用#include <cstdio>
#include <cstdint>
#include <vector>
#include <fstream>
#include <cassert>
#include <iostream>
int main()
{
std::FILE* fp = std::fopen("image.png", "rb");
assert(fp);
// Seek to end to get filesize
std::fseek(fp, 0, SEEK_END);
std::size_t filesize = std::ftell(fp);
// Rewind to beginning, allocate buffer and slurp entire file
std::fseek(fp, 0, SEEK_SET);
std::vector<uint8_t> buffer(filesize);
std::fread(buffer.data(), sizeof(uint8_t), buffer.size(), fp);
std::fclose(fp);
// Write filesize to stdout, followed by PNG image
std::cout.write((const char*)&filesize,sizeof(filesize));
std::cout.write((const char*)buffer.data(),filesize);
}
将PNG文件从磁盘转换为while (written<buf.size())
{
//send the current block of data
written += write(STDOUT_FILENO, buf.data()+written, s);
i++;
}
,然后调用s
将其转换回PNG以发送给父母。只需imread()
并将文件读为二进制文件并发送 - 它已经是一个PNG文件。
我认为您需要清楚地知道您是在发送PNG文件还是纯像素数据。 PNG文件将具有:
仅像素数据文件将具有: