camshift原理解析

算法步骤:

1.选出一个搜索框
2.利用搜索框绘画直方图(直方图就是目标的特征)
3.利用特征去找图像的目标,得到一个图像(图像用白色区域表示与目标有相同的特征)
该方法就是反向映射
4.用3中的图像和搜索框得出目标的位置,和重新框出新的搜索框,继续3(因为在第一帧图像中选出了一个搜索框,并且已经绘出直方图特征,所以接下来就直接用第一帧得出的特征找当前帧与特征相同的区域)
代码如下:

import cv2
import numpy as np
xs, ys, ws, hs = 0, 0, 0, 0  # selection.x selection.y
xo, yo = 0, 0  # origin.x origin.y
selectObject = False
trackObject = 0
HasFirst = False


# x y是鼠标在图像的当前位置
def on_mouse(event, x, y, flags, params):
    global xs, ys, ws, hs, selectObject, xo, yo, trackObject
    if selectObject is True:
        xs = min(x, xo)
        ys = min(y, yo)
        ws = abs(x-xo)
        hs = abs(y-yo)
    if event == cv2.EVENT_LBUTTONDOWN:
        # 记下原始的坐标
        xo, yo = x, y
        xs, ys, ws, hs = x, y, 0, 0
        selectObject = True
    elif event == cv2.EVENT_LBUTTONUP:
        selectObject = False
        trackObject = -1


cap = cv2.VideoCapture(0)
ret, frame = cap.read()
cv2.namedWindow('show')
cv2.setMouseCallback('show', on_mouse)
# 设置迭代的终止标准,最多十次迭代
term_criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1)
# 算法步骤:
# 1.建立搜索框
# 2.反向投影:利用搜索框算出h分量的直方图(概率分布图)
# 3.meanshift:利用2中的概率分布图重新计算搜索框的大小和位置
# 4.在下一帧图像中初始化3中的搜索框,跳转到2继续执行

