回到首页 返回首页
回到顶部 回到顶部
返回上一页 返回上一页

【花雕动手做】CanMV K230 AI视觉识别模块之使用照相机 简单

头像 驴友花雕 2025.11.06 3 0

00 (2).jpg

 

什么是 CanMV K230?
CanMV K230是一款高性价比的RISC-V边缘AI平台,凭借低功耗、强视觉处理能力和开放的开发生态,成为嵌入式AI开发的理想选择,尤其适合需要快速部署视觉与AI功能的创客、中小企业及教育场景。CanMV 是一套 AI 视觉开发平台,K230 是其核心芯片。该模块结合了图像采集、AI推理、边缘计算等能力,适合嵌入式视觉应用开发。

CanMV:类似 OpenMV 的图像处理框架,支持 Python 编程,简化视觉识别开发流程。
K230 芯片:嘉楠科技推出的 AIoT SoC,采用 RISC-V 架构,内置第三代 KPU(AI加速单元),算力高达 6 TOPS,性能是 K210 的 13.7 倍。

 

00 (4).jpg

【花雕动手做】CanMV K230 AI视觉识别模块之使用照相机

勘智 K230(平头哥旗下)AI 视觉模块的照相机使用,核心是MIPI/USB 摄像头硬件适配 + 驱动配置 + 图像采集(含 AI 推理联动),以下是针对勘智 K230 模块的专属实操指南,覆盖从硬件到编程的完整流程:
一、勘智 K230 摄像头硬件适配
1. 摄像头选型(官方推荐兼容型号)
K230 模块的 CSI 接口和驱动对以下摄像头支持最优,无需额外修改设备树:
MIPI-CSI 摄像头(优先选,低延迟):OV5640(500 万像素)、GC2083(200 万像素)、OV2640(200 万像素),支持 640x480、1280x720 等分辨率。
USB 摄像头(兼容 fallback):免驱动 UVC 摄像头(如罗技 C270),支持 USB 2.0/3.0,适合快速测试。
2. 硬件连接步骤(以 MIPI 摄像头为例)
确认 K230 模块的 CSI 接口(通常标注 “CSI0”“CSI1”),使用 FPC 排线连接摄像头(注意排线金手指朝向,与接口丝印对齐,插紧卡扣)。
摄像头供电:多数兼容型号支持 3.3V 供电,直接从 K230 模块的 3.3V 引脚取电(避免外接电源导致电压不稳)。
若用 USB 摄像头:直接插入 K230 的 USB-A 接口,模块自动识别 UVC 设备。

二、环境与驱动配置(勘智 K230 专属)
1. 系统与 SDK 准备
安装勘智 K230 官方 SDK:从平头哥官网下载 k230_sdk_vx.x,包含定制化 Linux 镜像(Buildroot/Debian)、摄像头驱动、ISP 工具(tioxygen)。
烧录系统:用官方 kflash 工具将镜像写入 SD 卡,插入 K230 模块启动(默认已预装 v4l2-utils ffmpeg 等工具)。
2. 驱动加载与验证
MIPI 摄像头(以 OV5640 为例):
开机后执行 dmesg | grep ov5640,查看驱动是否自动加载(无报错则成功)。
若未加载,手动加载驱动:insmod /lib/modules/$(uname -r)/drivers/media/i2c/ov5640.ko。
USB 摄像头:插入后执行 ls /dev/video*,出现 /dev/video0 或 /dev/video1 即识别成功(内核自带 uvcvideo 驱动)。
验证摄像头能力:v4l2-ctl --device=/dev/video0 --list-formats-ext,查看支持的分辨率、帧率(如 YUYV 格式 640x480@30fps)。

三、基础操作:拍照 / 预览(快速上手)
1. 命令行操作(无需编程)
拍照保存:ffmpeg -f v4l2 -video_size 640x480 -i /dev/video0 -vframes 1 k230_capture.jpg,图像保存到当前目录。
实时预览:需 K230 模块连接 HDMI 显示器,执行 ffplay -f v4l2 -video_size 640x480 -i /dev/video0,屏幕显示实时画面。
连续录制视频:ffmpeg -f v4l2 -video_size 1280x720 -i /dev/video0 -t 10 k230_record.mp4(录制 10 秒 720P 视频)。
2. 编程实现(Python + OpenCV,勘智适配版)
K230 支持 Python 3.8+,需先安装 OpenCV(pip3 install opencv-python),以下代码适配 MIPI/USB 摄像头:
python
运行
import cv2
import time

# 初始化摄像头(/dev/video0 对应第一个摄像头)
cap = cv2.VideoCapture(0)
# 设置分辨率(需与摄像头支持格式匹配)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
cap.set(cv2.CAP_PROP_FPS, 30)  # 帧率30fps

if not cap.isOpened():
   print("勘智K230 摄像头打开失败!")
   exit()

