Camera 摄像头
1.实验目的
学习K230上如何使用摄像头。
2.实验硬件
目前我们提供的摄像头为GC2093,镜头参数为:
- 感光元件:GC2093
 - 最大分辨率:1920x1080
 - 最大帧率:60fps
 - 感光尺寸:1/2.9
 
机械尺寸及引脚定义:

3.实验原理
板子上提供了三路摄像头接口,其中CSI2为22p-0.5mm的FPC接口,CSI0和CSI1为二合一接口,这一小节我们先讲解CSI2的使用。
在DshanPI-K230-CanMV开发板中,Sensor模块的主要作用是负责获取图像数据。这个模块将光信号转化为数字信号,供后续图像处理算法使用。
K230的Sensor模块API提供了对这些硬件的底层控制,模块负责图像采集与数据处理。该模块提供了一套高级 API,开发者可以利用这些接口轻松获取不同格式与尺寸的图像,而无需了解底层硬件的具体实现。其架构如下图所示:

K230 平台支持最多 三路图像传感器(Sensor 0/1/2) 同时接入,每个传感器可独立完成图像采集与信号转换。这些传感器可安装于不同位置,用于捕捉不同视角或区域的图像,例如:
- 自动驾驶场景:车前、车后及车内的三路摄像头;
 - 安防监控:覆盖多个区域;
 - 工业检测 :多面或多角度图像获取。
 
每个图像传感器可灵活映射到任一 Camera Device(Camera Device 0/1/2)。Camera Device 是图像数据处理的核心单元,负责完成图像采集、格式转换及预处理等操作。Sensor 与 Camera Device 之间支持多对多映射,提供更高的配置灵活性。
每个 Camera Device 提供 三路输出通道(Output Channel 0/1/2),支持并行输出不同格式、分辨率的图像数据,可分别用于:
- 实时显示;
 - AI 算法推理;
 - 视频存储或回放。
 
K230 也支持通过 CSI0+CSI1 合并为一个 4-lane 接口,从而连接高分辨率摄像头,扩展单路带宽以满足更高质量图像需求。
4.代码解析
导入模块
import time, os, sys
from media.sensor import *
from media.display import *
from media.media import *
引入 time, os, sys 是为了时间控制、系统接口等通用功能;
media.sensor, media.display, media.media 是K230平台自带的摄像头、显示和媒体资源管理模块。
保存图像函数
def save_img(img, chn):
    if img.format() == image.YUV420:
        suffix = "yuv420sp"
    elif img.format() == image.RGB888:
        suffix = "rgb888"
    elif img.format() == image.RGBP888:
        suffix = "rgb888p"
    else:
        suffix = "unkown"
判断图像格式,选择合适的文件后缀名。
    filename = f"/sdcard/camera_chn_{chn:02d}_{img.width()}x{img.height()}.{suffix}"
    print("save capture image to file:", filename)
    img.save(filename)
构造图像文件保存路径,例如 /sdcard/camera_chn_00_1920x1080.yuv420sp
使用 img.save() 将图像原始数据保存到 SD 卡中。
摄像头初始化和配置
try:
    print("camera_test")
    sensor = Sensor()           # 创建 Sensor 对象,默认使用 sensor0
    sensor.reset()              # 重置sensor,加载默认配置
设置默认通道输出参数
    sensor.set_framesize(Sensor.FHD)        # 设置分辨率为 Full HD 1920x1080
    sensor.set_pixformat(Sensor.YUV420SP)   # 设置输出图像格式为 YUV420SP
摄像头通道绑定到显示层
    bind_info = sensor.bind_info()
    Display.bind_layer(**bind_info, layer = Display.LAYER_VIDEO1)
bind_info() 获取绑定信息(比如buffer、宽高等)
Display.bind_layer() 将通道0图像显示到视频层 VIDEO1(通常用于 HDMI 显示)
设置通道的输出格式
    sensor.set_framesize(width = 640, height = 480, chn = CAM_CHN_ID_1)
    sensor.set_pixformat(Sensor.RGB888, chn = CAM_CHN_ID_1)
    sensor.set_framesize(width = 640, height = 480, chn = CAM_CHN_ID_2)
    sensor.set_pixformat(Sensor.RGBP888, chn = CAM_CHN_ID_2)
配置通道1和通道2为 VGA 分辨率 (640x480),格式分别为 RGB888 和 RGB888P(Planar)。
初始化显示模块+媒体管理器
    Display.init(Display.VIRT, sensor.width(), sensor.height(), to_ide = True)
    MediaManager.init()
