美文网首页
OpenCV视频篇——颜色跟踪

OpenCV视频篇——颜色跟踪

作者: WaitFoF | 来源:发表于2019-12-19 22:26 被阅读0次

    @[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二值化处理的函数:

    • 说明
      检查数组元素是否位于其他两个数组的元素之间。
      该功能检查范围如下:

      🎈 对于单通道输入数组的每个元素:
      \texttt{dst} (I)= \texttt{lowerb} (I)_0 \leq \texttt{src} (I)_0 \leq \texttt{upperb} (I)_0
      🎈 对于两通道阵列:
      \texttt{dst} (I)= \texttt{lowerb} (I)_0 \leq \texttt{src} (I)_0 \leq \texttt{upperb} (I)_0 \land \texttt{lowerb} (I)_1 \leq \texttt{src} (I)_1 \leq \texttt{upperb} (I)_1

      也就是说,如果src(I)在指定的1D,2D,3D等区域内,则dst(I)设置为255(全1位),否则设置为0。

      ==高低阈值指的是hsv的高低阈值,当图像的hsv在高低阈值之间那么输出图像为255(白),否则为0(黑色)。==

      当下边界参数和/或上边界参数为标量时,应忽略上式中在lowerb和upperb处的索引(I)。

    • 声明
      void inRange(InputArray src, InputArray lowerb,InputArray upperb, OutputArray dst);
      
    • 参数
      src 输入图像
      lowerb 低阈值
      upperb 高阈值。
      dst 输出图像(大小与输入图像一样,类型是8U)相当于:Mat dstImage = Mat::zeros(srcImage.size(),CV_8U);

    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函数
    • 说明
      查找对象的中心,大小和方向。

    • 声明

      RotatedRect cv::CamShift(
              InputArray probImage,
              Rect &window,
              TermCriteria criteria 
      )       
      Python:
             retval, window=cv.CamShift(  probImage, window, criteria )
      
    • 参数

      probImage 对象直方图的反向投影。
      window 初始搜索窗口。
      criteria 底层meanShift的停止条件。

      criterial:
      OpenCV--TermCriteria类
      返回(在旧接口中)CAMSHIFT用于收敛函数的迭代次数,实现CAMSHIFT对象跟踪算法。首先,它使用meanShift找到一个对象中心,然后调整窗口大小,找到最佳旋转。该函数返回旋转后的矩形结构,其中包括对象位置、大小和方向。通过RotatedRect::boundingRect()可以获得搜索窗口的下一个位置。

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

    //用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这是什么意思啊?

    相关文章

      网友评论

          本文标题:OpenCV视频篇——颜色跟踪

          本文链接:https://www.haomeiwen.com/subject/kggvnctx.html