轨迹跟踪——二维轨迹跟踪

在读研期间,由于导师与水环研究生水生物有项目交叉,我主要研究视频跟踪技术。用来提取鱼类的轨迹以及鱼类的微动作。其中鱼类的轨迹提取我已做了两部分工作,二维视频跟踪,提取鱼类的轨迹;另一部分工作是重建三维鱼类游动轨迹。鱼类微特征提取还没有动工(惭愧)。
----------

二维的视频跟踪

在做这项工作之前我们花费了很大的力气去获取实验数据。购买了三个汉邦高科的摄像头,水箱,摄像头支架等。搭建好实验装置。(由于主要说视频跟踪,具体与鱼相关的就带过)
注意:
1、摄像头要固定住,这样拍摄的视频帧才有固定的摄像机坐标,最后才好转换成统一的现实坐标。
2、获取的视频尽量减少运动背景的干扰,熟悉前景检测的人都应该知道。
3、注意光线,不要太暗了,也不要太刺眼,有光圈亮点。

前景跟踪算法过程

首先都是查看文献来着,大概看了20多篇文献资料。其实起指导作用的文献还是只有那么几篇。我就列出来:
[1].Zhang Z.A flexible new technique for camera calibration[J].Transactions on Pattern Analysis and Machine Intelligence,2000(11):1330-1334.
[2]Olivier Barnich, Marc Van Droogenbroeck. ViBe: A universal background subtraction algorithm for video sequences[J]. IEEE Transactions on Image Processing, 20(6):1709-1724, June 2011.
[3]Robust Fragments-based Tracking using the Integral Histogram.
还有很多文献,就不一一列出来了。对我用C++来实现这些算法的启蒙文献应该是文献[3]:
第[3]篇文献:是一种改进的模板匹配方法,把模板和目标都分成多个字块,然后分别匹配,这样可以避免部分被遮挡就丢失目标的情况。(有源码可以查看)
文献[1]很经典,我是直接用的,用来消除摄像头的扭曲形变。
文献[2]是我们方法的基础,我直接将它的源码移植过来了。

也许你们会问我做了什么,都是别人的东西,确实,是这样的。

我的工作

1、主要是从前景检测的结果中用一种更优的方法找出鱼类所在的位置。能够更加精确、实时的提取鱼类轨迹。
2、将轨迹做平滑处理,使用了基于平方的方法,效果很不错。
3、开发二维轨迹跟踪软件,确实只是小软件,我只用了3个星期不到的时间做完,当然还有很多需要完善的地方。其实也有之前做三维鱼类轨迹跟踪系统的基础。很多复用了之前的代码。

二维估计跟踪算法:

原始帧->去失真->vibe->局部搜索->二维轨迹

vibe算法不是我的原创,是已经很成熟的算法,故不详细说明。贴出它的源码吧,希望大家可以直接借鉴学习。

二次封装之后的vibe.hxx:

#ifndef _VIBE_HXX_
#define _VIBE_HXX_
class VIBE
{
public:
    VIBE();
    ~VIBE();
    void initialize();
    void update();
    inline void setCurrentFrame( unsigned char* i ) { _image = i; }
    inline void setSegmentMap( unsigned char* i ) { _segMap = i; }

    inline void setFrameWidth( int w ) { _frameWidth = w; }
    inline void setFrameHeight( int h ) { _frameHeight = h; }
    inline void setFrameWidthStrip( int s ) { _frameWidthStrip = s; }

    inline bool isInitilized() { return _samples; }

private:
    int getRandomSample();
    int getRandomSubSample();
    int getRandomNeightXCoordinate( int x );
    int getRandomNeightYCoordinate( int y );

private:
    int _frameWidth;
    int _frameHeight;
    int _frameWidthStrip;

    int _sphereRadius;
    int _pixelSamples;
    int _backgroundThreshold;
    int _subSampling;

    int _borderWidth;

    unsigned char* _image;
    unsigned char* _segMap;
    unsigned char** _samples;
};
#endif
#include "stdafx.h"
#include <assert.h>
#include<stdlib.h>
#include <time.h>

#include <math.h>
#include "VIBE.hxx"

#define N 25
#define R 15
#define ZMIN    3
#define PHI     16

VIBE::VIBE()
{
    _frameWidth = 0;
    _frameHeight = 0;
    _frameWidthStrip = 0;

    _sphereRadius = R;
    _pixelSamples = N;

    _backgroundThreshold = ZMIN;
    _subSampling = PHI;

    _image = 0;
    _segMap = 0;

    _samples = 0;

    _borderWidth = 0;
}

VIBE::~VIBE()
{
    if( _samples )
    {
        for( int i = 0; i < _pixelSamples; i ++ )
            if( _samples[ i ] )
            {
                delete [] _samples[ i ];
                _samples[ i ] = 0;
            }
        delete [] _samples;
        _samples = 0;
    }
}

void VIBE::initialize()
{
    assert( (_frameWidth < 1 || _frameHeight < 1 || _frameWidthStrip < 1) || "Please set frame info for initialize...\n");

    srand((int)time(0));

    _samples = new unsigned char*[ _pixelSamples ];

    for( int i = 0; i < _pixelSamples; i ++ )
        _samples[ i ] = new unsigned char[ _frameHeight * _frameWidthStrip ];

    int tq = sqrtf( _pixelSamples );
    _borderWidth = tq / 2;

    for( int y = _borderWidth; y < _frameHeight - _borderWidth; y ++ )
    {
        for( int x = _borderWidth; x < _frameWidth - _borderWidth; x ++ )
        {
            int c = 0;
            for( int i = -_borderWidth; i <= _borderWidth && c < _pixelSamples ; i ++ )
            {
                for( int j = -_borderWidth; j <= _borderWidth && c < _pixelSamples; j ++ )
                    if( c < _pixelSamples - _backgroundThreshold )
                        *(_samples[ c++ ] + y * _frameWidthStrip + x) = *(_image + ( y + i ) * _frameWidthStrip + ( x + j ));
                    else
                        *(_samples[ c++ ] + y * _frameWidthStrip + x) = *(_image +  y * _frameWidthStrip + x);

            }
        }
    }
}

void VIBE::update()
{
    for( int x = 0; x < _frameWidth; x ++ )
    {
        for( int y = 0; y < _frameHeight; y ++ )
        {
            int count = 0, index = 0, dist = 0;

            if( y < _borderWidth || x < _borderWidth || x >= _frameWidth - _borderWidth || y >= _frameHeight - _borderWidth )
            {
                *(_segMap + y * _frameWidthStrip + x ) = 0;
                continue;
            }

            while( count < _backgroundThreshold && index < _pixelSamples )
            {
                dist = abs( *(_image + y * _frameWidthStrip + x) -
                    *(_samples[ index ] + y * _frameWidthStrip + x ) );

                if( dist < _sphereRadius )
                    count ++;

                index ++;
            }

            if( count >= _backgroundThreshold )
            {
                *(_segMap + y * _frameWidthStrip + x ) = 0;

                int rand = getRandomSubSample();
                //if( rand == 0 )
              if( rand < 6)
                {
                    rand = getRandomSample();
                    *(_samples[ rand ] + y * _frameWidthStrip + x ) = *( _image + y * _frameWidthStrip + x );
                }

                rand = getRandomSubSample();
                //if( rand == 0 )
               if( rand < 6 )
                {
                    int xg, yg;

                    xg = getRandomNeightXCoordinate(x);
                    yg = getRandomNeightYCoordinate(y);

                    rand = getRandomSample();
                    *(_samples[ rand ] + yg * _frameWidthStrip + xg ) = *( _image + y * _frameWidthStrip + x );
                }

            }
            else
            {
                *(_segMap  + y * _frameWidthStrip + x ) = 255;
            }
        }
    }
}

int VIBE::getRandomSample()
{
    int val = _pixelSamples * 1.0 * rand() / RAND_MAX;

    if( val == _pixelSamples )
        return val - 1;
    else
        return val;

}

int VIBE::getRandomSubSample()
{
    int val = _subSampling * 1.0 * rand() / RAND_MAX;
    if( val == _subSampling )
        return val - 1;
    else
        return val;

}

int VIBE::getRandomNeightXCoordinate( int x )
{
    int val = 4 * 1.0 * rand() / RAND_MAX - 2;
    if( x + val >= _frameWidth || x + val < 0 )
        return x;
    else
        return x + val;
}

int VIBE::getRandomNeightYCoordinate( int y )
{
    int val = 4 * 1.0 * rand() / RAND_MAX - 2;
    if( y + val >= _frameWidth || y + val < 0 )
        return y;
    else
        return y + val;
}

调用这个VIBE这个类的方法:

VIBE vibe;
    vibe.setFrameWidth(image->width);
    vibe.setFrameHeight(image->height);
    vibe.setFrameWidthStrip(segImage->widthStep);
    vibe.setSegmentMap( (unsigned char*)(segImage->imageData));

    vibe.setCurrentFrame((unsigned char*)grayImage->imageData);
    vibe.initialize();
    ……
    //更新
vibe.setCurrentFrame( (unsigned char*)grayImage->imageData );
vibe.update();

不会用多看看类的源码吧。“多折腾”,我导师的原话。

局部搜索以及平滑处理

设k为帧数,R为搜索半径,P(x,y)为在 (x,y)处的像素值。设第 帧的坐标为(xk,yk) ,可以得到第k+1 帧的位置为 :

平滑处理过程:

设平滑窗口的宽度为W1,窗口中每一帧的权重为:

原理为每一位局当前位置距离的平方,最远处为1^2,……,当前最大2^k

平滑结果:

效果展示:

VIBE结果:

局部搜索:

跟踪结果:

如有疑问,可以联系我。

等我的论文录用后,再深入讨论三维轨迹跟踪方法。

时间: 2024-10-11 12:41:32

轨迹跟踪——二维轨迹跟踪的相关文章

iOS AV Foundation 二维码扫描 04 使用合成语音朗读二维码内容

