OpenCV视频篇——颜色跟踪

2019-12-19  本文已影响0人  WaitFoF

@[TOC]


在这里插入图片描述

一、黑白图片

来自:轻舞飞扬

在了解色彩空间前,先了解一下黑白图片的形成。

用光线对着传感器,从传感器的立场上来看的话,一部分接收到了光线,那么这一部分就是明亮的,一部分没有接收到光线,那么这一部分将会是黑暗的,我们可以视为传感器存在两个通道。

如果传感器存在多个通道的话,意思就是说我们将光线进行分类,高能量的,中等能量的,低能量的等等多种分类,传感器对这多种能力进行区分显示出不同的颜色

不同的颜色有不同的能量分布,一般来说红色(R)在600-700nm波长处能量最高,绿色在534-555nm处能量最高,蓝色(B)在420-440nm处能力最高。

在这里插入图片描述

二、HSV颜色空间

✨色调H
用角度度量,取值范围为0°~360°,从红色开始按逆时针方向计算,红色为0°,绿色为120°,蓝色为240°。它们的补色是:黄色为60°,青色为180°,品红为300°;

✨饱和度S
饱和度S表示颜色接近光谱色的程度。一种颜色,可以看成是某种光谱色与白色混合的结果。其中光谱色所占的比例愈大,颜色接近光谱色的程度就愈高,颜色的饱和度也就愈高。饱和度高,颜色则深而艳。光谱色的白光成分为0,饱和度达到最高。通常取值范围为0%~100%,值越大,颜色越饱和。

✨明度V
明度表示颜色明亮的程度,对于光源色,明度值与发光体的光亮度有关;对于物体色,此值和物体的透射比或反射比有关。通常取值范围为0%(黑)到100%(白)。

HSV模型的三维表示从RGB立方体演化而来。设想从RGB沿立方体对角线的白色顶点向黑色顶点观察,就可以看到立方体的六边形外形。六边形边界表示色彩,水平轴表示纯度,明度沿垂直轴测量。<center>


在这里插入图片描述
在这里插入图片描述

</center>


在这里插入图片描述

三、OpenCV中的HSV

HSV颜色空间与人眼较为接近,一般以HSV为颜色检测和识别。
opencv H范围(0-180) S(0-255) V(0-255)。

1. HSV二值化处理的函数:

2. HSV颜色范围的选取:

HSV色环

内容来自: 阿卡蒂奥

比如我们选择某个偏红色的范围:
💢 1. 色环图中这个区间即BGR(0,128,255)到BGR(255,0,213);
💢 2. B、G、R这三个通道的范围分别为0-255,0-128,213-255。
💢 3. 因此阈值下限lowerb=Scalar(0,0,213),阈值上限upperb=Scalar(255,128,255)。


在这里插入图片描述

四、颜色直方图的获取与目标跟踪

1. 颜色直方图的获取

颜色直方图是对运动目标表面颜色分布的统计,不受目标的形状、姿态等变化的影响。所以用直方图作为目标的特征,依据颜色分布进行匹配,具有稳定性好、抗部分遮挡、计算方法简单和计算量小的特点,是比较理想的目标颜色特征。为了抵抗光照亮暗带来的影响,一般的颜色直方图均在HSV色系下提取。

对HSV3个分量按照对颜色变化的敏感程度不同分别进行量化。设量化后,3个分量的取值范围分别为\{0,1,...L_H-1\}\{0,1,...,L_S-1\}\{0,1,...,L_V-1\}按照[H,S,V]的形式排列成一个矢量,则其范围为:\{0,1,...,L_H-1,...,L_H+L_S-1,...,L_H+L_S+L_V-1\}设颜色i的像素点个数为m_i,图像的像素点的总数为:<center>

在这里插入图片描述 </center>
则颜色i的出现概率,即被定义为颜色直方图,即<center> 在这里插入图片描述
</center>

2.基于颜色直方图的目标跟踪

因为颜色直方图是矢量,以此作为特征进行目标跟踪时,即基于颜色直方图的目标跟踪时,可采用Bhattacharyya距离作为两直方图相似度的度量。计算公式为:<center> 在这里插入图片描述 在这里插入图片描述

</center>

其中:ρ为两直方图的Bhattacharyya系数;p为候选目标直方图分布;q为模板直方图分布;d为两直方图的Bhattacharyya距离,其值越小,表明两直方图的相似度越高;反之,两直方图相似度越低。

五、camshift算法原理

camshift就是利用目标的颜色直方图模型将图像转换为颜色概率分布图,初始化一个搜索窗的大小和位置,并根据上一帧得到的结果自适应调整搜索窗口的位置和大小,从而定位出当前图像中目标的中心位置。

分为三个部分:

1. 色彩投影图(反向投影):

(1) RGB颜色空间对光照亮度变化较为敏感,为了减少此变化对跟踪效果的影响,==首先将图像从RGB空间转换到HSV空间==。

(2) 然后==对其中的H分量作直方图,在直方图中代表了不同H分量值出现的概率或者像素个数==,就是说可以查找出H分量大小为h的概率或者像素个数,即得到了颜色概率查找表。

(3) ==将图像中每个像素的值用其颜色出现的概率对替换,就得到了颜色概率分布图==。这个过程就叫反向投影,颜色概率分布图是一个灰度图像。

2. meanshift

meanshift算法是一种密度函数梯度估计的非参数方法,通过迭代寻优找到概率分布的极值来定位目标。
算法过程:
💞1). 在颜色概率分布图中选取搜索窗W
💞2). 计算阶距:

  •        计算零阶矩
    M_{00}=\sum_x\sum_yI(x,y)
  •        计算一阶矩:
    M_{10}=\sum_x\sum_y{xI(x,y) }
    M_{01}=\sum_x\sum_y{yI(x,y)}
  •        计算搜索窗的质心:
    x_c=\frac{M_{10}}{M_{00}};
    y_c=\frac{M_{01}}{M_{00}};

💞3)调整搜索窗的大小
宽度:s=\sqrt{\frac{M_{00}}{256}};
长度: l=1.2s;
💞4)移动搜索窗的中心到质心,如果移动距离大于预设的固定阈值,则重复2)3)4),直到搜索窗的中心与质心间的移动距离小于预设的固定阈值,或者循环运算的次数达到某一最大值,停止计算。关于meanshift的收敛性证明可以google相关文献。

3. camshift

将meanshift算法扩展到连续图像序列,就是camshift算法。==它将视频的所有帧做meanshift运算,并将上一帧的结果,即搜索窗的大小和中心,作为下一帧meanshift算法搜索窗的初始值==。如此迭代下去,就可以实现对目标的跟踪。

算法过程

(1).初始化搜索窗
(2).计算搜索窗的颜色概率分布(反向投影)
(3).运行meanshift算法,获得搜索窗新的大小和位置。
(4).在下一帧视频图像中用(3)中的值重新初始化搜索窗的大小和位置,再跳转到(2)继续进行。

camshift能有效解决目标变形和遮挡的问题,对系统资源要求不高,时间复杂度低,在简单背景下能够取得良好的跟踪效果。但当背景较为复杂,或者有许多与目标颜色相似像素干扰的情况下,会导致跟踪失败。因为它单纯的考虑颜色直方图,忽略了目标的空间分布特性,所以这种情况下需加入对跟踪目标的预测算法。

4. OpenCV中相关API

1. 直方图

前面已经提过:
OpenCV--017:图像直方图
OpenCV--018:图像直方图均衡化
OpenCV--020:图像直方图反向投影
OpenCV--021:直方图规定化

2. CamShift函数

六、基于颜色特征的目标跟踪