print("摄像头启动成功,按 's' 拍照,按 'q' 退出")

while True:
   ret, frame = cap.read()  # 读取一帧图像
   if not ret:
       print("图像采集失败")
       break

   # 实时显示画面(标注K230标识)
   cv2.putText(frame, "K230 AI Camera", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 2)
   cv2.imshow("K230 Capture", frame)

   # 按键处理
   key = cv2.waitKey(1) & 0xFF
   if key == ord('q'):  # 退出
       break
   elif key == ord('s'):  # 拍照保存
       filename = f"k230_capture_{time.strftime('%Y%m%d%H%M%S')}.jpg"
       cv2.imwrite(filename, frame)
       print(f"照片已保存:{filename}")

# 释放资源
cap.release()
cv2.destroyAllWindows()
四、AI 视觉联动:采集 + 识别(勘智 K230 核心能力)
K230 的 NPU(算力 1TOPS)可直接运行轻量 AI 模型,以下是 “摄像头采集 + 目标检测” 完整示例:
1. 模型准备
下载预训练轻量模型(如 YOLOv8n),用勘智官方工具 bmnetc 转换为 K230 支持的 bmodel 格式(命令:bmnetc --model yolov8n.onnx --output yolov8n_k230.bmodel)。
将转换后的 yolov8n_k230.bmodel 上传到 K230 模块的 /root/models 目录。
2. 采集 + 识别代码(Python)
python
运行
import cv2
import numpy as np
from k230_tengine import TEngine  # 勘智Tengine-Lite适配库

# 初始化摄像头
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)

# 初始化K230 NPU模型(目标检测)
model = TEngine(model_path="/root/models/yolov8n_k230.bmodel")
class_names = ["person", "bicycle", "car", ...]  # YOLOv8n类别列表

while True:
   ret, frame = cap.read()
   if not ret:
       break

   # 图像预处理(适配模型输入:320x320,归一化)
   input_img = cv2.resize(frame, (320, 320))
   input_img = input_img / 255.0
   input_img = np.transpose(input_img, (2, 0, 1)).astype(np.float32)  # CHW格式

   # NPU推理(获取检测结果)
   results = model.infer([input_img])

   # 绘制识别结果(框选目标+标注类别)
   for det in results[0]:
       x1, y1, x2, y2, conf, cls_id = det
       if conf > 0.5:  # 置信度阈值
           # 转换为原图坐标
           x1 = int(x1 * 640 / 320)
           y1 = int(y1 * 480 / 320)
           x2 = int(x2 * 640 / 320)
           y2 = int(y2 * 480 / 320)
           cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
           cv2.putText(frame, f"{class_names[int(cls_id)]} {conf:.2f}", 
                       (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)

   # 显示带AI识别的画面
   cv2.imshow("K230 AI Detection", frame)
   if cv2.waitKey(1) == ord('q'):
       break

cap.release()
cv2.destroyAllWindows()
五、勘智 K230 专属注意事项
ISP 优化:K230 内置 ISP 模块,可通过 tioxygen-tools 调整摄像头参数(如白平衡、曝光),命令:tioxygen-isp -d /dev/video0 -ex auto -wb auto(自动曝光 + 自动白平衡)。
性能调优:AI 推理时,建议将摄像头分辨率设为 640x480(与模型输入缩放比匹配),单帧处理延迟可控制在 50ms 内。
常见问题排查:
摄像头无画面:检查 dmesg | grep video 查看驱动报错,重新插拔 FPC 排线(MIPI)或 USB 接口。
推理卡顿:降低模型输入尺寸(如 320x320),关闭不必要的后台进程。
图像偏色:用 ISP 工具校准白平衡,或在代码中添加色彩校正(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))。

 

46.jpg

【花雕动手做】CanMV K230 AI视觉识别模块之使用照相机
项目测试实验代码

 

代码
#【花雕动手做】CanMV K230 AI视觉识别模块之使用照相机

# 导入必要的系统和操作系统模块
# Import necessary system and OS modules
import uos
import time
from media.sensor import *
from media.display import *
from media.media import *
import ybUtils.YbKey as YbKey  # 导入按键模块

# 定义图片保存路径和文件命名相关变量
# Define variables for image saving path and file naming
save_path = "/data/snapshot/"  # 保存基础路径 Base saving path
prefix = time.ticks_us() % 10000  # 使用时间戳作为文件夹名 Use timestamp as folder name
i = 1  # 照片计数器 Photo counter

# 保存图像原始数据,可使用7yuv预览
# save image raw data, use 7yuv to preview

