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

Android将ImageProxy转byteArray经Socket发至Python服务器后图片无效求助

问题修复方案

1. 修复ImageProxy转NV21的核心逻辑

CameraX返回的YUV_420_888格式中,每个平面的rowStride(行步长)可能大于实际图像宽度,直接用buffer.remaining()拷贝会包含填充的无效数据,导致YUV转JPEG失败。修改转换方法:

private byte[] imageProxyToByteArray(ImageProxy image) {
    ImageProxy.PlaneProxy[] planes = image.getPlanes();
    ByteBuffer yBuffer = planes[0].getBuffer();
    ByteBuffer uBuffer = planes[1].getBuffer();
    ByteBuffer vBuffer = planes[2].getBuffer();

    int width = image.getWidth();
    int height = image.getHeight();

    // 提取行步长和像素步长,忽略填充数据
    int yRowStride = planes[0].getRowStride();
    int uvRowStride = planes[1].getRowStride();
    int uvPixelStride = planes[1].getPixelStride();

    byte[] nv21 = new byte[width * height * 3 / 2];

    // 拷贝Y平面有效像素数据
    int yIndex = 0;
    for (int i = 0; i < height; i++) {
        yBuffer.position(i * yRowStride);
        yBuffer.get(nv21, yIndex, width);
        yIndex += width;
    }

    // 拷贝UV平面数据,按NV21格式(VU交替)排列
    int uvIndex = width * height;
    for (int i = 0; i < height / 2; i++) {
        uBuffer.position(i * uvRowStride);
        vBuffer.position(i * uvRowStride);
        for (int j = 0; j < width / 2; j++) {
            nv21[uvIndex++] = vBuffer.get(j * uvPixelStride);
            nv21[uvIndex++] = uBuffer.get(j * uvPixelStride);
        }
    }

    YuvImage yuvImage = new YuvImage(nv21, ImageFormat.NV21, width, height, null);
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    yuvImage.compressToJpeg(new Rect(0, 0, width, height), 100, out);
    return out.toByteArray();
}

2. 替换Socket分隔符方案

原方案用<start>/<end>字符串作为分隔符,存在与JPEG数据冲突的风险,改用长度前缀方式确保数据完整性:

Android端发送逻辑修改

if (imageBytes != null && imageBytes.length > 0) {
    try (Socket socket = new Socket("192.168.143.234", 65432)) {
        OutputStream outputStream = socket.getOutputStream();
        // 先发送4字节的图片长度(大端序)
        byte[] lengthBytes = ByteBuffer.allocate(4).putInt(imageBytes.length).array();
        outputStream.write(lengthBytes);
        // 发送图片数据
        outputStream.write(imageBytes);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

Python端接收逻辑修改

import socket
import numpy as np
import cv2

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('0.0.0.0', 65432))
server_socket.listen(1)

while True:
    client_socket, addr = server_socket.accept()
    # 读取4字节的长度信息
    length_data = client_socket.recv(4)
    if not length_data:
        break
    img_length = int.from_bytes(length_data, byteorder='big')
    # 分块读取完整图片数据
    img_array = b''
    while len(img_array) < img_length:
        chunk = client_socket.recv(min(img_length - len(img_array), 4096))
        if not chunk:
            break
        img_array += chunk
    # 解码并保存图片
    image = cv2.imdecode(np.frombuffer(img_array, dtype=np.uint8), cv2.IMREAD_COLOR)
    if image is not None:
        cv2.imwrite('received_image.jpg', image)
    client_socket.close()

3. 可选优化:保持Socket长连接

每次创建新Socket会增加开销,建议全局持有Socket并维护连接:

private Socket socket = null;

// 初始化连接(如在Activity onCreate中)
try {
    socket = new Socket("192.168.143.234", 65432);
} catch (IOException e) {
    e.printStackTrace();
}

// 分析器中发送数据
imageAnalysis.setAnalyzer(executor, image -> {
    byte[] imageBytes = imageProxyToByteArray(image);
    if (imageBytes != null && imageBytes.length > 0 && socket != null && socket.isConnected()) {
        try {
            OutputStream outputStream = socket.getOutputStream();
            byte[] lengthBytes = ByteBuffer.allocate(4).putInt(imageBytes.length).array();
            outputStream.write(lengthBytes);
            outputStream.write(imageBytes);
            outputStream.flush();
        } catch (Exception e) {
            e.printStackTrace();
            // 连接断开时尝试重连
            try {
                socket.close();
                socket = new Socket("192.168.143.234", 65432);
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    image.close();
});

// Activity销毁时关闭连接
@Override
protected void onDestroy() {
    super.onDestroy();
    if (socket != null) {
        try {
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

额外检查点

  • 若开启setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888),可简化转换逻辑:直接将ByteBuffer转Bitmap再压缩为JPEG
  • 确认Python端OpenCV版本支持JPEG解码,接收数据无截断

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 04:00:56