【发布时间】:2021-06-14 01:09:48
【问题描述】:
我有一个配备摄像头的树莓派 4,一个进程正在使用 OpenCV 库捕获图像。然后将此图像转发到两个目的地:
- 配备 Arch Linux 的标准笔记本电脑
- Android 智能手机
这是 Raspberry PI (C++) 上的图像服务器:
#define MAX_IMAGESIZE 40090
typedef struct __attribute__((packed))
{
uint16_t len;
uint8_t data[MAX_IMAGESIZE];
} image_msg;
void __attribute__((noreturn)) camera_task()
{
cv::VideoCapture capture(0);
int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
struct sockaddr_in pcaddr, phaddr;
memset(&pcaddr, 0x00, sizeof(struct sockaddr_in));
memset(&phaddr, 0x00, sizeof(struct sockaddr_in));
pcaddr.sin_family = AF_INET;
pcaddr.sin_addr.s_addr = inet_addr(PC_ADDRESS);
pcaddr.sin_port = htons(4321);
phaddr.sin_family = AF_INET;
phaddr.sin_addr.s_addr = inet_addr(PH_ADDRESS);
phaddr.sin_port = htons(4321);
if (!capture.isOpened())
{
exit(EXIT_FAILURE);
}
cv::Mat frame;
while (1)
{
if (!capture.read(frame))
{
exit(EXIT_FAILURE);
}
cv::flip(frame, frame, -1);
std::vector<int> params;
std::vector<uint8_t> buffer;
params.push_back(cv::IMWRITE_JPEG_QUALITY);
params.push_back(60);
cv::imencode(".jpg", frame, buffer, params);
image_msg outmsg;
outmsg.len = static_cast<uint16_t>(buffer.size());
for(uint16_t i = 0; i < outmsg.len; i++)
{
outmsg.data[i] = buffer[i];
}
if(sendto(sock, reinterpret_cast<char*>(&outmsg), sizeof(outmsg), 0, reinterpret_cast<struct sockaddr*>(&pcaddr), sizeof(pcaddr)) > 0)
{
//printf("OK sendto PC\n");
}
else
{
perror("Camera task to PC");
}
if(sendto(sock, reinterpret_cast<char*>(&outmsg), sizeof(outmsg), 0, reinterpret_cast<struct sockaddr*>(&phaddr), sizeof(phaddr)) > 0)
{
//printf("OK sendto PHONE\n");
}
else
{
perror("Camera task to PHONE");
}
}
}
这是笔记本电脑上的图像客户端(C++):
cv::Mat *imagewindow;
void init_window()
{
init_localsock(&imu_socket, &imu_saddr, IMUPORT); //not related with camera
init_localsock(&speed_socket, &speed_saddr, VELPORT); //not related with camera
init_localsock(&attitude_socket, &attitude_saddr, ATTPORT); //not related with camera
init_localsock(&radiation_socket, &radiation_saddr, RADPORT); //not related with camera
init_localsock(&throttle_socket, &throttle_saddr, THRPORT); //not related with camera
init_localsock(&image_socket, &image_saddr, RENPORT); //initialize socket on port 4321
imagewindow = new cv::Mat(600, 600, CV_8UC3, cv::Scalar(0, 0, 0));
cv::imshow(PROJNAME, *imagewindow);
}
void main_loop(const char* board_address)
{
while(true)
{
imu_task();
speed_task();
attitude_task();
radiation_task();
throttle_task();
image_task(); //this is the image receiver task
render_window();
cmd_out_task(board_address);
}
}
void image_task()
{
image_msg recv; //the same struct defined on the raspberry
socklen_t len;
ssize_t bytes_recv = recvfrom(image_socket, &recv, sizeof(image_msg), 0, (struct sockaddr*)&image_saddr, &len);
if(bytes_recv > 0)
{
memset(imagewindow->data, 0x00, imagewindow->dataend - imagewindow->data);
update_image(recv);
}
}
void update_image(image_msg image)
{
std::vector<char> data(image.data, image.data + image.len);
*imagewindow = cv::imdecode(cv::Mat(data), 1);
}
我们来解决问题。 android上的receiver如下:
private byte[] recv_image(DatagramSocket sock) throws Exception
{
byte[] b = new byte[40090];
DatagramPacket p = new DatagramPacket(b, b.length);
byte[] recv_length = new byte[4];
length[3] = b[0];
length[2] = b[1];
int recv_len = ByteBuffer.wrap(length).getInt();
byte[] image_bytes = new byte[recv_len];
for(int i = 0; i < recv_len; i++)
{
image_bytes[i] = b[4 + i];
}
return image_bytes;
}
接收线程正在做:
initialize_videosocket(); //init a local UDP socket on port 4321
while(true)
{
try
{
final byte[] imagebytes = recv_image(video_sock);
Bitmap bmp = BitmapFactory.decodeByteArray(imagebytes, 0, imagebytes.length);
}
catch(Exception e)
{
Log.d("Exception in image thread: " + e.toString();
}
}
问题在于 Bitmap bmp 始终为空。从文档中,如果 JPG 无效,则 decodeByteArray 返回 null。这些是我所做的尝试:
-
尝试在我的 android 应用中使用 opencv:
Mat mat = Imgcodecs.imdecode(new MatOfByte(imagebytes), Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); Bitmap bmp = Bitmap.createBitmap(mat.cols(), mat.rows(), Bitmap.Config.ARGB_8888); Utils.matToBitmap(rgb, bmp);但是
mat.cols()和mat.rows()都返回-1。我强制参数为 480,640(应该是),并且在执行Utils.matToBitmap时得到一个断言失败。 -
尝试比较笔记本电脑和手机上收到的 jpg 缓冲区。他们是平等的。
-
尝试在 .jpg 文件上写入 jpg 缓冲区,并使用 Gimp 将其作为标准 .jpg 文件打开。它有效,因此缓冲区包含正确的 .jpg 文件,但如果缓冲区不是有效的 .jpg 文件,则 decodeByteArray 应返回 null。
我真的陷入了困境,感谢任何帮助。谢谢
【问题讨论】:
-
but the decodeByteArray should return null if the buffer is not a valid .jpg file.你的意思是:decodeByteArray 应该返回一个位图,如果缓冲区包含一个有效的 .jpg 文件并且图像的分辨率不是那么大,以至于位图对于可用内存来说太大了。它的分辨率是多少?尝试发送一张小图片作为测试。 -
Tried to write jpg buffer on a file .jpg你是在你的安卓设备上用安卓代码做的吗? -
第一个问题,分辨率是640x480(宽,高),这里举个例子:ibb.co/RccSkTY 第二个,我是用PC做的。在 android 中我无法访问应用程序的 /data 文件夹,这是另一件有趣的事情
-
那非常小。应该不是问题。字节[] image_bytes = 新字节[recv_len];所以 recv_len 是服务器发送的 jpg 的字节大小?你检查了吗?
-
是的,我查过了,recv_len 是正确的