前一节,我们为程序识别到的二维码提供了可视化的显示,这一节,我们使用合成语音朗读扫描到的二维码的内容. 修改ViewController.m,定义以下实例变量并进行初始化: AVSpeechSynthesizer *_speechSynthesizer; _speechSynthesizer = [[AVSpeechSynthesizer alloc] init]; 初始化语音合成器十分简单.语音合成器会控制对每个语音数据的回放和顺序.初始化完成后,Metadata output将触发语音合成器

带参数二维码如何跟踪用户来自哪个推广人员?

运营微信公众号难免对公众号进行推广,比如每个推广人员都去推广这个公众号,然后需要统计每个推广人员到底带来了多少粉丝.公众号后台的二维码没有这个功能,开发平台接口提供带参数的二维码生成,但是需要程序员.服务器等等,成本太高.现在通过微号帮平台可以直接生成带参数二维码,每个推广人员对应一个二维码,实时统计每个推广人员带来的粉丝. 1.参数二维码统计在微号帮平台选择渠道二维码生成参数二维码 2.参数二维码[端午前夕]填写[端午前夕],所有通过这个参数二维码关注或进入公众号的粉丝都会自动打标签[端午前夕

{Django基础八之cookie和session}一 会话跟踪 二 cookie 三 django中操作cookie 四 session 五 django中操作session

本节目录 一 会话跟踪 二 cookie 三 django中操作cookie 四 session 五 django中操作session 六 xxx 七 xxx 八 xxx 一 会话跟踪 我们需要先了解一下什么是会话!可以把会话理解为客户端与服务器之间的一次会晤,在一次会晤中可能会包含多次请求和响应.例如你给10086打个电话,你就是客户端,而10086服务人员就是服务器了.从双方接通电话那一刻起,会话就开始了,到某一方挂断电话表示会话结束.在通话过程中,你会向10086发出多个请求,那么这多个请

二维码生成类

import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Map; import javax.imageio.ImageIO; import com.google.zxing.BarcodeFormat; import com.google.z

DotNet二维码操作组件ThoughtWorks.QRCode

DotNet二维码操作组件ThoughtWorks.QRCode 在生活中有一种东西几乎已经快要成为我们的另一个电子"身份证",那就是二维码.无论是在软件开发的过程中,还是在普通用户的日常中,几乎都离不开二维码.二维码 (dimensional barcode) ,又称二维条码,是在一维条码的基础上扩展出的一种具有可读性的条码.设备扫描二维条码,通过识别条码的长度和宽度中所记载的二进制数据,可获取其中所包含的信息.相比一维条码,二维码记载更复杂的数据,比如图片.网络链接等. 今天介绍一

jquery-qrcode客户端二维码生成类库扩展--融入自定义Logo图片

年后换了部门,现在主要的职责就是在网上卖精油,似乎这个就是传说中的网络营销. 跟着公司的MM们也了解不了少关于网络营销的知识,间接的了解到马云和刘强东都是些怎样龌龊的人,尽管之前也这样认为. 淘宝就不多说了,全球最大的中文假货销售平台(尽管淘宝没有打出全球中文等字样,可是其必须当之无愧).百度,当当等厚颜无耻之徒的明智之举就在于此,老外做的再大也很少会有直接支持中文的,因此他们都会在其名称前增加:“全球最大的中文”等字样,为自己镶金. 之前还一直比较力挺京东的,认为其根本自营根本不会销售假货,所

注册绑定页面及微信二维码登陆页面开发项目总结

乐帝来到新的实习单位,也许是之前面试或者在爱奇艺实习的履历,很快被项目组"委以重任".而不是老套路刚入职,先在架构师那培训两周,专心钻研框架,不问具体业务.乐帝只有几天看框架的时间,当被分配给框架页面时,还是不能得心应手,正如同事所说,学习还得按部就班,写写例子,看代码是不行的.    目前这家公司类似<走出软件作坊>作者阿朱所在行业,是面向中大型企业,提供人才管理解决方案的软件公司,时髦的词叫SAAS.这类公司层次要比外包公司高,却还有很多外包公司的特点,不像互联网公司有

C:二维数组常用操作

/* 说明:程序实现二维数组中插入列.插入行.交换两个指定位置的元素,并输出指定 位置元素的变化轨迹 作者:socrates 日期:2014-08-17 */ #include "stdafx.h" #include <stdlib.h> #include <assert.h> /*二维数组最大行数和列数*/ #define MAX_ROW_NUM (9) #define MAX_COL_NUM (9) /*二维数组中各元素位置信息*/ typedef stru

.net之qrcode二维码(一)——基础知识

1.二维码定义: 二维码(2-Dimensional Bar Code),是用某种特定的几何图形按一定规律在平面(二维方向上)分布的黑白相间的图形记录数据符号信息的.它是指在一维条码的基础上扩展出另一维具有可读性的条码,使用黑白矩形图案表示二进制数据,被设备扫描后可获取其中所包含的信息.一维条码的宽度记载着数据,而其长度没有记载数据.二维条码的长度.宽度均记载着数据.二维条码有一维条码没有的"定位点"和"容错机制".容错机制在即使没有辨识到全部的条码.或是说条码有污