驴友花雕 发表于 2025-11-6 11:36:18

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



什么是 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 倍。





驴友花雕 发表于 2025-11-6 11:52:50

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

本帖最后由 驴友花雕 于 2025-11-6 12:03 编辑

【花雕动手做】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()



    # 绘制识别结果(框选目标+标注类别)

    for det in results:

      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} {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))。



驴友花雕 发表于 2025-11-6 12:11:17

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

【花雕动手做】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. 可扩展架构
模块化设计便于功能扩展

实验串口返回情况



驴友花雕 发表于 2025-11-6 12:17:20

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

实验场景图






页: [1]
查看完整版本: 【花雕动手做】CanMV K230 AI视觉识别模块之使用照相机