图像基础与MaixPy图像处理:图像格式、色彩空间、图像采集与显示的基本API使用
在K210物体识别项目中,图像是一切视觉算法的输入源头。无论是后续的图像分类、目标检测,还是简单的颜色识别,都依赖于对图像数据的正确理解与高效处理。MaixPy作为K210芯片的官方MicroPython固件,封装了底层复杂的硬件加速单元(KPU、FPIOA、DMA等),为开发者提供了简洁而强大的图像处理接口。本章将系统梳理图像在数字世界中的表示方式、MaixPy中图像对象的本质,以及如何通过标准API完成图像的采集与显示,为后续的物体识别模型部署打下坚实基础。
图像格式与色彩空间:从像素到数据
数字图像的本质是一个二维矩阵,每个元素称为像素(Pixel)。对于灰度图,每个像素用一个字节(0-255)表示亮度;对于彩色图,则需要多个通道来描述颜色信息。K210的摄像头传感器(如OV2640、OV5640)输出原始Bayer格式数据,经过ISP(图像信号处理器)处理后,通常转换为RGB565、RGB888或YUV422等格式。MaixPy中,sensor.set_pixformat()函数用于设置采集格式,最常用的是sensor.RGB565和sensor.GRAYSCALE。
| 格式 | 位深 | 内存占用(320x240) | 适用场景 | MaixPy常量 |
|---|---|---|---|---|
| RGB565 | 16bit/像素 | 150KB | 彩色显示、颜色识别 | sensor.RGB565 |
| GRAYSCALE | 8bit/像素 | 75KB | 灰度识别、边缘检测 | sensor.GRAYSCALE |
| JPEG | 压缩格式 | 约20-50KB | 网络传输、存储 | sensor.JPEG |
| RGB888 | 24bit/像素 | 225KB | 高精度颜色处理 | sensor.RGB888 |
色彩空间是颜色在数学上的表示方法。RGB空间适合显示设备,但亮度与色度耦合,不利于图像分析。YUV空间将亮度(Y)与色度(U/V)分离,YUV422格式在K210上常用于人脸检测等算法,因为KPU硬件加速器对YUV数据有优化。MaixPy内部会自动处理格式转换,但开发者需要理解:RGB565是K210显示和存储的默认格式,而YUV422是KPU推理的常用输入格式。当调用img.to_yuv422()时,MaixPy会执行像素格式转换,这个过程会消耗CPU周期,应避免在循环中频繁转换。
kpu模块内部会自动处理格式对齐,但若手动预处理,务必确认输入张量的shape和dtype与模型要求一致。
图像采集API:sensor模块的核心用法
MaixPy的sensor模块封装了摄像头驱动,其初始化流程遵循固定顺序:复位→设置像素格式→设置帧大小→设置帧率→关闭白平衡/曝光补偿→启动。以下是一个标准初始化代码片段:
import sensor, lcd
# 初始化摄像头
sensor.reset() # 复位摄像头
sensor.set_pixformat(sensor.RGB565) # 设置RGB565格式
sensor.set_framesize(sensor.QVGA) # 设置320x240分辨率
sensor.set_vflip(1) # 垂直翻转(根据安装方向调整)
sensor.set_hmirror(1) # 水平镜像
sensor.set_auto_whitebal(False) # 关闭自动白平衡(固定色温)
sensor.set_auto_exposure(False) # 关闭自动曝光(固定亮度)
sensor.skip_frames(30) # 跳过30帧,等待传感器稳定
# 初始化LCD
lcd.init(freq=15000000) # 初始化LCD,SPI频率15MHz
lcd.rotation(2) # 旋转屏幕方向
sensor.snapshot()是采集一帧图像的核心函数,返回一个Image对象。该函数会阻塞当前线程直到一帧数据准备就绪,默认超时时间为100ms。在实时识别场景中,应确保主循环的帧处理时间小于帧间隔(例如30fps时帧间隔约33ms),否则实际帧率会下降。MaixPy还提供了sensor.run(1)和sensor.stop()来控制采集的启停,但通常不需要手动调用。
图像显示API:LCD与FrameBuffer
K210开发板通常配备2.4英寸或2.8英寸SPI接口LCD屏,分辨率多为320x240。MaixPy的lcd模块提供了简洁的显示接口。lcd.display(img)接受一个Image对象或RGB565字节数组,将其直接刷新到屏幕。该函数内部通过DMA传输数据,不会阻塞CPU,但若连续调用,需要确保上一帧传输完成,否则可能造成撕裂。MaixPy的lcd.display()会自动处理等待,因此开发者无需关心同步问题。
除了直接显示图像,LCD还支持绘制基本图形:lcd.draw_line()、lcd.draw_rect()、lcd.draw_string()等。这些函数直接操作LCD的FrameBuffer,适合在图像上叠加文字或框选目标。但注意,这些绘制操作是覆盖式的,不会修改原始Image对象。若需要在图像数据上绘制(例如保存带标注的图片),应使用img.draw_*()系列方法。
img.draw_*()修改的是图像数据本身,后续保存或送入模型时会包含绘制内容;lcd.draw_*()仅修改屏幕显示,不影响图像数据。在物体识别项目中,推荐使用img.draw_*()绘制检测框和标签,然后一次性lcd.display(img),这样既能显示结果,又能保存带标注的图像。
案例拆解:实时显示摄像头画面并叠加帧率信息
下面通过一个完整的案例,演示图像采集、处理、显示的全流程。该案例实现了一个实时视频预览器,在画面左上角显示当前帧率(FPS),并在底部显示图像格式和分辨率信息。
import sensor, lcd, time, gc
# 初始化
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.set_vflip(1)
sensor.set_hmirror(1)
sensor.skip_frames(30)
lcd.init(freq=15000000)
lcd.rotation(2)
# 帧率计算变量
clock = time.clock()
frame_count = 0
start_time = time.ticks_ms()
fps = 0
while True:
clock.tick()
img = sensor.snapshot() # 采集一帧
# 每30帧更新一次FPS
frame_count += 1
if frame_count % 30 == 0:
elapsed = time.ticks_diff(time.ticks_ms(), start_time)
fps = 30000 // elapsed # 计算FPS
start_time = time.ticks_ms()
# 在图像上绘制信息
img.draw_string(5, 5, "FPS: %d" % fps, color=(255, 255, 0), scale=2)
img.draw_string(5, 30, "Format: RGB565", color=(0, 255, 0), scale=1)
img.draw_string(5, 45, "Size: %dx%d" % (img.width(), img.height()), color=(0, 255, 0), scale=1)
# 显示到LCD
lcd.display(img)
# 内存管理
gc.collect()
该案例的关键点在于:帧率计算采用滑动窗口法,每30帧计算一次平均FPS,避免单帧波动导致显示跳动。img.draw_string()的color参数使用RGB888元组,但内部会转换为RGB565存储。在长时间运行时,gc.collect()可以回收碎片内存,防止内存泄漏导致系统崩溃。
gc.collect()定期回收;2) 避免在循环中创建大对象;3) 必要时降低分辨率到QQVGA(160x120)或使用GRAYSCALE格式。
常见误区与总结复盘
误区一:混淆图像格式与显示格式。很多初学者认为sensor.set_pixformat(sensor.JPEG)后,LCD显示会更流畅。实际上,JPEG格式需要解码才能显示,反而增加CPU负担。LCD显示原生支持RGB565,因此采集RGB565是最佳选择。
误区二:忽略摄像头方向校准。K210开发板的摄像头安装方向不统一,set_vflip和set_hmirror必须根据实际安装调整。若画面上下颠倒或左右镜像,物体识别模型的输入方向错误,会导致识别准确率大幅下降。
误区三:在循环中频繁创建Image对象。sensor.snapshot()返回的Image对象是复用的,但若手动img.copy()或img.resize(),会创建新对象。这些操作应在初始化时完成,循环中应复用预分配的对象。
误区四:忽视LCD刷新率限制。虽然K210的SPI LCD标称支持30fps,但实际刷新率受SPI时钟和DMA带宽影响。若发现画面闪烁或撕裂,可尝试降低lcd.init(freq=)的频率,或使用lcd.display(img, raw=True)跳过格式检查。