在智能硬件和 AI 视觉项目中,颜色识别是非常经典的入门实验。很多看起来复杂的视觉应用,例如物料分拣、颜色标记追踪、机器人巡检、色块定位、教学演示和视觉反馈,本质上都可以从"摄像头采集画面,程序判断颜色区域,再把识别结果显示出来"这个流程开始理解。
本实验使用 CanMV K210 摄像头采集图像,通过 LAB 颜色阈值识别红色、绿色和蓝色色块。程序会在 LCD 屏幕上实时显示摄像头画面,并对识别到的色块绘制矩形框、中心十字和颜色名称,同时在画面左上角显示 FPS 和识别数量。串口端会按照固定帧间隔打印识别结果,避免持续刷屏影响调试观察。
实验运行后,LCD 上可以直接看到摄像头画面、颜色识别框、中心点标记和运行状态信息。下面的效果图可以先建立整体印象,后续代码中的 find_blobs()、draw_rectangle()、draw_cross() 和 lcd.display() 都会围绕这个显示效果展开。

| 学习目标 | 说明 |
|---|---|
| 理解视觉识别流程 | 掌握摄像头采集、颜色阈值判断、结果绘制和 LCD 显示之间的关系 |
| 掌握 LAB 阈值识别 | 使用 LAB 颜色范围识别红、绿、蓝三种色块 |
| 理解色块对象 | 认识 blob 中的矩形、中心点、颜色编码和坐标信息 |
| 学会结果可视化 | 在图像上绘制识别框、中心十字、颜色名称、FPS 和识别数量 |
| 建立视觉扩展思路 | 为后续颜色分拣、色块追踪、云台控制和 AI 视觉实验打基础 |
文章目录
理论基础
颜色识别实验的核心是把摄像头画面中的颜色区域转换成程序可以处理的数据。摄像头负责采集真实画面,程序根据预设颜色阈值查找目标区域,找到目标后再把识别结果绘制到图像上,最后通过 LCD 显示出来。这个过程不是单纯显示摄像头画面,而是在采集和显示之间增加了"图像分析"和"结果标注"。
本实验使用 LAB 颜色阈值进行识别。相比直接使用 RGB 判断颜色,LAB 空间更适合做颜色分割实验,因为它可以把亮度和颜色分量分开描述。代码中的阈值格式为 (L Min, L Max, A Min, A Max, B Min, B Max),分别限制亮度、红绿方向和蓝黄方向的取值范围。只要图像中某个区域的颜色落在对应阈值范围内,find_blobs() 就可能把它识别为目标色块。
红、绿、蓝三种颜色被统一放在 COLOR_CONFIGS 中管理。每种颜色都有名称、编码、阈值和绘制颜色。红色编码为 1,绿色编码为 2,蓝色编码为 4,这些数值采用二进制位设计,便于在合并色块时判断一个 blob 是否包含多个颜色。比如某个识别结果同时包含红色和绿色编码时,程序就可以通过按位与运算解析出混合结果。
下面这张流程图从视觉数据链路理解本实验。摄像头采集真实画面,程序根据 LAB 阈值查找色块,再把检测结果绘制到图像上,最后交给 LCD 显示。串口输出只作为辅助调试手段,不参与图像显示链路。
真实场景
红色 / 绿色 / 蓝色色块
摄像头采集
sensor.snapshot()
图像帧
RGB565 / QVGA
LAB 阈值检测
img.find_blobs()
色块对象 blob
位置 / 中心点 / 颜色编码
结果绘制
矩形框 / 十字 / 文字
LCD 显示
lcd.display(img)
串口调试
定时打印识别结果
颜色配置
COLOR_CONFIGS
运行状态
FPS / Count
从这条链路可以看出,颜色识别不是孤立的一行函数,而是由采集、阈值、筛选、绘制、显示和调试输出共同组成。摄像头提供图像数据,阈值负责定义"什么颜色算目标",blob 保存识别结果,绘图函数把结果变成可观察标记,LCD 负责把最终画面显示出来。
颜色识别对环境光比较敏感。相同颜色物体在强光、弱光、背光、阴影和不同白平衡条件下,实际采集到的 LAB 数值可能不同。因此代码中尝试关闭自动增益和自动白平衡,目的是减少摄像头自动调整导致的颜色漂移,让阈值判断更加稳定。实际使用时,仍然需要根据现场光照和目标颜色调整 COLOR_CONFIGS 中的阈值。
硬件设施
本实验围绕 CanMV K210 的摄像头图像采集、颜色阈值识别和 LCD 显示展开。代码中实际使用到 sensor、image、time 和 lcd 模块,没有使用 GPIO、按键、蜂鸣器、电机或网络通信模块,因此硬件重点集中在摄像头、LCD、开发板运行环境和图像处理流程上。
| 硬件 / 软件 | 作用 | 说明 |
|---|---|---|
| CanMV K210 开发板 | 实验运行平台 | 负责运行 MicroPython 程序,完成图像采集、颜色识别和结果显示 |
| 摄像头模块 | 图像采集设备 | 通过 sensor.snapshot() 获取实时画面,为颜色检测提供图像数据 |
| LCD 屏幕 | 结果显示设备 | 通过 lcd.display(img) 显示处理后的图像画面 |
sensor 模块 |
摄像头控制模块 | 用于初始化摄像头、设置图像格式、分辨率和采集参数 |
image 模块 |
图像处理模块 | 提供颜色识别、绘制矩形、绘制十字和绘制文字等图像操作能力 |
time 模块 |
帧率统计模块 | 通过 time.clock() 统计程序运行帧率 |
lcd 模块 |
屏幕显示模块 | 用于初始化 LCD,并把处理后的图像显示到屏幕上 |
当前代码没有出现具体的 GPIO 引脚注册,也没有手动配置摄像头和 LCD 的物理引脚。摄像头由 sensor 模块统一管理,LCD 由 lcd 模块统一管理,程序层面不需要直接操作底层引脚。实验检查时重点不在排针接线,而在摄像头是否能正常采集画面、LCD 是否能正常显示、阈值是否匹配当前光照环境。
| 硬件模块 / 数据对象 | 代码对象 | 作用 | 说明 |
|---|---|---|---|
| 摄像头模块 | sensor |
图像采集入口 | 负责采集 RGB565 图像,并提供给后续颜色识别流程 |
| LCD 屏幕 | lcd |
显示输出设备 | 负责显示绘制了识别框、文字和状态信息的图像 |
| 图像帧 | img |
单帧图像对象 | 来自 sensor.snapshot(),每一帧都会参与识别和显示 |
| 色块对象 | blob |
识别结果对象 | 保存色块位置、中心点、外接矩形和颜色编码信息 |
| 颜色配置 | COLOR_CONFIGS |
阈值配置表 | 保存红、绿、蓝三种颜色的 LAB 阈值、编码和绘制颜色 |
| 阈值列表 | THRESHOLDS |
检测参数 | 从 COLOR_CONFIGS 中提取后传入 find_blobs() |
| 帧率统计 | clock |
运行状态统计 | 用于计算 FPS,观察程序运行速度 |
摄像头可以理解成程序观察真实世界的入口,LCD 则是程序反馈识别结果的出口。sensor.snapshot() 每执行一次,就会采集一帧图像;img.find_blobs() 会根据颜色阈值从图像中找出目标色块;lcd.display(img) 把处理后的图像显示到屏幕上。整个过程不是单纯显示摄像头画面,而是在显示之前增加了颜色判断、图形标注和状态统计。
| 实验现象 | 正常表现 | 异常提示 |
|---|---|---|
| 程序启动 | LCD 显示摄像头画面 | 如果没有画面,检查摄像头、LCD 和初始化函数是否正常执行 |
| 放入红色色块 | 屏幕中红色区域出现矩形框和中心十字 | 如果无法识别,调整红色 LAB 阈值 |
| 放入绿色色块 | 屏幕中绿色区域被框选 | 如果背景也被识别,说明绿色阈值范围可能过宽 |
| 放入蓝色色块 | 屏幕中蓝色区域被框选 | 如果识别不稳定,检查光照和白平衡设置 |
| 多个色块同时出现 | 屏幕显示多个识别框,左上角数量增加 | 如果相邻色块被合并,检查 MERGE_BLOBS 设置 |
| 画面左上角 | 显示 FPS 和识别数量 | FPS 偏低时可减少绘制内容或优化识别区域 |
| 串口输出 | 每隔固定帧数打印识别结果 | 输出间隔由 PRINT_INTERVAL 控制 |
软件代码
本实验代码围绕颜色识别流程展开,整体可以分成颜色配置、参数配置、摄像头初始化、LCD 初始化、颜色解析、结果绘制和主循环识别几个部分。程序使用 COLOR_CONFIGS 统一管理红、绿、蓝三种颜色的阈值和绘制颜色,后续识别、标注和结果打印都围绕这份配置展开。
| 软件环境 | 作用 | 检查重点 |
|---|---|---|
| CanMV IDE | 编辑、运行和调试 K210 程序 | 能识别开发板串口,并能运行基础摄像头示例 |
| CanMV 固件 | 提供 sensor、image、lcd 等模块 |
固件环境需要支持摄像头采集、图像处理和 LCD 显示 |
| 摄像头驱动环境 | 获取实时图像 | sensor.snapshot() 能正常返回图像帧 |
| LCD 显示环境 | 显示处理结果 | lcd.display(img) 能正常刷新画面 |
| 串口终端 | 查看调试输出 | 能定时看到识别结果和 FPS 信息 |
python
import sensor
import image
import time
import lcd
# =========================
# 颜色识别配置区
# =========================
# LAB 阈值格式:
# (L Min, L Max, A Min, A Max, B Min, B Max)
#
# code 采用二进制位:
# red = 1 -> 001
# green = 2 -> 010
# blue = 4 -> 100
COLOR_CONFIGS = [
{
"name": "red",
"code": 1,
"threshold": (30, 100, 15, 127, 15, 127),
"draw_color": (255, 0, 0)
},
{
"name": "green",
"code": 2,
"threshold": (0, 80, -70, -10, 0, 30),
"draw_color": (0, 255, 0)
},
{
"name": "blue",
"code": 4,
"threshold": (0, 30, 0, 64, -128, -20),
"draw_color": (0, 0, 255)
}
]
THRESHOLDS = [item["threshold"] for item in COLOR_CONFIGS]
# =========================
# 参数配置区
# =========================
PIXELS_THRESHOLD = 120 # 色块像素数量过滤,数值越大,越不容易识别小色块
AREA_THRESHOLD = 120 # 色块外接区域过滤,数值越大,越不容易识别小区域
MERGE_BLOBS = True # 是否合并相邻或重叠色块
SHOW_FPS = True # 是否在画面显示帧率
PRINT_INTERVAL = 20 # 每隔多少帧打印一次识别信息,避免串口刷屏
# =========================
# 摄像头初始化
# =========================
def init_camera():
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.set_vflip(0)
# 颜色识别实验建议关闭自动增益和自动白平衡,
# 避免光线变化导致颜色阈值漂移。
try:
sensor.set_auto_gain(False)
sensor.set_auto_whitebal(False)
except:
pass
sensor.skip_frames(time=2000)
# =========================
# LCD 初始化
# =========================
def init_lcd():
lcd.init()
# =========================
# 颜色解析函数
# =========================
def get_color_names(code):
"""
根据 blob.code() 解析颜色名称。
当 merge=True 时,一个色块可能同时包含多个颜色。
"""
names = []
for item in COLOR_CONFIGS:
if code & item["code"]:
names.append(item["name"])
if len(names) == 0:
return "unknown"
return "+".join(names)
def get_draw_color(code):
"""
根据识别结果返回绘制颜色。
单色使用对应颜色,混合色统一使用黄色。
"""
matched_count = 0
matched_color = (255, 255, 255)
for item in COLOR_CONFIGS:
if code & item["code"]:
matched_count += 1
matched_color = item["draw_color"]
if matched_count == 1:
return matched_color
return (255, 255, 0)
# =========================
# 绘制识别结果
# =========================
def draw_blob_info(img, blob):
code = blob.code()
color_name = get_color_names(code)
draw_color = get_draw_color(code)
x = blob.x()
y = blob.y()
cx = blob.cx()
cy = blob.cy()
img.draw_rectangle(blob.rect(), color=draw_color)
img.draw_cross(cx, cy, color=draw_color)
label = "{} ({},{})".format(color_name, cx, cy)
img.draw_string(x + 2, y + 2, label, color=draw_color)
return color_name
def draw_screen_info(img, fps, count):
"""
在画面左上角显示运行状态。
"""
if SHOW_FPS:
img.draw_string(2, 2, "FPS: {:.1f}".format(fps), color=(255, 255, 255))
img.draw_string(2, 16, "Color Count: {}".format(count), color=(255, 255, 255))
# =========================
# 主循环
# =========================
def loop():
clock = time.clock()
frame_count = 0
while True:
clock.tick()
img = sensor.snapshot()
blobs = img.find_blobs(
THRESHOLDS,
pixels_threshold=PIXELS_THRESHOLD,
area_threshold=AREA_THRESHOLD,
merge=MERGE_BLOBS
)
detected_names = []
for blob in blobs:
color_name = draw_blob_info(img, blob)
detected_names.append(color_name)
fps = clock.fps()
draw_screen_info(img, fps, len(blobs))
lcd.display(img)
frame_count += 1
if frame_count % PRINT_INTERVAL == 0:
if len(detected_names) > 0:
print("识别结果:", detected_names, "FPS:", fps)
else:
print("未识别到目标颜色", "FPS:", fps)
# =========================
# 程序入口
# =========================
if __name__ == '__main__':
init_camera()
init_lcd()
loop()
代码中的 COLOR_CONFIGS 是整个实验的核心配置区。每一种颜色都包含名称、编码、LAB 阈值和绘制颜色。红色的编码是 1,绿色的编码是 2,蓝色的编码是 4,这些编码采用二进制位设计,方便在合并色块时判断一个结果中是否包含多个颜色。THRESHOLDS 通过列表推导式从配置中提取阈值,直接传入 find_blobs() 进行识别。
摄像头初始化由 init_camera() 完成。程序设置图像格式为 RGB565,分辨率为 QVGA,并尝试关闭自动增益和自动白平衡。颜色识别对光线比较敏感,关闭这些自动调节有助于减少颜色漂移。sensor.skip_frames(time=2000) 用于等待摄像头画面稳定,再进入正式识别流程。
绘制部分由 draw_blob_info() 和 draw_screen_info() 完成。前者负责给每个色块绘制矩形框、中心十字和文字标签,后者负责在左上角显示 FPS 和识别数量。主循环中每一帧都会执行采集、识别、绘制和显示,串口只按 PRINT_INTERVAL 设置的间隔打印结果。
| 函数名 | 功能 | 对应现象 |
|---|---|---|
init_camera() |
初始化摄像头采集参数 | 摄像头开始输出 RGB565 格式的 QVGA 图像 |
init_lcd() |
初始化 LCD 屏幕 | 屏幕可以显示摄像头画面和识别结果 |
get_color_names(code) |
根据颜色编码解析颜色名称 | 色块标签显示为 red、green、blue 或混合名称 |
get_draw_color(code) |
根据识别颜色返回绘制颜色 | 单色框使用对应颜色,混合色框显示为黄色 |
draw_blob_info(img, blob) |
绘制色块识别结果 | 色块区域出现矩形框、中心十字和坐标文字 |
draw_screen_info(img, fps, count) |
绘制屏幕状态信息 | 左上角显示 FPS 和识别数量 |
loop() |
持续采集、识别、绘制和显示 | LCD 实时刷新识别画面,串口定时打印结果 |
主循环 loop() 是程序持续运行的核心。每一轮循环都会通过 sensor.snapshot() 获取当前画面,再用 img.find_blobs() 查找符合红、绿、蓝阈值的色块。识别到色块后,程序会逐个绘制信息,并把颜色名称保存到 detected_names 中。LCD 每帧都会刷新显示结果,串口只在 frame_count % PRINT_INTERVAL == 0 条件成立时打印,既能保留调试信息,又不会让串口输出过于频繁。
扩展应用
颜色识别实验最常见的问题通常不是代码语法错误,而是光线、阈值、摄像头参数和显示刷新带来的识别不稳定。排查时可以围绕画面是否正常、阈值是否匹配、色块面积是否过小、环境光是否变化等方向进行判断。
| 问题现象 | 可能原因 | 处理思路 |
|---|---|---|
| LCD 没有画面 | LCD 未初始化成功、摄像头未正常输出图像 | 确认 init_lcd() 和 init_camera() 都已执行,检查摄像头与屏幕连接状态 |
| 能显示画面但识别不到颜色 | LAB 阈值与当前环境不匹配 | 调整 COLOR_CONFIGS 中对应颜色的阈值范围,保持目标颜色处于稳定光照下 |
| 识别框频繁跳动 | 光线变化明显,自动增益或白平衡影响颜色 | 保持环境光稳定,确认自动增益和自动白平衡关闭成功 |
| 小色块无法识别 | PIXELS_THRESHOLD 或 AREA_THRESHOLD 设置过大 |
适当降低像素数量过滤和外接区域过滤阈值 |
| 背景被误识别 | 阈值范围过宽,背景颜色落入识别区间 | 收窄对应颜色的 LAB 阈值,减少背景干扰 |
| 多个相邻色块被合并 | MERGE_BLOBS = True 会合并相邻或重叠区域 |
如果需要独立识别相邻色块,可将 MERGE_BLOBS 改为 False |
| 串口输出太频繁 | 打印间隔设置过小 | 调大 PRINT_INTERVAL,减少串口刷新频率 |
| FPS 偏低 | 分辨率、绘制内容或识别区域影响处理速度 | 保持 QVGA 分辨率,减少复杂绘制,必要时缩小识别区域 |
| 红绿蓝方向正常但颜色名称错乱 | 阈值重叠或色块颜色不纯 | 分别单独测试三种颜色,重新校准每一组 LAB 阈值 |
颜色识别实验的价值在于把摄像头画面转化成可计算的数据。程序不只是显示图像,而是能判断画面中是否存在指定颜色、色块位于什么位置、当前识别到了多少目标。这种能力可以自然延伸到很多 AI 硬件项目中,例如颜色分拣、视觉定位、机器人巡线辅助、教学识别演示和生产现场标记检测。
| 应用场景 | 实现思路 | 可扩展能力 |
|---|---|---|
| 颜色分拣教学 | 使用红、绿、蓝阈值识别不同颜色物体 | 后续可扩展舵机或电机,实现自动分拣动作 |
| 视觉定位演示 | 通过色块中心点 cx、cy 判断目标位置 |
可扩展为目标跟随、画面居中或机器人方向控制 |
| 智能硬件状态识别 | 使用颜色标签区分不同设备状态或标记物 | 可结合串口通信,把识别结果发送给其他控制板 |
| AI 视觉入门实验 | 用阈值识别理解机器视觉的基础流程 | 后续可扩展二维码、人脸检测或模型推理实验 |
| 课堂互动演示 | 使用不同颜色卡片触发不同识别结果 | 可扩展为颜色答题板、课堂反馈器或互动教具 |
| 工业标记检测 | 检测画面中是否出现指定颜色标记 | 可结合 ROI 区域限制,提高检测效率和稳定性 |
| 图像调试工具 | 在屏幕上显示 FPS、色块数量和中心坐标 | 可扩展更多调试信息,例如面积、像素数量和中心偏移量 |
| 摄像头云台控制 | 根据色块中心点与画面中心的偏差调整方向 | 可结合舵机或步进电机,实现简易目标跟随 |
从工程角度看,当前代码已经具备较好的扩展基础。颜色配置集中在 COLOR_CONFIGS 中,新增颜色时只需要补充名称、编码、阈值和绘制颜色;识别结果统一由 draw_blob_info() 绘制,后续增加面积、宽高、中心偏移量等信息也比较方便;主循环已经完成采集、识别、显示和串口输出,后续接入舵机、电机、蜂鸣器或网络通信时,可以直接基于 detected_names 和色块中心坐标设计新的控制逻辑。
总结
本实验通过 CanMV K210 摄像头完成了红、绿、蓝三种颜色的实时识别,核心能力包括摄像头初始化、RGB565 图像采集、LAB 阈值配置、色块查找、二进制颜色编码解析、图像绘制、LCD 显示和 FPS 统计。代码从摄像头采集开始,把真实画面转换成图像数据,再通过阈值检测找出目标颜色区域,完整展示了 Python 程序如何理解并标注真实世界中的颜色信息。
这类视觉实验非常适合作为 AI 硬件课程的入门案例。颜色阈值让图像识别不再停留在抽象概念中,矩形框和中心坐标让识别结果变得直观可见,LCD 实时显示让调试过程更加清晰。后续课程可以在此基础上继续扩展按键切换颜色模式、蜂鸣器提示识别结果、串口上传检测数据、舵机追踪色块、摄像头 AI 模型推理等方向。只要理解了"采集图像、提取特征、绘制结果、反馈状态"这条主线,更多视觉类智能硬件实验都可以沿着同一套思路继续深入。