显示初始化,设置显示区域为虚拟显示(VIRT)并输出到 IDE(一般是 HDMI 屏幕);
媒体管理器初始化,申请内部 buffer,做视频资源统一管理。
启动摄像头采集
    sensor.run()  # 开始运行摄像头
丢弃前100帧图像
    for i in range(100):
        sensor.snapshot()
用 于预热、去抖。
抓拍图像并保存到SD卡
    img = sensor.snapshot(chn = CAM_CHN_ID_0)
    save_img(img, 0)
    img = sensor.snapshot(chn = CAM_CHN_ID_1)
    save_img(img, 1)
    img = sensor.snapshot(chn = CAM_CHN_ID_2)
    save_img(img, 2)
分别从三个通道抓拍图像并保存。
异常处理
except KeyboardInterrupt as e:
    print(f"user stop")
except BaseException as e:
    print(f"Exception '{e}'")
捕捉中断(Ctrl+C)和其他异常,避免程序崩溃。
资源清理
finally:
    if isinstance(sensor, Sensor):
        sensor.stop()               # 停止摄像头
    Display.deinit()                # 关闭显示
    os.exitpoint(os.EXITPOINT_ENABLE_SLEEP)  # 允许休眠
    time.sleep_ms(100)             # 延迟一会儿,等待硬件稳定
    MediaManager.deinit()          # 释放媒体资源
确保资源被正确释放,避免系统崩溃或下次初始化失败。
5.示例代码
下面的代码保存摄像头图像数据:
'''
本程序遵循GPL V3协议, 请遵循协议
实验平台: DshanPI CanMV
开发板文档站点	: https://eai.100ask.net/
百问网学习平台   : https://www.100ask.net
百问网官方B站    : https://space.bilibili.com/275908810
百问网官方淘宝   : https://100ask.taobao.com
'''
import time, os, sys
from media.sensor import *
from media.display import *
from media.media import *
# save image raw data, use 7yuv to preview
def save_img(img, chn):
    if img.format() == image.YUV420:
        suffix = "yuv420sp"
    elif img.format() == image.RGB888:
        suffix = "rgb888"
    elif img.format() == image.RGBP888:
        suffix = "rgb888p"
    else:
        suffix = "unkown"
    filename = f"/sdcard/camera_chn_{chn:02d}_{img.width()}x{img.height()}.{suffix}"
    print("save capture image to file:", filename)
    img.save(filename)
try:
    print("camera_test")
    # construct a Sensor object with default configure
    sensor = Sensor()
    # sensor reset
    sensor.reset()
    # set hmirror
    # sensor.set_hmirror(False)
    # sensor vflip
    # sensor.set_vflip(False)
    # set chn0 output size, 1920x1080
    sensor.set_framesize(Sensor.FHD)
    # set chn0 output format
    sensor.set_pixformat(Sensor.YUV420SP)
    # bind sensor chn0 to display layer video 1
    bind_info = sensor.bind_info()
    Display.bind_layer(**bind_info, layer = Display.LAYER_VIDEO1)
    # set chn1 output format
    sensor.set_framesize(width = 640, height = 480, chn = CAM_CHN_ID_1)
    sensor.set_pixformat(Sensor.RGB888, chn = CAM_CHN_ID_1)
    # set chn2 output format
    sensor.set_framesize(width = 640, height = 480, chn = CAM_CHN_ID_2)
    sensor.set_pixformat(Sensor.RGBP888, chn = CAM_CHN_ID_2)
    # use hdmi as display output
    Display.init(Display.VIRT, sensor.width(), sensor.height(), to_ide = True)
    # init media manager
    MediaManager.init()
    # sensor start run
    sensor.run()
    # drop 100 frames
    for i in range(100):
        sensor.snapshot()
    # snapshot and save
    img = sensor.snapshot(chn = CAM_CHN_ID_0)
    save_img(img, 0)
    img = sensor.snapshot(chn = CAM_CHN_ID_1)
    save_img(img, 1)
    img = sensor.snapshot(chn = CAM_CHN_ID_2)
    save_img(img, 2)
except KeyboardInterrupt as e:
    print(f"user stop")
except BaseException as e:
    print(f"Exception '{e}'")
finally:
    # sensor stop run
    if isinstance(sensor, Sensor):
        sensor.stop()
    # deinit display
    Display.deinit()
    os.exitpoint(os.EXITPOINT_ENABLE_SLEEP)
    time.sleep_ms(100)
    # release media buffer
    MediaManager.deinit()
6.实验结果
在开发板上电前,提前连接好摄像头:

连接后上电运行上述示例代码后,会调用摄像头,拍摄图片,拍摄完成后保存的sdcard目录下:
