前言
打球的时候,我们常常要用眼睛去识别球的位置,确定球的落点和轨迹。用摄像头和视频捕捉物体的位置,确定中心坐标,绘制轮廓边框,就成为了基于Python的OpenCV(Opencv for Python)要处理的重要问题。
思路
1.获取原视频
cap=cv2.VideoCapture('ballsvideo.mp4')#获取原视频
cv2.namedWindow('processed_video', cv2.WINDOW_NORMAL) #创建一个可调整大小的窗口
cv2.resizeWindow('processed_video', 500, 800)#设置窗口宽度为500,高度为800
2.逐帧读取原视频
while True:
ret,frame=cap.read()#读取原视频每帧
if not ret:#直到视频没有下一帧了才结束
break
processed_frame=process(frame)#处理每帧
cv2.imshow('processed_video',processed_frame)#逐帧播放新视频
if cv2.waitKey(1) & 0xFF == ord('q'):#按q键退出视频播放
break
3.逐帧处理
把RGB图像转变成HSV图像
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
在利用OpenCV进行颜色识别时,通常会将图像从RGB(红绿蓝)颜色空间转换到HSV(色调、饱和度、亮度)颜色空间。
HSV颜色空间能够更好地分离颜色信息(通过色调Hue),而RGB颜色空间中的颜色信息是分散在三个通道中的。在HSV空间中,色调(H)通道表示颜色,而饱和度(S)和亮度(V)通道表示颜色的强度和亮度,这使得颜色分析和识别更为简单直接。而且HSV颜色空间对光照变化具有较好的稳定性。在RGB空间中,光照变化会影响R、G和B三个通道的值,而在HSV空间中,光照变化主要影响V通道的值,而H通道(颜色)的值相对稳定。
颜色掩码区分颜色
#颜色掩码
#绿色掩码
lower_green, upper_green = numpy.array([35, 50, 50]), numpy.array([85, 255, 255])
green_mask = cv2.inRange(hsv, lower_green, upper_green)
#红色掩码
#红色在hsv颜色空间有两个范围
lower_red1, upper_red1 = numpy.array([0, 50, 50]), numpy.array([10, 255, 255])
lower_red2, upper_red2 = numpy.array([170, 50, 50]), numpy.array([180, 255, 255])
mask1 = cv2.inRange(hsv, lower_red1, upper_red1)
mask2 = cv2.inRange(hsv, lower_red2, upper_red2)
red_mask = cv2.bitwise_or(mask1, mask2)
#蓝色掩码
lower_blue, upper_blue = numpy.array([100, 50, 40]), numpy.array([130, 255, 255])
blue_mask = cv2.inRange(hsv, lower_blue, upper_blue)
cv2.inRange函数用于检查输入数组(图像)的元素是否在两个指定的值之间。如果元素的值在指定的范围内,该元素在输出数组中的值将被设置为255(白色),否则将被设置为0(黑色)。
参数src
(源图像): 输入图像,通常是一个多通道图像,例如RGB或HSV图像。lowerb
(下界): 表示颜色范围的下界,是一个数组,包含了每个通道的最小值。upperb
(上界): 表示颜色范围的上界,是一个数组,包含了每个通道的最大值。
输出是一个二值图像,其中的像素值要么是0(黑色),要么是255(白色)。如果输入图像中的某个像素的颜色在指定的范围内,对应的输出图像中的像素值将为255,否则为0。
处理每种颜色掩码并绘制
#创建掩码列表用于for循环
masks = [(red_mask, (0, 0, 255), 'red'), (green_mask, (0, 255, 0), 'green'), (blue_mask, (255, 0, 0), 'blue')]
for mask, color, label in masks:
mask_blurred = cv2.GaussianBlur(mask, (3, 3), 0)#高斯滤波平滑边缘
edges = cv2.Canny(mask_blurred, 10, 200)#边缘检测
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)#寻找轮廓
min_area_threshold = 500#确定面积阈值
for contour in contours:#处理多个轮廓
if cv2.contourArea(contour) > min_area_threshold:#如果轮廓面积大于面积阈值,就绘制
x, y, w, h = cv2.boundingRect(contour)
centerX, centerY = int(x + w//2), int(y + h//2)#确定中心位置
cv2.rectangle(result_img, (x, y), (x+w, y+h), color, 2)#画框
text = '{}({},{})'.format(label, centerX, centerY)#设置文本内容
position = (x, y)#设置写字坐标位置(刚好在框上面)
font, font_scale = cv2.FONT_HERSHEY_SIMPLEX, 1#设置字体风格和大小
cv2.putText(result_img, text, position, font, font_scale, color)#在图像副本上写字
注意事项tips
除了OpenCV库之外,我们还要导入numpy库,最基本的原因是要用numpy.array()描述颜色空间,参见本文代码。
要确定视频中物体的HSV值,你也许需要查阅表格、反复调试才能得到最适合的HSV范围,或者,用一些自动化脚本来寻找HSV范围。
颜色掩码功能的inrange()函数生成的是二值图像,而不是彩色图像。我们只是利用二值图像来确定坐标位置,最后要在彩色图像上绘制字体和边框。
原来的图像是RGB图像,我们要用copy()方法创建图像的RGB副本用于绘制。如果用HSV图像绘制,显示效果就会不一样。
视频里可能会有一些杂乱的颜色光点,影响到我们对图像的识别。有的朋友用了先膨胀后腐蚀的形态学运算;我用了中值滤波去噪音,高斯滤波平滑边缘,最后加了一个面积阈值,用来除去面积太小的杂质,最后处理出来的效果是一样的。
4.退出
cap.release()#释放内存
cv2.destroyAllWindows()#关闭窗口
cap.release()是用于释放与视频捕获相关的所有内存资源。在OpenCV中,当你打开一个视频文件或连接到一个摄像头时,它会分配一些内存来处理视频流。cap.release()确保所有这些内存资源都被正确地释放,以避免内存泄漏。
cv2.destroyAllWindows()是用于关闭所有的OpenCV窗口。之前,我们创建了一个名为processed_video的窗口来显示处理过的视频帧,不再需要这个窗口时,应该使用cv2.destroyAllWindows()来关闭它。
如果你不调用cap.release()和cv2.destroyAllWindows(),可能会出现一些问题:
内存泄漏:不释放视频捕获对象可能会导致内存泄漏。内存泄漏可能会随着时间的推移而累积,最终导致应用程序崩溃或系统变慢。
窗口无法关闭:如果不调用cv2.destroyAllWindows(),OpenCV窗口可能会保持打开状态,即使程序已经结束。这可能会让用户感到困惑,而且可能需要手动关闭这些窗口。
物体识别
汇总程序
#一.导入库
import cv2
import numpy
#二.定义函数处理每一帧
def process(frame):
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)#把RGB转化成HSV
hsv = cv2.medianBlur(hsv, 5)#中值滤波去噪音
result_img = frame.copy()#创建图像rgb副本用于绘制(用hsv副本会变色)
#颜色掩码
#绿色掩码
lower_green, upper_green = numpy.array([35, 50, 50]), numpy.array([85, 255, 255])
green_mask = cv2.inRange(hsv, lower_green, upper_green)
#红色掩码
#红色在hsv颜色空间有两个范围
lower_red1, upper_red1 = numpy.array([0, 50, 50]), numpy.array([10, 255, 255])
lower_red2, upper_red2 = numpy.array([170, 50, 50]), numpy.array([180, 255, 255])
mask1 = cv2.inRange(hsv, lower_red1, upper_red1)
mask2 = cv2.inRange(hsv, lower_red2, upper_red2)
red_mask = cv2.bitwise_or(mask1, mask2)
#蓝色掩码
lower_blue, upper_blue = numpy.array([100, 50, 40]), numpy.array([130, 255, 255])
blue_mask = cv2.inRange(hsv, lower_blue, upper_blue)
#创建掩码列表用于for循环
masks = [(red_mask, (0, 0, 255), 'red'), (green_mask, (0, 255, 0), 'green'), (blue_mask, (255, 0, 0), 'blue')]
for mask, color, label in masks:
mask_blurred = cv2.GaussianBlur(mask, (3, 3), 0)#高斯滤波平滑边缘
edges = cv2.Canny(mask_blurred, 10, 200)#边缘检测
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)#寻找轮廓
min_area_threshold = 500#确定面积阈值
for contour in contours:#处理多个轮廓
if cv2.contourArea(contour) > min_area_threshold:#如果轮廓面积大于面积阈值,就绘制
x, y, w, h = cv2.boundingRect(contour)
centerX, centerY = int(x + w//2), int(y + h//2)#确定中心位置
cv2.rectangle(result_img, (x, y), (x+w, y+h), color, 2)#画框
text = '{}({},{})'.format(label, centerX, centerY)#设置文本内容
position = (x, y)#设置写字坐标位置(刚好在框上面)
font, font_scale = cv2.FONT_HERSHEY_SIMPLEX, 1#设置字体风格和大小
cv2.putText(result_img, text, position, font, font_scale, color)#在图像副本上写字
return result_img
#三.主函数
if __name__=='__main__':
cap=cv2.VideoCapture('ballsvideo.mp4')#获取原视频
cv2.namedWindow('processed_video', cv2.WINDOW_NORMAL) #创建一个可调整大小的窗口
cv2.resizeWindow('processed_video', 500, 800)#设置窗口宽度为500,高度为800
while True:
ret,frame=cap.read()#读取原视频每帧
if not ret:#直到视频没有下一帧了才结束
break
processed_frame=process(frame)#处理每帧
cv2.imshow('processed_video',processed_frame)#逐帧播放新视频
if cv2.waitKey(1) & 0xFF == ord('q'):#按q键退出视频播放
break
cap.release()#释放内存
cv2.destroyAllWindows()#关闭窗口