计算机视觉中的几何形状与颜色识别技术
任务:先自己在一张白纸上,分别画有矩形、三角形和圆形,同时给它们内部涂上不同的填充色,你也可以采用电脑绘制这样的图形,并填充色,转到自己手机上。然后 用 python opencv编程,利用笔记本上的摄像头识别出手机画布上的矩形、三角形和圆形,以及它们的颜色,并且把识别的结果用英文文字显示在图形中央位置。
任务流程
绘制图形和填充颜色:首先,在纸上绘制矩形、三角形和圆形,并填充不同颜色。你可以使用 OpenCV 或其他图形软件在电脑上创建图形,然后通过手机拍照,上传到笔记本。
捕获图像:利用笔记本上的摄像头获取手机屏幕上的图片。我们将通过 OpenCV 来处理这个图片。
图形识别:使用 OpenCV 对捕获的图像进行处理,识别矩形、三角形和圆形。
颜色识别:通过分析每个图形的颜色来确定其填充颜色。
显示识别结果:在识别到的图形上标记它们的名称(矩形、三角形、圆形),并显示其填充颜色。
第一步:采集摄像头图像
背景
需要实时从摄像头获取每一帧图像,作为后续处理的数据来源。
原理
用OpenCV的 cv2.VideoCapture
打开摄像头,通过cap.read()
循环读取每一帧。
函数原型
cap = cv2.VideoCapture(index[, apiPreference])
ret, frame = cap.read()
cap.release()
参数含义
index
:摄像头编号,0
为默认。
apiPreference
:指定后端API(通常不用,系统自动选择)。
返回值
cv2.VideoCapture
:返回摄像头对象。
cap.read()
:返回(ret, frame),ret为读取状态,frame为当前帧(numpy数组)。
示例代码
cap = cv2.VideoCapture(0)
ret, frame = cap.read()
cap.release()
变量内容
cap:摄像头对象
frame:捕获的当前帧(BGR彩色)
常见问题
- 读取不到图像(ret为False),常见于摄像头被占用、驱动问题。
- 外接摄像头需改index为1、2等。
应用场景
实时监控、人脸识别、物体检测
第二步:选取ROI(感兴趣区域/只识别手机屏幕)
背景
为了只检测手机或平板屏幕内的图形,裁剪ROI,避免背景干扰。
原理
利用numpy切片,指定矩形区域提取ROI。
函数原型
roi = img[y1:y2, x1:x2]
参数含义
img
:输入图像
x1, x2, y1, y2
:感兴趣区域左上和右下坐标
返回值
roi
:指定的区域,numpy数组
示例代码
y1, y2 = 190, 410
x1, x2 = 250, 650
roi = frame[y1:y2, x1:x2]
变量内容
roi
:ROI区域的图像
常见问题
- 坐标写错会导致裁剪不准,需配合画红框调试。
- 如果屏幕斜放,可用透视变换(更高级,这里不展开)。
应用场景
指定区域检测,避免背景干扰。
第三步: 图像灰度化
背景
后续二值化、轮廓等处理都基于单通道灰度图。
原理
cv2.cvtColor
把彩色图(BGR)转为灰度图(单通道)。
函数原型
dst = cv2.cvtColor(src, code)
参数含义
src
:输入图像
code
:颜色空间转换代码,灰度化用cv2.COLOR_BGR2GRAY
返回值
dst
:灰度图(单通道)
示例代码
gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
变量内容
gray
:灰度图像(单通道)
常见问题
输入参数错误导致灰度图全黑或全白。
应用场景
二值化、边缘检测、模板匹配等。
第四步:自适应阈值二值化
背景
将灰度图转为黑白(仅0/255),便于后续分割。自适应方式能处理光照不均的情况。
原理
对每个像素,根据其邻域的平均值/高斯加权均值决定阈值。
常用cv2.THRESH_BINARY_INV
让形状为白色,背景为黑色,适合找填色形状。
函数原型
dst = cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C)
参数含义
src
:灰度输入图
maxValue
:阈值化后的最大值(通常255)
adaptiveMethod
(自适应算法):
cv2.ADAPTIVE_THRESH_MEAN_C
:
含义:对每个像素点,取其周围blockSize大小区域的像素均值,然后减去C作为阈值。
通俗理解:用邻域平均值减C作为当前点的阈值,适合光照不均匀的场景。
适用:大部分图像、局部对比度大的图形、纸面、屏幕等。
cv2.ADAPTIVE_THRESH_GAUSSIAN_C
:
含义:用高斯加权的邻域均值(就是离当前点越近的像素权重越大)减去C作为阈值。
适用:光线变化特别柔和、有晕影的时候效果更好。
区别:比MEAN_C更适合有明显阴影或梯度的图像。
thresholdType
(阈值类型):
cv2.THRESH_BINARY
:
含义:高于阈值的像素为最大值(如255),否则为0。
效果:普通黑白分割,物体为白色,背景为黑色。
cv2.THRESH_BINARY_INV
含义:高于阈值的像素为0,否则为最大值(如255),黑白反转。
常用场景:大部分OpenCV轮廓检测喜欢物体是白色、背景黑色时选THRESH_BINARY_INV,这样填色图形区域就是白的更好识别。
blockSize
:邻域大小(奇数)
C
:常数偏移(调阈值)
返回值
dst
:二值化结果(0和255)
示例代码
thresh = cv2.adaptiveThreshold(gray, 255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY_INV,15, 3)
变量内容
thresh
:二值图像(0/255)
常见问题
blockSize
太小或太大,物体断裂或糊掉。C
值太大,细节丢失。
应用场景
纸面拍照、屏幕识别、车牌/票据OCR等。
第五步:轮廓检测
背景
二值化后提取每个独立封闭区域(如圆、三角、矩形等)。
原理
cv2.findContours
扫描二值图,返回所有轮廓点。
函数原型
contours, hierarchy = cv2.findContours(image, mode, method)
参数含义
image
:二值图(0/255)
mode
:
cv2.RETR_EXTERNAL
:只检测最外层轮廓
cv2.RETR_LIST
、cv2.RETR_TREE
:带层级,适合嵌套
method
:
cv2.CHAIN_APPROX_SIMPLE
:只保存转折点,压缩存储
v2.CHAIN_APPROX_NONE
:保留所有点,精细
返回值
contours
:所有轮廓(每个是一个点集)
hierarchy
:轮廓层级(本任务用不到)
示例代码
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
变量内容
contours
:每个轮廓是Nx1x2数组(坐标)
常见问题
- 轮廓数量多杂点多,可加面积筛选。
- 图形边缘模糊可能导致多个轮廓。
应用场景
物体检测、计数、分割。
第六步:多边形逼近与形状分类
背景
用多边形逼近,将轮廓简化为少量顶点,判断形状(三角形、矩形、圆形等)。
原理
cv2.approxPolyDP
根据曲线逼近精度(epsilon
)简化轮廓,顶点数=形状类别。
函数原型
approx = cv2.approxPolyDP(curve, epsilon, closed)
参数含义
curve
:输入轮廓(点集)
epsilon
:逼近精度(一般取0.04 * 周长)
closed
:是否闭合(通常True)
返回值
approx
:简化后的多边形点集。
示例代码
epsilon = 0.04 * cv2.arcLength(contour, True)
approx = cv2.approxPolyDP(contour, epsilon, True)
if len(approx) == 3:
shape_name = "Triangle"
elif len(approx) == 4:
shape_name = "Rectangle"
elif len(approx) >= 7:
shape_name = "Circle"
else:
continue
变量内容
approx
:逼近多边形点集
shape_name
:当前形状标签
常见问题
epsilon
过小:曲线没简化epsilon
过大:图形失真- 圆形容易判成多边形,需放宽判定条件(顶点数>=7)
应用场景
智能识图、工业检测
第七步:颜色提取与标签化
背景
要把每个图形内部的填充色检测出来。
原理
利用掩模提取图形区域,用cv2.mean算区域平均色,再用HSV区间判断主色
示例代码
mask = np.zeros(gray.shape, dtype=np.uint8) # 创建了一个和原灰度图(gray)一样大小的二维数组,数据类型是uint8(0~255)
cv2.drawContours(mask, [approx], -1, 255, -1)
mean_color = cv2.mean(roi, mask=mask)[:3]
def get_color_name(bgr):
hsv = cv2.cvtColor(np.uint8([[bgr]]), cv2.COLOR_BGR2HSV)[0][0]
h, s, v = hsv
if s < 60 and v > 180:
return "White"
elif 10 < h < 30:
return "Yellow"
elif 90 < h < 130:
return "Blue"
elif h < 10 or h > 160:
return "Red"
elif 40 < h < 80:
return "Green"
else:
return "Other"
参数含义
cv2.drawContours()
:OpenCV 用来在图像上画轮廓的函数。
mask
:目标图像,这里是你的掩膜(一般都是全黑的二值图)。
[approx]
:要绘制的轮廓(注意是个列表,里面只有一个轮廓 approx,通常是用cv2.approxPolyDP()
得到的)。
-1
:画所有的轮廓。
255
:画笔颜色,这里是255,表示白色(因为掩膜是0~255的灰度图)。
-1
:填充模式,-1 表示把轮廓“内部”填满(不是只画线,是整个区域都涂白)。
cv2.mean(roi, mask=mask)
用来算一张图片的“均值”。,
其中roi
可以是你的原图、区域图、或者任何一张三通道/四通道的图片。
mask=mask 表示只计算掩膜为白色(255)部分的平均值,黑色部分会被忽略。
如果没有mask
,会对整张图做平均。
[:3]
cv2.mean 的返回值通常是个长度为4的元组,比如 (B, G, R, alpha)。
但我们通常只关心颜色,不用alpha
通道。
所以取前3个元素,即 (B, G, R)。
返回值
mask
:掩模
mean_color
:tuple
,BGR平均值
变量内容
mask
:只包含当前图形区域的0/255图像
mean_color
:平均色(B, G, R)
常见问题
- 区域里混入边界,颜色偏差
- HSV区间需按实际色卡调整
应用场景
色块识别、智能批改、玩具/工业识别
第八步:计算中心点、标注彩色文字、绘制轮廓
背景
把检测结果直观标在图上(彩色文字+描边),方便人理解。
原理
cv2.moments
求轮廓质心
cv2.putText
写文字,先用黑色粗笔描边再填主色
轮廓点需加上ROI
偏移
M = cv2.moments(approx)
cv2.putText(img, text, org, fontFace, fontScale, color, thickness)
cv2.drawContours(img, [approx], -1, color, thickness)
参数含义
img
:原图
text
:写入内容
org
:左下角坐标
fontFace
:字体
fontScale
:字号
color
:BGR颜色
thickness
:线宽
approx
:当前轮廓
M
:几何矩
从通俗的角度来说,就是用来描述图像形状和区域的一些特性的一种工具。通过计算图像中的“像素”值,我们可以得出一些有用的图像信息,比如图像的中心、面积、形状方向等。可以把它理解为:图像的“统计”信息,它告诉你图像的一些“关键特征”。
返回值
无返回
示例代码
M = cv2.moments(approx)
# 当面积不为0时,重心坐标是 (M["m10"]/M["m00"], M["m01"]/M["m00"])。
if M["m00"] != 0: # M["m00"] 是面积(零阶矩)。
# M["m10"]、M["m01"] 分别是横、纵方向的“加权和”。
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
# 如果面积为0(极小或退化成一点),就用approx的第一个顶点作中心。
else:
cX, cY = approx[0][0]
# 局部坐标(cX, cY)加上偏移量(x1, y1),转换到原图(全局)坐标
cX_global = cX + x1
cY_global = cY + y1
approx_global = approx + [x1, y1]
text = f"{shape_name}, {color_label}"
text_color = tuple([int(x) for x in mean_color])
cv2.putText(frame, text, (cX_global-60, cY_global), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0,0,0), 4, cv2.LINE_AA)
cv2.putText(frame, text, (cX_global-60, cY_global), cv2.FONT_HERSHEY_SIMPLEX, 0.9, text_color, 2, cv2.LINE_AA)
cv2.drawContours(frame, [approx_global], -1, text_color, 2)
变量内容
cX_global
, cY_global
:全图中的中心
text_color
:BGR
常见问题
- 亮色文字不易看,加黑色描边
- 文字坐标可能出框,微调-60等偏移量
应用场景
自动可视化检测结果
第九步:绘制ROI框、显示窗口
背景
调试ROI
区域,方便校准。
原理
cv2.rectangle
画红框,cv2.imshow
弹出窗口。
函数原型
cv2.rectangle(img, pt1, pt2, color, thickness)
cv2.imshow(winname, mat)
参数含义
img
:原图
pt1/pt2
:左上/右下点
color
:BGR
thickness
:线宽
winname
:窗口名
返回值
无
示例代码
cv2.rectangle(frame, (x1, y1), (x2, y2), (0,0,255), 2)
cv2.imshow('Only ROI Detect', frame)
变量内容
无
常见问题
框画错,需微调坐标
应用场景
检测调试、指定区域作业
第十步:退出与资源释放
背景
程序要有友好的退出方式,释放摄像头和窗口。
原理
v2.waitKey
监听按键,cap.release()
释放,cv2.destroyAllWindows()
关闭窗口。
函数原型
cv2.waitKey(delay)
cap.release()
cv2.destroyAllWindows()
参数含义
delay
:等待毫秒数,1表示每帧
cap.release()
:释放摄像头资源
cv2.destroyAllWindows()
:关闭所有OpenCV窗口
返回值
无
示例代码
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
变量内容
无
常见问题
不释放会占用摄像头
应用场景
所有OpenCV交互型脚本
完整代码
import cv2
import numpy as np
def get_color_name(bgr):
hsv = cv2.cvtColor(np.uint8([[bgr]]), cv2.COLOR_BGR2HSV)[0][0]
h, s, v = hsv
if s < 60 and v > 180:
return "White"
elif 10 < h < 30:
return "Yellow"
elif 90 < h < 130:
return "Blue"
elif h < 10 or h > 160:
return "Red"
elif 40 < h < 80:
return "Green"
else:
return "Other"
cap = cv2.VideoCapture(0)
while True:
ret, frame = cap.read()
if not ret:
break
# ROI选屏幕区域
y1, y2 = 190, 410
x1, x2 = 250, 650
roi = frame[y1:y2, x1:x2]
# 灰度化
gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
# 自适应阈值二值化
thresh = cv2.adaptiveThreshold(
gray, 255,
cv2.ADAPTIVE_THRESH_MEAN_C,
cv2.THRESH_BINARY_INV,
15, 3
)
# 轮廓检测
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for contour in contours:
area = cv2.contourArea(contour) # 计算当前轮廓的面积
if area < 800 or area > 80000:
continue # 跳过当前的轮廓,进入下一个轮廓的处理
epsilon = 0.04 * cv2.arcLength(contour, True)
approx = cv2.approxPolyDP(contour, epsilon, True)
if len(approx) == 3:
shape_name = "Triangle"
elif len(approx) == 4:
shape_name = "Rectangle"
elif len(approx) >= 7:
shape_name = "Circle"
else:
continue
mask = np.zeros(gray.shape, dtype=np.uint8)
cv2.drawContours(mask, [approx], -1, 255, -1)
mean_color = cv2.mean(roi, mask=mask)[:3]
color_label = get_color_name(mean_color)
M = cv2.moments(approx)
# 当面积不为0时,重心坐标是 (M["m10"]/M["m00"], M["m01"]/M["m00"])。
if M["m00"] != 0: # M["m00"] 是面积(零阶矩)。
# M["m10"]、M["m01"] 分别是横、纵方向的“加权和”。
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
# 如果面积为0(极小或退化成一点),就用approx的第一个顶点作中心。
else:
cX, cY = approx[0][0]
# 局部坐标(cX, cY)加上偏移量(x1, y1),转换到原图(全局)坐标
cX_global = cX + x1
cY_global = cY + y1
approx_global = approx + [x1, y1]
text = f"{shape_name}, {color_label}"
text_color = tuple([int(x) for x in mean_color]) # 浮动类型的列表或元组转换为一个包含整数的元组,用于表示颜色值
cv2.putText(frame, text, (cX_global-60, cY_global), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0,0,0), 4, cv2.LINE_AA) # 图像的指定位置绘制黑色的文字
cv2.putText(frame, text, (cX_global-60, cY_global), cv2.FONT_HERSHEY_SIMPLEX, 0.9, text_color, 2, cv2.LINE_AA)
cv2.drawContours(frame, [approx_global], -1, text_color, 2)
cv2.rectangle(frame, (x1, y1), (x2, y2), (0,0,255), 2)
cv2.imshow('Only ROI Detect', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
在这里插入图片描述
cap.release()
cv2.destroyAllWindows()