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

如何用Rust读取并转换Arduino UNO的OV7670摄像头YVYU输出?

Arduino OV7670摄像头图像解码问题

背景

我在Arduino UNO项目中使用OV7670摄像头,采用对应代码可正常运行,通过Windows平台的Serial Port Reader工具能查看摄像头图像。现在尝试用Rust重写该工具适配Mac,已成功连接并读取串口输出,但无法将原始字节转换为有效图像。

已知信息

  • 摄像头通过captureImg函数中的UDR0 = (PINC & 15) | (PIND & 240);语句输出原始像素;
  • 像素格式为YUV422,对应配置const struct regval_list yuv422_ov7670;
  • 字节顺序为Y V Y U,由yuv422_ov7670中的{ REG_COM13, COM13_UVSAT }配置,该信息来自OV7670 datasheet第18页。

疑问点

1. 输出字节数为何不固定?

  • 摄像头发送像素前会先发送*RDY*标识,用于确定解码起始点;
  • 代码调用captureImg(320, 240),应输出320×240=76800个像素,但两次*RDY*间的字节数在79000-79500之间。

2. 如何将YUV422 YVYU格式字节转换为RGB?

  • 搜索得知两种编码方式:2像素用4字节Y0 V Y1 U编码,或1像素用2字节(Y+高4位V+低4位U)编码;
  • 找到yuv crate可实现转换,但需指定MatrixCoefficients,不清楚yuv422_ov7670中的对应系数。

当前代码问题

以下是我编写的解码保存函数,通过image crate将每4字节转换为2个RGB像素,但因字节数不足导致图像一半为黑,且最终生成的图像与原工具生成的正常图像差异极大。

fn decode_and_save(data: &[u8]) {
    let mut imgbuf = image::ImageBuffer::new(IMAGE_WIDTH, IMAGE_HEIGHT);
    let mut ix = IMAGE_WIDTH;
    let mut iy = IMAGE_HEIGHT;
    let converter = RGBConvert::<u8>::new(Range::Full, MatrixCoefficients::BT709).unwrap();
    for ch in data.chunks_exact(4) {
        let yuv1 = YUV {
            y: ch[0],
            v: ch[1],
            u: ch[3],
        };
        let yuv2 = YUV {
            y: ch[2],
            v: ch[1],
            u: ch[3],
        };

        let rgb1 = converter.to_rgb(yuv1);
        let rgb2 = converter.to_rgb(yuv2);

        imgbuf.put_pixel(ix - 1, iy - 1, Rgb([rgb1.r, rgb1.g, rgb1.b]));
        imgbuf.put_pixel(ix - 2, iy - 1, Rgb([rgb2.r, rgb2.g, rgb2.b]));
        ix -= 2;

        if ix == 0 {
            ix = IMAGE_WIDTH;
            iy -= 1;
        }
        if iy == 0 {
            break;
        }
    }
    imgbuf.save("./test.png").unwrap();
}

问题解答

关于字节数不固定的问题

Arduino UNO的串口在高速传输摄像头数据时,易因硬件中断优先级、IO延迟出现传输丢包、帧错误或额外填充字节。另外你混淆了像素数与字节数:YUV422 YVYU格式下,320×240像素需要320*240*2=153600字节(每2像素占4字节),当前读取的79000-79500字节远不足有效数据量。

解决方法:找到*RDY*标识后,跳过标识本身的5字节,持续读取直到获取153600字节,而非依赖两次*RDY*的间隔。

关于YUV转RGB的正确方式

OV7670默认使用BT.601转换系数(你代码中误用了BT709),这是色彩差异的核心原因。同时你的像素写入顺序完全错误:摄像头是逐行从左到右、从上到下输出像素,而你从图像右下角开始写入,导致画面颠倒混乱。

修正后的解码代码

use image::{ImageBuffer, Rgb};
use yuv::{RGBConvert, Range, MatrixCoefficients, YUV};

const IMAGE_WIDTH: u32 = 320;
const IMAGE_HEIGHT: u32 = 240;
const TOTAL_BYTES: usize = (IMAGE_WIDTH * IMAGE_HEIGHT * 2) as usize; // YVYU格式下2字节/像素

fn decode_and_save(raw_data: &[u8]) {
    // 定位*RDY*标识,跳过标识获取有效数据起始位置
    let rdy_mark = b"*RDY*";
    let start_idx = raw_data.windows(rdy_mark.len())
        .position(|window| window == rdy_mark)
        .map(|idx| idx + rdy_mark.len())
        .unwrap_or(0);
    
    // 截取固定长度的有效数据,避免多余字节干扰
    let valid_data = &raw_data[start_idx..start_idx + TOTAL_BYTES.min(raw_data.len() - start_idx)];
    
    let mut imgbuf = ImageBuffer::new(IMAGE_WIDTH, IMAGE_HEIGHT);
    // 使用OV7670默认的BT.601转换系数
    let converter = RGBConvert::<u8>::new(Range::Full, MatrixCoefficients::BT601).unwrap();
    
    let mut pixel_idx = 0;
    // 逐4字节处理,对应2个连续像素
    for chunk in valid_data.chunks_exact(4) {
        let (y0, v, y1, u) = (chunk[0], chunk[1], chunk[2], chunk[3]);
        
        // 转换并写入第一个像素(左)
        let rgb1 = converter.to_rgb(YUV { y: y0, u, v });
        let x = (pixel_idx % IMAGE_WIDTH as usize) as u32;
        let y = (pixel_idx / IMAGE_WIDTH as usize) as u32;
        imgbuf.put_pixel(x, y, Rgb([rgb1.r, rgb1.g, rgb1.b]));
        pixel_idx += 1;
        
        // 转换并写入第二个像素(右)
        let rgb2 = converter.to_rgb(YUV { y: y1, u, v });
        let x = (pixel_idx % IMAGE_WIDTH as usize) as u32;
        let y = (pixel_idx / IMAGE_WIDTH as usize) as u32;
        imgbuf.put_pixel(x, y, Rgb([rgb2.r, rgb2.g, rgb2.b]));
        pixel_idx += 1;
    }
    
    imgbuf.save("./test.png").unwrap();
}

额外注意事项

  • 确保串口波特率与Arduino代码完全一致(通常为115200);
  • Mac下串口读取需禁用RTS/CTS流控,避免传输中断;
  • 若仍有丢包,可在Arduino代码中增大串口缓冲区,或降低摄像头输出分辨率测试。

内容的提问来源于stack exchange,提问作者Виталий Свинчак

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 15:39:56