def ensure_dir(directory):
    """
    递归创建目录
    (Recursively create directory)
    
    功能:确保指定的目录存在,如果不存在则递归创建
    参数:directory - 要创建的目录路径
    """
    # 如果目录为空字符串或根目录,直接返回
    # (If directory is empty string or root directory, return directly)
    if not directory or directory == '/':
        return

    # 处理路径分隔符,确保使用标准格式
    # (Process path separators to ensure standard format)
    directory = directory.rstrip('/')

    try:
        # 尝试获取目录状态,如果目录存在就直接返回
        # (Try to get directory status, if directory exists then return directly)
        os.stat(directory)
        print(f'目录已存在: {directory}')
        # (Directory already exists: {directory})
        return
    except OSError:
        # 目录不存在,需要创建
        # (Directory does not exist, need to create)

        # 分割路径以获取父目录
        # (Split path to get parent directory)
        if '/' in directory:
            parent = directory[:directory.rindex('/')]
            if parent and parent != directory:  # 避免无限递归
                                                # (Avoid infinite recursion)
                ensure_dir(parent)  # 递归创建父目录

        try:
            # 创建目录
            # (Create directory)
            os.mkdir(directory)
            print(f'已创建目录: {directory}')
            # (Directory created: {directory})
        except OSError as e:
            # 可能是并发创建导致的冲突,再次检查目录是否存在
            # (Possible conflict due to concurrent creation, check again if directory exists)
            try:
                os.stat(directory)
                print(f'目录已被其他进程创建: {directory}')
                # (Directory has been created by another process: {directory})
            except:
                # 如果仍然不存在,则确实出错了
                # (If it still doesn't exist, there is definitely an error)
                print(f'创建目录时出错: {e}')
                # (Error creating directory: {e})
    except Exception as e:
        # 捕获其他可能的异常
        # (Catch other possible exceptions)
        print(f'处理目录时出错: {e}')
        # (Error processing directory: {e})

if __name__ == "__main__":
    try:
        # 初始化按键检测
        # Initialize key detection
        key = YbKey.YbKey()

        # 使用默认配置构造传感器对象
        # Construct a Sensor object with default configuration
        sensor = Sensor()
        
        # 重置传感器
        # Reset sensor
        sensor.reset()

        # 设置通道1的输出格式
        # Set channel 1 output format
        sensor.set_framesize(width=640, height=480, chn=CAM_CHN_ID_1)
        sensor.set_pixformat(Sensor.RGB565, chn=CAM_CHN_ID_1)

        # 初始化显示
        # Initialize display
        Display.init(Display.ST7701, width=640, height=480, to_ide=True)

        # 初始化媒体管理器
        # Initialize media manager
        MediaManager.init()
        
        # 启动传感器
        # Start sensor
        sensor.run()

        last_status = False  # 记录上一次按键状态,用于检测按键按下事件
        
        # 主循环
        # Main loop
        while True:
            # 捕获图像
            # Capture image
            img = sensor.snapshot(chn=CAM_CHN_ID_1)
            
            # 创建显示用的图像缓冲
            # Create image buffer for display
            img2 = image.Image(640, 480, image.RGB565)
            img2.clear()
            img2.copy_from(img)  # 将原始图像复制到显示缓冲
            
            # 在图像上绘制信息文本
            # Draw information text on image
            img2.draw_string_advanced(10, 10, 30, "存储目录: " + str(prefix) + ", 照片 " + str(i) + " ", color=(255, 0, 0))
            img2.draw_string_advanced(10, 45, 30, "Save Folder: " + str(prefix) + " , photo: " + str(i) + " ", color=(255, 0, 0))
            
            # 显示带文本的图像
            # Display image with text
            Display.show_image(img2, 0, 0, Display.LAYER_OSD2)

            # 重新初始化按键检测(可能需要刷新状态)
            # Reinitialize key detection (may need to refresh state)
            key = YbKey.YbKey()
            
            # 按键检测和图片保存逻辑
            # Button detection and image saving logic
            if key.is_pressed() == 1:
                if last_status == False:
                    # 检测到按键按下事件(从松开到按下)
                    # Detected key press event (from released to pressed)
                    last_status = True
                    
                    ######################### 保存图片流程 #########################
                    # 确保保存目录存在
                    # Ensure save directory exists
                    ensure_dir(save_path + str(prefix) + "/")

                    # 构建完整文件路径
                    # Build complete file path
                    path = save_path + str(prefix) + "/" + str(i) + ".jpg"
                    i = i + 1  # 递增照片计数器
                    
                    print(path)  # 打印保存路径
                    
                    # 保存图像为JPEG格式
                    # Save image as JPEG format
                    img.save(path)
                    
                    print("已保存至:" + path)
                    time.sleep_ms(1)  # 短暂延时,防止按键抖动
                    #########################
            else:
                # 按键未按下,重置状态
                # Key not pressed, reset state
                last_status = False
                
    except KeyboardInterrupt as e:
        # 处理用户中断(Ctrl+C)
        # Handle user interrupt (Ctrl+C)
        print(f"用户停止程序 User stopped the program")
    except BaseException as e:
        # 处理其他所有异常
        # Handle all other exceptions
        print(f"发生异常 Exception occurred: '{e}'")
    finally:
        # 清理资源和退出程序
        # Clean up resources and exit program

        # 停止传感器
        # Stop sensor
        if isinstance(sensor, Sensor):
            sensor.stop()
            
        # 反初始化显示
        # Deinitialize display
        Display.deinit()

        # 启用睡眠模式
        # Enable sleep mode
        os.exitpoint(os.EXITPOINT_ENABLE_SLEEP)
        time.sleep_ms(100)  # 等待资源完全释放

        # 释放媒体缓冲
        # Release media buffer
        MediaManager.deinit()