while True:
    # ret判断是否读到图片
    # frame读取到的当前帧的矩阵
    # 返回的是元组类型,所以也可以加括号
    ret, frame = cap.read()
    if trackObject != 0:
        # 转为hsv模型hue saturation value
        #           色调:角度 饱和度:半径 透明度:高度
        hsv = cv2.cvtColor(frame, cv2.COLOR_RGB2HSV)
        # 掩模范围
        # inRange函数设置亮度阈值
        # 去除低亮度的像素点的影响
        mask = cv2.inRange(hsv, np.array((0., 30., 10.)), np.array((180., 256., 255.)))
        # 1.建立搜索框
        # 为了得到直方图
        if trackObject == -1:
            track_window = (xs, ys, ws, hs)
            # 掩模框出的感兴趣区域
            mask_roi = mask[ys:ys+hs, xs:xs+ws]

            hsv_roi = hsv[ys:ys+hs, xs:xs+ws]
            # 利用hsv图像数组和掩模制出直方图
            # cv2.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate ]])
            # mask 即上文的阈值设置
            # histSize表示这个直方图分成多少份(即多少个直方柱)
            # range是表示直方图能表示像素值的范围
            # 返回直方图
            # 反向投影函数(特征提取函数)
            # 反向投影是一种记录给定图像中的像素点如何适应直方图模型像素分布的方式
            # 反向投影就是首先计算某一特征的直方图模型,然后使用模型去寻找图像中存在的特征
            roi_hist = cv2.calcHist([hsv_roi], [0], mask_roi, [180], [0, 180])
            # 将直方图归一化
            # 归一化函数cv2.normalize(src[, dst[, alpha[, beta[, norm_type[, dtype[, mask]]]]]])
            # 返回dst类型
            # 归一化就是要把需要处理的数据经过处理后(通过某种算法)限制在你需要的一定范围内
            # src  - 输入数组
            # dst  - 与src大小相同的输出数组
            # alpha  - 范围值,   以便在范围归一化的情况下归一化到较低范围边界
            # beta  - 范围归一化时的上限范围; 它不用于标准规范化
            # normType  - 规范化类型 这里的NORM_MINMAX是数组的数值被平移或缩放到一个指定的范围,线性归一化。
            # dtype  - 当为负数时,输出数组与src的类型相同;否则,它具有与src相同的通道数;深度=CV_MAT_DEPTH(dtype)
            # mask  - 可选的操作掩码。
            cv2.normalize(roi_hist, roi_hist, 0, 255, cv2.NORM_MINMAX)
            # 搜索框建立完毕
            trackObject = 1
        # 2.反向映射
        # images:待处理的图像,图像格式为uint8或float32
        # channels:对应图像需要统计的通道,若是灰度图则为0,彩色图像B、G、R对应0、1、2
        # mask:掩膜图像。如果统计整幅图像就设置为None,否则这里传入设计的掩膜图像。
        # histSize表示这个直方图分成多少份(即多少个直方柱)
        # ranges:像素量化范围,通常为0 - 255。
        dst = cv2.calcBackProject([hsv], [0], roi_hist, [0, 180], 1)
        dst &= mask
        cv2.imshow("show2", dst)
        # 得到新的搜索框
        # RotatedRect CamShift(InputArray probImage, Rect&window, TermCriteria criteria)。
        # probImage为输入图像直方图的反向投影图,
        # window为要跟踪目标的初始位置矩形框,
        # criteria为算法结束条件。
        # 函数返回一个有方向角度的矩阵。
        ret, track_window = cv2.CamShift(dst, track_window, term_criteria)
        cv2.rectangle(frame, (track_window[0], track_window[1]), (track_window[0]+track_window[2], track_window[1]+track_window[3]), (0, 255, 0), 2)
        # 得到矩形的四个顶点坐标
        # Draw it on image
        pts = cv2.boxPoints(ret)
        # float类型转为64位int型
        # 类型转换int0()用于索引的整数(same as C ssize_t; normally either int32 or int64)
        pts = np.int0(pts)
        # 多边形的绘制
        # 非填充多边形:polylines()
        # cv2.polylines(img, pts, isClosed, color[, thickness[, lineType[, shift]]])
        # img – 要画的图片
        # pts – 多边形的顶点
        # isClosed – 是否闭合线段
        # color – 颜色
        # cv2.polylines(frame, [pts], True, 255, 2)

    if selectObject is True and ws > 0 and hs > 0:
        cv2.imshow('show1', frame[ys:ys+hs, xs:xs+ws])
        # 二进制取非操作~1=0,~0=1
        cv2.bitwise_not(frame[ys:ys+hs, xs:xs+ws], frame[ys:ys+hs, xs:xs+ws])
    cv2.imshow('show', frame)
    if cv2.waitKey(10) == 27:
        break

cv2.destroyAllWindows()

理解:

其实关键理解的地方在Camshift函数,该函数返回了两个参数。一个ret,一个track_window。这两个都是元组类型(tuple),关键在于track_window这个搜索框。
这个框是用来框目标的,也就是说你直接画出这个搜索框就相当于把目标找出来了。但是你会发现第一帧框出来的搜索框可以只框目标的一小部分,比如我框视频里面的一个球的一小部分,然后利用框出来的区域画直方图得到特征,至此我们不再需要框出搜索框来得到特征了,下面要进行的工作是用特征来找当前帧中与特征很相似的区域,这句话的意思是你在当前帧中找到目标或者与目标相似的区域,接下来在此搜索框中使用meanshift算法,把重心移到密度密集中心点处(这里解释一下:你可以看看calcBackProject函数执行后返回的dst图像,把它打印出来,我原代码里面有。你会发现dst是黑白图像,所谓密集处就是白色区域,看到图像你就会明白白色区域就是目标或者特征与目标相似的区域,所以我们不能把与目标相似的区域也识别进来了,这个时候搜索框就体现价值了,我们在搜索框中移动重心,我们得到的目标就不会包含与目标相似的区域),之后我们要重新把搜索框定位在目标区域,这样下一次搜索时还是会在框内找目标(因为两帧之间间隔很短,所以目标移动的距离不会很大,这样就可以用上一帧的框来预测下一帧目标位置)。第二个就是ret,你会发现用ret绘出目标更准确,其实ret是方向矩形,它可以任意方向框出目标,但是一般用搜索框也可以框出目标,所以我比较喜欢用搜索框来框出目标,源代码里两种形式都有。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值