O3.4 opencv摄像头跟踪
摄像头OCR
逻辑
1. 导入库与辅助函数定义
python
import numpy as np
import cv2def cv_show(name, img):cv2.imshow(name, img)cv2.waitKey(1) # 等待1毫秒,实现实时显示(不阻塞循环)def order_points(pts):# 对4个点排序:左上、右上、右下、左下rect = np.zeros((4, 2), dtype="float32")s = pts.sum(axis=1) # 计算每个点的x+y之和rect[0] = pts[np.argmin(s)] # 左上(和最小)rect[2] = pts[np.argmax(s)] # 右下(和最大)diff = np.diff(pts, axis=1) # 计算每个点的y-x之差rect[1] = pts[np.argmin(diff)] # 右上(差最小)rect[3] = pts[np.argmax(diff)] # 左下(差最大)return rectdef four_point_transform(image, pts):# 透视变换:将不规则四边形矫正为矩形rect = order_points(pts) # 排序4个点(tl, tr, br, bl) = rect # 解包为左上、右上、右下、左下# 计算矫正后图像的宽和高(取最长边)widthA = np.sqrt(((br[0]-bl[0])**2) + ((br[1]-bl[1])** 2)) # 下边长widthB = np.sqrt(((tr[0]-tl[0])**2) + ((tr[1]-tl[1])** 2)) # 上边长maxWidth = max(int(widthA), int(widthB)) # 最大宽度heightA = np.sqrt(((tr[0]-br[0])**2) + ((tr[1]-br[1])** 2)) # 右边长heightB = np.sqrt(((tl[0]-bl[0])**2) + ((tl[1]-bl[1])** 2)) # 左边长maxHeight = max(int(heightA), int(heightB)) # 最大高度# 定义矫正后的目标坐标(矩形)dst = np.array([[0, 0], [maxWidth-1, 0], [maxWidth-1, maxHeight-1], [0, maxHeight-1]], dtype="float32")# 计算透视变换矩阵并应用M = cv2.getPerspectiveTransform(rect, dst) # 生成变换矩阵warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight)) # 应用变换return warped
cv_show
:简化图像显示,waitKey(1)
确保实时刷新不卡顿。order_points
:对四边形的 4 个顶点排序,为透视变换做准备。four_point_transform
:核心函数,通过透视变换将倾斜的文档矫正为正面矩形。
2. 摄像头初始化与主循环
python
cap = cv2.VideoCapture(0) # 打开默认摄像头(0表示第一个摄像头)
if not cap.isOpened(): # 检查摄像头是否打开成功print("Cannot open camera")exit()while True:flag = 0 # 标记是否检测到文档(1=检测到,0=未检测到)ret, image = cap.read() # 读取一帧图像(ret:是否成功,image:图像数据)orig = image.copy() # 保存原始图像(用于后续透视变换)if not ret: # 读取失败则退出print("不能读取摄像头")breakcv_show("image", image) # 显示原始摄像头画面
- 初始化摄像头并进入循环,持续读取图像帧。
orig = image.copy()
保留原始图像,避免后续处理修改原始数据。
3. 图像预处理与轮廓检测
python
# 预处理:灰度化→高斯滤波→边缘检测
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 彩色转灰度
gray = cv2.GaussianBlur(gray, (5, 5), 0) # 高斯滤波去噪(5x5卷积核)
edged = cv2.Canny(gray, 15, 45) # Canny边缘检测(阈值15和45)
cv_show('1', edged) # 显示边缘检测结果# 轮廓检测与筛选
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2] # 提取轮廓
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)[:3] # 按面积排序,取前3大轮廓(减少计算量)
image_contours = cv2.drawContours(image, cnts, -1, (0, 255, 0), 2) # 在图像上绘制轮廓
cv_show("image_contours", image_contours) # 显示带轮廓的图像
- 预处理流程:灰度化简化计算→高斯滤波减少噪声→Canny 边缘检测提取物体边缘。
- 轮廓检测:
cv2.findContours
提取边缘轮廓,按面积取前 3 大轮廓(文档通常是较大的物体)。
4. 文档轮廓识别(核心逻辑)
python
# 遍历前3大轮廓,判断是否为文档(四边形且面积足够大)
for c in cnts:peri = cv2.arcLength(c, True) # 计算轮廓周长(闭合轮廓)approx = cv2.approxPolyDP(c, 0.05 * peri, True) # 轮廓近似(0.05×周长为精度)area = cv2.contourArea(approx) # 计算近似轮廓的面积# 条件:面积>20000(过滤小物体)且轮廓为4边形(文档通常是矩形)if area > 20000 and len(approx) == 4:screenCnt = approx # 记录文档的4个顶点flag = 1 # 标记检测到文档print(peri, area) # 打印周长和面积(调试用)print('检测到文档')break
- 轮廓近似:
cv2.approxPolyDP
将不规则轮廓简化为多边形(如矩形会被简化为 4 个顶点)。 - 筛选条件:面积足够大(
>20000
)且为 4 边形,排除小物体和非矩形轮廓。
5. 文档矫正与二值化
python
if flag == 1: # 如果检测到文档# 绘制文档轮廓并显示image_contours = cv2.drawContours(image, [screenCnt], 0, (0, 255, 0), 2)cv_show("image", image_contours)# 透视变换矫正文档warped = four_point_transform(orig, screenCnt.reshape(4, 2)) # 用原始图像矫正cv_show("warped", warped) # 显示矫正后的文档# 二值化处理(突出文字/内容)warped = cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY) # 转灰度ref = cv2.threshold(warped, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] # 自动阈值二值化cv_show("ref", ref) # 显示二值化结果
- 检测到文档后,先绘制轮廓标记位置,再通过
four_point_transform
矫正为正面视图。 - 二值化使用
OTSU
算法自动确定阈值,将文档内容(如文字)与背景分离。
6. 资源释放
python
cap.release() # 关闭摄像头
cv2.destroyAllWindows() # 关闭所有图像窗口
- 循环结束后释放摄像头资源,关闭所有窗口。
# 导入工具包
import numpy as np
import cv2def cv_show(name, img):cv2.imshow(name, img)cv2.waitKey(1)
def order_points(pts):# 一共4个坐标点rect = np.zeros((4, 2), dtype="float32")# 按顺序找到对应坐标0123分别是 左上,右上,右下,左下# 计算左上,右下s = pts.sum(axis=1)rect[0] = pts[np.argmin(s)]rect[2] = pts[np.argmax(s)]# 计算右上和左下diff = np.diff(pts, axis=1)rect[1] = pts[np.argmin(diff)]rect[3] = pts[np.argmax(diff)]return rect
def four_point_transform(image, pts):# 获取输入坐标点rect = order_points(pts)(tl, tr, br, bl) = rect# 计算输入的w和h值widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))maxWidth = max(int(widthA), int(widthB))heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))maxHeight = max(int(heightA), int(heightB))# 变换后对应坐标位置dst = np.array([[0, 0],[maxWidth - 1, 0],[maxWidth - 1, maxHeight - 1],[0, maxHeight - 1]], dtype="float32")# 计算变换矩阵M = cv2.getPerspectiveTransform(rect, dst)warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))# 返回变换后结果return warped# 读取输入
import cv2
cap = cv2.VideoCapture(0) # 确保摄像头是可以启动的状态。
if not cap.isOpened(): # 打开失败print("Cannot open camera")exit()while True:flag = 0 # 用于标识 当前是否检测到文档ret, image = cap.read() # 如果正确读取帧,ret为Trueorig = image.copy()if not ret: # 读取失败,则退出循环print("不能读取摄像头")break #cv_show("image", image)gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 图像处理-转换为灰度图# 预处理gray = cv2.GaussianBlur(gray, (5, 5), 0) # 高斯滤波edged = cv2.Canny(gray, 15, 45)cv_show('1',edged)# 轮廓检测cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]cnts = sorted(cnts, key=cv2.contourArea, reverse=True)[:3]image_contours = cv2.drawContours(image, cnts, -1, (0, 255, 0), 2)cv_show("image_contours", image_contours)# 遍历轮廓for c in cnts:# 计算轮廓近似peri = cv2.arcLength(c, True) #计算轮廓的周长# C表示输入的点集# epsilon表示从原始轮廓到近似轮廓的最大距离,它是一个准确度参数# True表示封闭的approx = cv2.approxPolyDP(c, 0.05 * peri, True) # 轮廓近似area = cv2.contourArea(approx)# 4个点的时候就拿出来if area > 20000 and len(approx) == 4: #20000screenCnt = approxflag = 1print(peri, area)print('检测到文档')breakif flag == 1:# 展示结果# print("STEP 2: 获取轮廓")image_contours = cv2.drawContours(image, [screenCnt], 0, (0, 255, 0), 2)cv_show("image", image_contours)# 透视变换warped = four_point_transform(orig, screenCnt.reshape(4, 2))cv_show("warped", warped)# 二值处理warped = cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY)# ref = cv2.threshold(warped, 220, 255, cv2.THRESH_BINARY)[1]ref = cv2.threshold(warped, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]cv_show("ref", ref)cap.release() # 释放捕获器
cv2.destroyAllWindows() # 关闭图像窗口
背景建模
背景建模是什么?
指在计算机视觉中,从视频序列中提取出静态背景的一种技术。在视频中,背景通常被定义为相对稳定的部分,例如墙壁、地面或天空等。背景建模的目标是将动态的前景对象与静态的背景进行分离,以便进一步分析和处理。
背景建模的目的?
通过背景建模,我们可以实现很多应用,例如运动检测、目标跟踪、
背景建模---帧差法
帧差法的原理
由于场景中的目标在运动,目标的影像在不同图像帧中的位置不同。该类算法对时间上连续的两帧图像进行差分运算,不同帧对应的像素点相减,判断灰度差的绝对值,当绝对值超过一定阈值时,即可判断为运动目标,从而实现目标的检测功能。
帧差法的优缺点
帧差法非常简单,但是会引入噪音和空洞(人物中间是黑色的)问题
逻辑
<think></think>这段代码实现了基于混合高斯模型的视频运动目标检测与跟踪功能,通过背景建模分离出前景运动目标,并对目标进行轮廓检测和框选。以下是详细的逻辑分析:
1. 导入库与初始化
python
import cv2# 读取测试视频(若参数为0则调用摄像头)
cap = cv2.VideoCapture('test.avi')# 创建3x3的十字形卷积核(用于后续形态学运算去噪)
kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (3,3))# 创建混合高斯模型背景减法器(用于分离前景和背景)
fgbg = cv2.createBackgroundSubtractorMOG2()
cv2.VideoCapture
:读取视频文件(test.avi
),若参数为0
则切换为摄像头实时采集。getStructuringElement
:生成十字形卷积核,用于后续形态学开运算(去噪)。createBackgroundSubtractorMOG2
:创建混合高斯模型,能自适应学习背景并分离出前景(运动目标)。
2. 视频帧处理主循环
python
while (True):ret, frame = cap.read() # 读取一帧视频(ret:是否成功读取,frame:当前帧图像)if not ret: # 若视频读取完毕或出错,退出循环break
- 循环读取视频帧,
ret
标记是否读取成功,frame
为当前帧的图像数据。
3. 背景建模与前景提取
python
cv2.imshow('frame', frame) # 显示原始视频帧# 应用混合高斯模型,提取前景掩码(fgmask:前景为白色255,背景为黑色0)
fgmask = fgbg.apply(frame)
cv2.imshow('fgmask', fgmask) # 显示原始前景掩码(可能含噪点)
fgbg.apply(frame)
:核心步骤,通过混合高斯模型对当前帧进行背景减法,输出前景掩码(fgmask
)。运动目标(前景)被标记为白色,静止背景为黑色,但可能存在噪点。
4. 形态学开运算去噪
python
# 对前景掩码进行开运算(先腐蚀后膨胀),去除小噪点
fgmask_new = cv2.morphologyEx(fgmask, cv2.MORPH_OPEN, kernel)
cv2.imshow('fgmask1', fgmask_new) # 显示去噪后的前景掩码
- 开运算:先腐蚀(消除小噪点)再膨胀(恢复目标轮廓),有效去除前景掩码中的孤立小噪点,使目标轮廓更清晰。
5. 轮廓检测与目标框选
python
# 从去噪后的前景掩码中提取轮廓(只检测外轮廓,简化轮廓点)
contours, h = cv2.findContours(fgmask_new, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)# 遍历所有轮廓,筛选并框选符合条件的目标
for c in contours:# 计算轮廓周长(用于筛选较大目标,排除微小噪声)perimeter = cv2.arcLength(c, True) # True表示轮廓闭合if perimeter > 188: # 周长阈值(过滤小目标)# 获取轮廓的最小外接矩形(x,y:左上角坐标;w,h:宽高)x, y, w, h = cv2.boundingRect(c)# 在原始帧上绘制矩形框(绿色,线宽2)fgmask_new_rect = cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)# 显示框选目标后的视频帧
cv2.imshow('fgmask_new_rect', fgmask_new_rect)
findContours
:从去噪后的前景掩码中提取目标轮廓,RETR_EXTERNAL
只保留外轮廓,CHAIN_APPROX_SIMPLE
简化轮廓点(减少计算量)。- 轮廓筛选:通过轮廓周长(
perimeter > 188
)过滤掉过小的噪声轮廓,只保留较大的运动目标。 - 绘制矩形:对符合条件的目标,用
boundingRect
获取外接矩形,并在原始帧上绘制绿色框标记目标。
6. 退出条件与资源释放
python
# 按ESC键(ASCII码27)退出循环k = cv2.waitKey(6) # 每帧等待6毫秒(控制视频播放速度)if k == 27:breakcap.release() # 释放视频资源
cv2.destroyAllWindows() # 关闭所有窗口
waitKey(6)
:控制视频播放速度(每帧停留 6 毫秒),同时检测按键,若按下ESC
则退出。- 循环结束后释放视频资源并关闭窗口。
光流估计
光流估计是什么?
是空间运动物体在观测成像平面上的像素运动的“瞬时速度”,根据各个像素点的速度矢量特征,可以对图像进行动态分析,例如目标跟踪。
光流估计的前提?
(1)亮度恒定:同一点随着时间的变化,其亮度不会发生改变。 (2)小运动:随着时间的变化不会引起位置的剧烈变化,只有小运动情况下才能用前后帧之间单位位置变化引起的灰度变化去近似灰度对位置的偏导数。 (3)空间一致:一个场景上邻近的点投影到图像上也是邻近点,且邻近点速度一致。因为光流法基本方程约束只有一个,而要求x,y方向的速度,有两个未知变量。所以需要连立n多个方程求解。
逻辑
初始化部分
- 打开视频捕获设备:
python
cap = cv2.VideoCapture(0)
使用cv2.VideoCapture(0)
打开默认的摄像头设备(若参数为视频文件名,则打开相应视频文件)。
- 随机生成颜色:
python
color = np.random.randint(0, 255, (100, 3))
生成 100 组随机颜色,用于绘制特征点的运动轨迹,每组颜色由 RGB 三个值组成。
- 读取并处理第一帧:
python
ret, old_frame = cap.read()
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)
读取视频的第一帧,并将其转换为灰度图像,因为光流计算通常在灰度图像上进行。
- 定义特征点检测参数并检测角点:
python
feature_params = dict(maxCorners=100, qualityLevel=0.3, minDistance=7)
p0 = cv2.goodFeaturesToTrack(old_gray, mask=None, **feature_params)
定义特征点检测的参数:
maxCorners
指定最大角点数量为 100。qualityLevel
为角点质量的阈值,只有质量测量值高于该阈值的角点才会被保留。minDistance
设定角点之间的最小距离,以确保角点分散。
然后使用cv2.goodFeaturesToTrack
函数在第一帧灰度图像中检测角点,并将检测到的角点存储在p0
中。- 创建掩模:
python
mask = np.zeros_like(old_frame)
创建一个与第一帧大小相同的全零掩模,用于绘制特征点的运动轨迹。
- 定义 Lucas - Kanade 光流参数:
python
lk_params = dict(winSize=(15, 15), maxLevel=2)
定义 Lucas - Kanade 光流法的参数:
winSize
指定搜索窗口的大小为 15x15 像素。maxLevel
表示金字塔层数为 2。
- 主循环部分
- 读取视频帧并转换为灰度图:
python
ret, frame = cap.read()
if not ret:break
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
在每次循环中读取视频的下一帧,检查是否成功读取。若成功读取,则将其转换为灰度图像frame_gray
。
- 计算光流:
python
p1, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)
使用cv2.calcOpticalFlowPyrLK
函数计算光流,该函数根据 Lucas - Kanade 光流法计算前一帧(old_gray
)中的特征点(p0
)在当前帧(frame_gray
)中的新位置(p1
)。同时返回状态向量st
,用于表示每个特征点是否被成功跟踪(st == 1
表示成功跟踪),以及误差向量err
。
- 选择成功跟踪的点:
python
good_new = p1[st == 1]
good_old = p0[st == 1]
从计算得到的新特征点位置p1
和旧特征点位置p0
中,选择状态为 1(即成功跟踪)的点,分别存储在good_new
和good_old
中。
- 绘制轨迹:
python
for i, (new, old) in enumerate(zip(good_new, good_old)):a, b = new.ravel()c, d = old.ravel()a, b, c, d = int(a), int(b), int(c), int(d)mask = cv2.line(mask,(a, b),(c, d),color[i].tolist(), 2)cv2.imshow('mask',mask)
遍历成功跟踪的特征点对,获取新点和旧点的坐标,并将其转换为整数类型。然后在掩模图像mask
上使用cv2.line
函数绘制从旧点到新点的线段,颜色从之前随机生成的颜色列表color
中选取,线宽为 2。同时显示掩模图像。
- 合成并显示结果:
python
img = cv2.add(frame, mask)
cv2.imshow('frame', img)
将绘制了轨迹的掩模图像mask
与当前帧图像frame
进行叠加,生成最终的结果图像img
,并显示在名为frame
的窗口中。
- 检查按键并更新数据:
python
k = cv2.waitKey(150)
if k == 27:break
old_gray = frame_gray.copy()
p0 = good_new.reshape(-1, 1, 2)
等待 150 毫秒,检查是否有按键按下。如果按下的是 Esc 键(键码为 27),则退出循环。否则,更新旧灰度图像为当前帧的灰度图像,并将当前帧中成功跟踪的特征点位置good_new
重新整理为适合下次光流计算的形状((-1, 1, 2)
),作为下一帧光流计算的起始特征点位置p0
。
- 资源释放部分
python
cv2.destroyAllWindows()
cap.release()
循环结束后,释放视频捕获设备资源,并关闭所有 OpenCV 窗口。
物体跟踪
1. 初始化与参数设置
python
import cv2# 创建CSRT跟踪器实例(CSRT算法适用于高精度跟踪,对遮挡和光照变化有一定鲁棒性)
tracker = cv2.TrackerCSRT_create()
tracking = False # 跟踪状态标志,初始为未跟踪状态
cap = cv2.VideoCapture(0) # 打开默认摄像头(0表示第一个摄像头)
- CSRT 跟踪器:选择
TrackerCSRT_create()
是因为该算法在精度和速度之间平衡较好,适合实时跟踪场景。 - 跟踪标志:
tracking
用于标记是否处于跟踪状态,初始为False
(未跟踪)。
2. 主循环:帧捕获与跟踪逻辑
python
while True:ret, frame = cap.read() # 读取摄像头一帧图像if not ret: # 若读取失败(如摄像头断开),退出循环break
- 循环读取摄像头帧,
ret
判断是否读取成功,frame
为当前帧图像数据。
3. 手动选择跟踪目标(触发条件)
python
if cv2.waitKey(1) == ord('s'): # 按下's'键时启动跟踪tracking = True # 切换到跟踪状态# 让用户用鼠标框选目标区域(ROI),窗口名为'Tracking',不显示十字准星roi = cv2.selectROI('Tracking', frame, showCrosshair=False)tracker.init(frame, roi) # 用当前帧和选中的ROI初始化跟踪器
- 触发方式:按下键盘
s
键,激活目标选择模式。 - ROI 选择:
cv2.selectROI
允许用户在图像上拖动鼠标框选目标区域,返回的roi
是一个元组(x, y, w, h)
(左上角坐标、宽、高)。 - 初始化跟踪器:
tracker.init
将选中的目标区域与当前帧关联,作为跟踪的初始状态。
4. 实时跟踪与结果绘制
python
if tracking: # 若处于跟踪状态success, box = tracker.update(frame) # 更新跟踪结果,返回是否成功及目标位置if success: # 跟踪成功时绘制矩形框x, y, w, h = [int(v) for v in box] # 转换为整数坐标# 在当前帧上绘制绿色矩形框(0,255,0),线宽2cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
- 更新跟踪:
tracker.update(frame)
根据当前帧计算目标的新位置,success
为True
表示跟踪有效,box
为目标区域坐标。 - 绘制结果:若跟踪成功,用矩形框标记目标位置,直观展示跟踪效果。
5. 窗口显示与退出机制
python
cv2.imshow('Tracking', frame) # 显示当前帧(含跟踪框)# 按下ESC键(ASCII码27)退出循环
if cv2.waitKey(1) == 27:break
- 实时显示处理后的帧,用户可观察跟踪效果。
- 按
ESC
键终止程序,避免无限循环。
6. 资源释放
python
cap.release() # 释放摄像头资源
cv2.destroyAllWindows() # 关闭所有显示窗口