//用HSV中的Hue分量进行跟踪

     Mat image;
    //表示是否要进入反向投影模式,true则表示要进入反向投影模式
    bool backprogectMode = false;

    //表示在选中要跟踪的初始目标,true表示正在用鼠标选择要跟踪的目标
    bool selectObject = false;

    //跟踪目标的个数
    int trackObject = 0;

    //是否显示HUE分量直方图
    bool showHist = true;

    //用于保存鼠标选择第一次单击时点的位置
    Point origin;

    //用于保存鼠标选择的矩形框
    Rect selection;


    int vmin = 10, vmax = 256, smin = 30;

    //鼠标事件,该函数用鼠标进行跟踪目标的选择
    static void onMouse(int event,int x,int y,int ,void*) {
        //鼠标左键按下时,则条件为true,在用鼠标进行目标选择
        //if里面的代码块就是确定所选择的矩形区域selection
        if (selectObject) {
            //确定鼠标点击的矩形左上角顶点的坐标
            selection.x = MIN(x, origin.x);
            cout << "origin: " << origin.x <<",";
            selection.y = MIN(y, origin.y);
            cout << origin.y << endl;

            selection.width = std::abs(x - origin.x);//矩形宽
            selection.height = std::abs(y - origin.y);//矩形高

            //用于确保所选的矩形区域在图片区域内
            selection &= Rect(0, 0, image.cols, image.rows);
        }

        switch (event)
        {
        //鼠标按下,开始点击选择跟踪物体
        case EVENT_LBUTTONDOWN:
            //记录鼠标第一次按下的位置
            origin = Point(x, y);
            //鼠标刚按下去时,初始化了一个矩形区域
            selection = Rect(x, y, 0, 0);
            selectObject = true;
            break;

        //鼠标松开,完成选择跟踪物体
        case EVENT_LBUTTONUP:
            selectObject = false;
            //如果选择物体有效,则打开跟踪功能
            if (selection.width > 0 && selection.height > 0)
                trackObject = -1;
            break;
        }
    }

    static void help() {
        cout << "\n这是一个基于meanShift算法的demo:\n"
            "   你可以选择一个跟踪的颜色目标,比如你的脸。\n";
        cout << "\n\nHot keys: \n"
            "\tESC - quit the program\n"
            "\tc - stop the tracking\n"
            "\tb - switch to/from backprojection view\n"
            "\th - show/hide object histogram\n"
            "To initialize tracking, select the object with mouse: \n";
    }
    

    static void camshiftTest() {
        help();
        VideoCapture cap;
        Rect trackWindow;

        int hsize = 16;
        float hranges[] = { 0,180 };
        const float* phranges = hranges;
        
        
        //打开默认的摄像头
        cap.open(0);
        //判断摄像头是否成功打开
        if (!cap.isOpened())
        {
            help();
            cout << "***could not initialize capturing...***\n";
            return ;
        }

        //直方图窗口:用于显示直方图
        namedWindow("Histogram", 0);
        //CamShift :用于显示视频
        namedWindow("CamShift Demo", 0);
        //设置鼠标回调函数
        setMouseCallback("CamShift Demo", onMouse, 0);
        //createTrackbar函数的功能是在对应的CamShift Demo窗口创建滑动条Vmin,Vmax,Smin
        //滑动条的值最大为256,最后一个参数为0代表没有调用滑动拖动的响应函数
        //vmin,vmax,smin初始值分别为10,256,30
        createTrackbar("Vmin", "CamShift Demo", &vmin, 256, 0);
        createTrackbar("Vmax", "CamShift Demo", &vmax, 256, 0);
        createTrackbar("Smin", "CamShift Demo", &smin, 256, 0);

        Mat frame, hsv, hue, mask, hist, histimg = Mat::zeros(200, 320, CV_8UC3), backproj;
        bool paused = false;
        for (;;) {
            if (!paused)
            {
                //从摄像头中读取的一帧存在frame中
                cap >> frame;
                //若读取失败,停止循环
                if (frame.empty())
                {
                    break;
                }
            }
            frame.copyTo(image);

            if (!paused)
            {
                //将BGR颜色空间转化成HSV颜色空间
                cvtColor(image, hsv, COLOR_BGR2HSV);

                //判断是否存在需要追踪的目标,非0即有需要跟踪的目标
                if (trackObject)
                {
                    //获取进度条中设置的值
                    int _vmin = vmin, _vmax = vmax;
                    //inRange函数的功能是检查输入数组每个元素大小是否在2个给定数值之间,
                    //如果3个通道都在对应的范围内,则mask对应的那个点的值全为1(0xff),否则为0(0x00).

                    //可以是多通道的,mask保存0通道的最小值,也就是h分量
                    //这里利用了hsv的3个通道,比较h:0~180,s:smin~256,v:min(vmin,vmax),max(vmin,vmax)。
                    inRange(hsv, Scalar(0, smin, MIN(_vmin, _vmax)), Scalar(180, 256, MAX(_vmin, _vmax)), mask);
                    
                    int ch[] = { 0,0 };
                    //hue初始化为与hsv大小深度一样的矩阵,色调的度量是用角度表示的,红绿蓝之间相差120度,反色相差180度
                    hue.create(hsv.size(), hsv.depth());
                    //将hsv第一个通道(也就是色调)的数复制到hue中,0索引数组
                    mixChannels(&hsv, 1, &hue, 1, ch, 1);

                    //表示已经选择了有效地待追踪区域
                    if (trackObject < 0)
                    {
                        //此处的构造函数roi用的是Mat hue的矩阵头,且roi的数据指针指向hue,即共用相同的数据,selection为其感兴趣的区域
                        Mat roi(hue, selection);
                        //设置掩膜板选择框为ROI
                        Mat maskroi(mask, selection);
                        //得到选择框内且满足掩膜板内的直方图
                        //calcHist()函数:第1个参数:输入矩阵序列
                                      //  第2个参数:表示输入的矩阵数目,
                                      //  第3个参数:表示将被计算直方图维数通道的列表,
                                      //  第4个参数:表示可选的掩码函数
                                      //  第5个参数:表示输出直方图,
                                      //  第6个参数: 表示直方图的维数,
                                      //  第7个参数: 为每一维直方图数组的大小,
                                      //  第8个参数: 为每一维直方图bin的边界
                        
                        calcHist(&roi, 1, 0, maskroi, hist, 1, &hsize, &phranges);
                        
                        //将hist矩阵进行归一化,都归一化到0-255
                        normalize(hist, hist, 0, 255, NORM_MINMAX);

                        trackWindow = selection;
                        //置trackObject为1,表明属性提取完成
                        //只要鼠标选完区域松开后,且没有按键盘清0键'c',则trackObject一直保持为1,因此该if函数只能执行一次,除非重新选择跟踪区域
                        trackObject = 1;

                        //与按下‘c’键是一样的,这里的all(0)表示的是标量全部清0
                        histimg = Scalar::all(0);

                        //histing是一个200*300的矩阵,hsize应该是每一个bin的宽度,也就是histing矩阵能分出几个bin出来
                        int binW = histimg.cols / hsize;

                        //定义一个缓冲单bin矩阵
                        Mat buf(1, hsize, CV_8UC3);
                        //saturate_case函数为从一个初始类型准确变换到另一个初始类型
                        for (int i = 0; i < hsize; i++)
                        {
                            buf.at<Vec3b>(i) = Vec3b(saturate_cast<uchar>(i * 180. / hsize), 255, 255);
                        }
                        cvtColor(buf, buf, COLOR_HSV2BGR);

                        for (int i = 0; i < hsize; i++)
                        {
                            //at函数为返回一个指定数组元素的参考值                                                          
                            //画直方图到图像空间,指定左上角和右下角,并定义颜色,大小,线型等
                            int val = saturate_cast<int>(hist.at<float>(i) * histimg.rows / 255);
                            rectangle(  histimg,
                                        Point(i * binW, histimg.rows),
                                        Point((i + 1) * binW, histimg.rows - val),
                                        Scalar(buf.at<Vec3b>(i)),-1, 8);
                        }
                    }

                    //计算直方图的反向投影
                    //计算hue图像0通道直方图hist的反向投影,并让入backproj中
                    calcBackProject(&hue, 1, 0, hist, backproj, & phranges);
                    backproj &= mask;
                    //得到掩膜内的反向投影
                    //trackWindow为鼠标选择的区域,TermCriteria为确定迭代终止的准则
                    //使用MeanShift算法对backproj中的内容进行搜索,返回跟踪结果
                    RotatedRect trackBox = CamShift(backproj, trackWindow,TermCriteria(1 | 2, 10, 1)); 
                    //如果鼠标选择的区域trackWindow小于等于1,重置trackWindow区域
                    if (trackWindow.area() <= 1)
                    {
                        int cols = backproj.cols, rows = backproj.rows, r = (MIN(cols, rows) + 5) / 6;
                        trackWindow = Rect(trackWindow.x - r, trackWindow.y - r,
                            trackWindow.x + r, trackWindow.y + r) &
                            Rect(0, 0, cols, rows);
                    }
                    if (backprogectMode)
                        cvtColor(backproj, image, COLOR_GRAY2BGR);

                    //画出跟踪结果的位置:以椭圆为代表
                    ellipse(image, trackBox, Scalar(0, 0, 255), 3);
                }
            }
            else if (trackObject < 0) {
                paused = false;
            }

            //如果正处于物体选择,画出选择框
            if (selectObject && selection.width > 0 && selection.height > 0)
            {
                Mat roi(image, selection);
                bitwise_not(roi, roi);// bitwise_not为将每一个bit位取反
            }

            imshow("CamShift Demo", image);
            imshow("Histogram", histimg);

            char c = (char)waitKey(10);
            if (c == 27)//退出键
                break;
            switch (c)
            {
            case 'b'://反向投影模型交替
                backprogectMode = !backprogectMode;
                break;
            case 'c'://清零跟踪目标对象
                trackObject = 0;
                histimg = Scalar::all(0);
                break;
            case 'h'://显示直方图交替
                showHist = !showHist;
                if (!showHist)
                    destroyWindow("Histogram");
                else
                    namedWindow("Histogram", 1);
                break;
            case 'p'://暂停跟踪交替
                paused = !paused;
                break;
            default:
                ;
            }

        }
    }
int main(){
    //Mat src=imread("D:/test/sh.png");
    //inRangeTest(src);

    camshiftTest();

    waitKey(0);


}

在这里插入图片描述 在这里插入图片描述
在这里插入图片描述

学习资料

  1. 点滴成海~: 基于颜色特征的目标跟踪

  2. 雷霄骅: Camshift算法原理及其Opencv实现

    ps:虽然不认识,但是每次看到都想哭,一想到他就会想起对我很重要的一个人。愿他们在天上能快快乐乐的。

  3. qq_35971623: opencv--颜色物体识别跟踪

  4. opencv3/C++基于颜色的目标跟踪

  5. a &=b这是什么意思啊?

上一篇下一篇

猜你喜欢

热点阅读