代码解读:

程序总体功能
这是一个完整的数码相机应用,使用CanMV K230模块实现实时预览、按键拍照和照片存储功能。

系统架构分析
核心组件
text
摄像头传感器 → 图像处理 → 显示输出 → 按键检测 → 文件存储

1. 初始化阶段
python
# 硬件初始化流程
sensor = Sensor()                    # 创建摄像头对象
sensor.reset()                       # 重置摄像头
sensor.set_framesize(640, 480)      # 设置分辨率
sensor.set_pixformat(Sensor.RGB565) # 设置图像格式
Display.init(...)                    # 初始化显示
MediaManager.init()                  # 初始化媒体管理
sensor.run()                         # 启动摄像头

2. 主循环工作流程
text
捕获图像 → 添加文字水印 → 显示预览 → 检测按键 → 保存照片

关键技术实现
1. 智能目录管理
python
def ensure_dir(directory):
   # 递归创建目录结构
   # 示例:/data/snapshot/1234/1.jpg
递归创建:自动创建多级目录
容错处理:处理目录已存在、并发创建等边界情况
路径安全:规范化路径分隔符

2. 照片命名策略
python
prefix = time.ticks_us() % 10000  # 时间戳作为文件夹名
i = 1                             # 照片计数器
path = f"/data/snapshot/{prefix}/{i}.jpg"
唯一性:使用微秒时间戳避免冲突
组织性:每次运行创建独立文件夹
顺序性:自动递增照片编号

3. 按键检测机制
python
if key.is_pressed() == 1:
   if last_status == False:  # 边缘检测
       # 执行拍照
       last_status = True
边缘触发:只在按键按下瞬间触发
防抖动:避免重复触发
状态管理:跟踪按键状态变化

4. 用户界面设计
python
img2.draw_string_advanced(10, 10, 30, "存储目录: " + str(prefix) + ", 照片 " + str(i) + " ", color=(255, 0, 0))
实时状态显示:显示当前目录和照片计数
视觉反馈:红色文字突出重要信息
双语言支持:中英文提示信息

性能优化特性
1. 图像处理优化
python
img2 = image.Image(640, 480, image.RGB565)
img2.copy_from(img)
双缓冲机制:原始图像+显示图像分离
格式一致:使用RGB565节省内存
高效复制:直接内存拷贝避免格式转换

2. 资源管理
分层初始化:确保硬件正确初始化顺序
异常安全:完整的try-except-finally保护
彻底清理:程序退出时释放所有资源

错误处理机制
三级保护策略
python
try:
   # 主程序逻辑
except KeyboardInterrupt:    # 用户主动中断
   print("用户停止程序")
except BaseException:        # 其他所有异常
   print(f"发生异常: '{e}'")
finally:                     # 强制清理
   # 释放所有资源
具体清理步骤
停止传感器:sensor.stop()
关闭显示:Display.deinit()
释放媒体资源:MediaManager.deinit()
系统睡眠:os.exitpoint(os.EXITPOINT_ENABLE_SLEEP)

文件存储结构
生成的目录结构
text
/data/snapshot/
├── 8341/          # 时间戳命名的文件夹
│   ├── 1.jpg
│   ├── 2.jpg
│   └── 3.jpg
└── 9267/          # 另一次运行的文件夹
   ├── 1.jpg
   └── 2.jpg

用户体验特性
1. 实时反馈
视觉反馈:画面显示保存状态
控制台输出:打印保存路径和确认信息
计数显示:实时显示已拍照片数量

2. 操作便捷性
一键拍照:简单按键操作
自动管理:无需手动创建目录
防误操作:按键防抖动处理

技术亮点
1. 完整的相机功能链
从图像采集、处理、显示到存储的完整流程
2. 工业级错误处理
完善的异常处理和资源管理
3. 用户友好设计
直观的状态显示和操作反馈
4. 可扩展架构
模块化设计便于功能扩展

 

实验串口返回情况

 

47.jpg

实验场景图 

 

48 (1).jpg
48 (2).jpg
48 (3).jpg

评论

user-avatar
icon 他的勋章
    展开更多