光流跟踪算法
A. 如何使用opencv实现金字塔光流lk跟踪算法
#include <stdio.h>
#include <windows.h>
#include "cv.h"
#include "cxcore.h"
#include "highgui.h"
#include <opencv2\opencv.hpp>
using namespace cv;
static const double pi = 3.14159265358979323846;
inline static double square(int a)
{
return a * a;
}
/*该函数目的:给img分配内存空间,并设定format,如位深以及channel数*/
inline static void allocateOnDemand(IplImage **img, CvSize size, int depth, int channels)
{
if (*img != NULL) return;
*img = cvCreateImage(size, depth, channels);
if (*img == NULL)
{
fprintf(stderr, "Error: Couldn't allocate image. Out of memory?\n");
exit(-1);
}
}
/*主函数,原程序是读取avi视频文件,然后处理,我简单改成从摄像头直接读取数据*/
int main(int argc, char *argv[])
{
//读取摄像头
VideoCapture cap(0);
//读取视频文件
//VideoCapture cap; cap.open("optical_flow_input.avi");
if (!cap.isOpened())
{
return -1;
}
Mat frame;
/*
bool stop = false;
while (!stop)
{
cap >> frame;
// cvtColor(frame, edges, CV_RGB2GRAY);
// GaussianBlur(edges, edges, Size(7, 7), 1.5, 1.5);
// Canny(edges, edges, 0, 30, 3);
// imshow("当前视频", edges);
imshow("当前视频", frame);
if (waitKey(30) >= 0)
stop = true;
}
*/
//CvCapture *input_video = cvCaptureFromFile( "optical_flow_input.avi" );
//cv::VideoCapture cap = *(cv::VideoCapture *) userdata;
//if (input_video == NULL)
// {
// fprintf(stderr, "Error: Can't open video device.\n");
// return -1;
// }
/*先读取一帧,以便得到帧的属性,如长、宽等*/
//cvQueryFrame(input_video);
/*读取帧的属性*/
CvSize frame_size;
frame_size.height = cap.get(CV_CAP_PROP_FRAME_HEIGHT);
frame_size.width = cap.get(CV_CAP_PROP_FRAME_WIDTH);
/*********************************************************/
/*用于把结果写到文件中去,非必要
int frameW = frame_size.height; // 744 for firewire cameras
int frameH = frame_size.width; // 480 for firewire cameras
VideoWriter writer("VideoTest.avi", -1, 25.0, cvSize(frameW, frameH), true);
/*开始光流法*/
//VideoWriter writer("VideoTest.avi", CV_FOURCC('D', 'I', 'V', 'X'), 25.0, Size(640, 480), true);
while (true)
{
static IplImage *frame = NULL, *frame1 = NULL, *frame1_1C = NULL,
*frame2_1C = NULL, *eig_image = NULL, *temp_image = NULL,
*pyramid1 = NULL, *pyramid2 = NULL;
Mat framet;
/*获取第一帧*/
// cap >> framet;
cap.read(framet);
Mat edges;
//黑白抽象滤镜模式
// cvtColor(framet, edges, CV_RGB2GRAY);
// GaussianBlur(edges, edges, Size(7, 7), 1.5, 1.5);
// Canny(edges, edges, 0, 30, 3);
//转换mat格式到lpiimage格式
frame = &IplImage(framet);
if (frame == NULL)
{
fprintf(stderr, "Error: Hmm. The end came sooner than we thought.\n");
return -1;
}
/*由于opencv的光流函数处理的是8位的灰度图,所以需要创建一个同样格式的
IplImage的对象*/
allocateOnDemand(&frame1_1C, frame_size, IPL_DEPTH_8U, 1);
/* 把摄像头图像格式转换成OpenCV惯常处理的图像格式*/
cvConvertImage(frame, frame1_1C, 0);
/* 我们需要把具有全部颜色信息的原帧保存,以备最后在屏幕上显示用*/
allocateOnDemand(&frame1, frame_size, IPL_DEPTH_8U, 3);
cvConvertImage(frame, frame1, 0);
/* 获取第二帧 */
//cap >> framet;
cap.read(framet);
// cvtColor(framet, edges, CV_RGB2GRAY);
// GaussianBlur(edges, edges, Size(7, 7), 1.5, 1.5);
// Canny(edges, edges, 0, 30, 3);
frame = &IplImage(framet);
if (frame == NULL)
{
fprintf(stderr, "Error: Hmm. The end came sooner than we thought.\n");
return -1;
}
/*原理同上*/
allocateOnDemand(&frame2_1C, frame_size, IPL_DEPTH_8U, 1);
cvConvertImage(frame, frame2_1C, 0);
/*********************************************************
开始shi-Tomasi算法,该算法主要用于feature selection,即一张图中哪些是我
们感兴趣需要跟踪的点(interest point)
input:
* "frame1_1C" 输入图像.
* "eig_image" and "temp_image" 只是给该算法提供可操作的内存区域.
* 第一个".01" 规定了特征值的最小质量,因为该算法要得到好的特征点,哪就
需要一个选择的阈值
* 第二个".01" 规定了像素之间最小的距离,用于减少运算复杂度,当然也一定
程度降低了跟踪精度
* "NULL" 意味着处理整张图片,当然你也可以指定一块区域
output:
* "frame1_features" 将会包含fram1的特征值
* "number_of_features" 将在该函数中自动填充上所找到特征值的真实数目,
该值<= 400
**********************************************************/
/*开始准备该算法需要的输入*/
/* 给eig_image,temp_image分配空间*/
allocateOnDemand(&eig_image, frame_size, IPL_DEPTH_32F, 1);
allocateOnDemand(&temp_image, frame_size, IPL_DEPTH_32F, 1);
/* 定义存放frame1特征值的数组,400只是定义一个上限 */
CvPoint2D32f frame1_features[400];
int number_of_features = 400;
/*开始跑shi-tomasi函数*/
cvGoodFeaturesToTrack(frame1_1C, eig_image, temp_image,
frame1_features, &number_of_features, .01, .01, NULL);
/**********************************************************
开始金字塔Lucas Kanade光流法,该算法主要用于feature tracking,即是算出
光流,并跟踪目标。
input:
* "frame1_1C" 输入图像,即8位灰色的第一帧
* "frame2_1C" 第二帧,我们要在其上找出第一帧我们发现的特征点在第二帧
的什么位置
* "pyramid1" and "pyramid2" 是提供给该算法可操作的内存区域,计算中间
数据
* "frame1_features" 由shi-tomasi算法得到的第一帧的特征点.
* "number_of_features" 第一帧特征点的数目
* "optical_flow_termination_criteria" 该算法中迭代终止的判别,这里是
epsilon<0.3,epsilon是两帧中对应特征窗口的光度之差的平方,这个以后的文
章会讲
* "0" 这个我不知道啥意思,反正改成1就出不来光流了,就用作者原话解释把
means disable enhancements. (For example, the second array isn't
pre-initialized with guesses.)
output:
* "frame2_features" 根据第一帧的特征点,在第二帧上所找到的对应点
* "optical_flow_window" lucas-kanade光流算法的运算窗口,具体lucas-kanade
会在下一篇详述
* "5" 指示最大的金字塔层数,0表示只有一层,那就是没用金字塔算法
* "optical_flow_found_feature" 用于指示在第二帧中是否找到对应特征值,
若找到,其值为非零
* "optical_flow_feature_error" 用于存放光流误差
**********************************************************/
/*开始为pyramid lucas kanade光流算法输入做准备*/
CvPoint2D32f frame2_features[400];
/* 该数组相应位置的值为非零,如果frame1中的特征值在frame2中找到 */
char optical_flow_found_feature[400];
/* 数组第i个元素表对应点光流误差*/
float optical_flow_feature_error[400];
/*lucas-kanade光流法运算窗口,这里取3*3的窗口,可以尝试下5*5,区别就是5*5
出现aperture problem的几率较小,3*3运算量小,对于feature selection即shi-tomasi算法来说足够了*/
CvSize optical_flow_window = cvSize(5, 5);
// CvSize optical_flow_window = cvSize(5, 5);
/* 终止规则,当完成20次迭代或者当epsilon<=0.3,迭代终止,可以尝试下别的值*/
CvTermCriteria optical_flow_termination_criteria= cvTermCriteria(CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 20, .3);
/*分配工作区域*/
allocateOnDemand(&pyramid1, frame_size, IPL_DEPTH_8U, 1);
allocateOnDemand(&pyramid2, frame_size, IPL_DEPTH_8U, 1);
/*开始跑该算法*/
cvCalcOpticalFlowPyrLK(frame1_1C, frame2_1C, pyramid1, pyramid2,frame1_features, frame2_features, number_of_features,
optical_flow_window, 5, optical_flow_found_feature,optical_flow_feature_error, optical_flow_termination_criteria, 0);
/*画光流场,画图是依据两帧对应的特征值,
这个特征值就是图像上我们感兴趣的点,如边缘上的点P(x,y)*/
for (int i = 0; i< number_of_features; i++)
{
/* 如果没找到对应特征点 */
if (optical_flow_found_feature[i] == 0)
continue;
int line_thickness;
line_thickness = 1;
/* CV_RGB(red, green, blue) is the red, green, and blue components
* of the color you want, each out of 255.
*/
CvScalar line_color;
line_color = CV_RGB(255, 0, 0);
/*画箭头,因为帧间的运动很小,所以需要缩放,不然看不见箭头,缩放因子为3*/
CvPoint p, q;
p.x = (int)frame1_features[i].x;
p.y = (int)frame1_features[i].y;
q.x = (int)frame2_features[i].x;
q.y = (int)frame2_features[i].y;
double angle;
angle = atan2((double)p.y - q.y, (double)p.x - q.x);
double hypotenuse;
hypotenuse = sqrt(square(p.y - q.y) + square(p.x - q.x));
/*执行缩放*/
q.x = (int)(p.x - 5 * hypotenuse * cos(angle));
q.y = (int)(p.y - 5 * hypotenuse * sin(angle));
/*画箭头主线*/
/* "frame1"要在frame1上作画.
* "p" 线的开始点.
* "q" 线的终止点.
* "CV_AA" 反锯齿.
* "0" 没有小数位.
*/
cvLine(frame1, p, q, line_color, line_thickness, CV_AA, 0);
/* 画箭的头部*/
p.x = (int)(q.x + 9 * cos(angle + pi / 4));
p.y = (int)(q.y + 9 * sin(angle + pi / 4));
cvLine(frame1, p, q, line_color, line_thickness, CV_AA, 0);
p.x = (int)(q.x + 9 * cos(angle - pi / 4));
p.y = (int)(q.y + 9 * sin(angle - pi / 4));
cvLine(frame1, p, q, line_color, line_thickness, CV_AA, 0);
}
/*显示图像*/
/*创建一个名为optical flow的窗口,大小自动改变*/
cvNamedWindow("Optical Flow", CV_WINDOW_NORMAL);
cvFlip(frame1, NULL, 2);
cvShowImage("Optical Flow", frame1);
/*延时,要不放不了*/
cvWaitKey(33);
/*写入到文件中去*/
// cv::Mat m = cv::cvarrToMat(frame1);//转换lpimgae到mat格式
// writer << m;//opencv3.0 version writer
}
cap.release();
cvWaitKey(33);
system("pause");
}
B. 目标跟踪检测算法(一)——传统方法
姓名:刘帆;学号:20021210609;学院:电子工程学院
https://blog.csdn.net/qq_34919792/article/details/89893214
【嵌牛导读】目标跟踪算法研究难点与挑战在于实际复杂的应用环境 、背景相似干扰、光照条件的变化、遮挡等外界因素以及目标姿态变化,外观变形,尺度变化、平面外旋转、平面内旋转、出视野、快速运动和运动模糊等。而且当目标跟踪算法投入实际应用时,不可避免的一个问题——实时性问题也是非常的重要。正是有了这些问题,才使得算法研究充满着难点和挑战。
【嵌牛鼻子】目标跟踪算法,传统算法
【嵌牛提问】利用目标跟踪检测算法要达到何目的?第一阶段的单目标追踪算法包括什么?具体步骤有哪些?它们有何特点?
【嵌牛正文】
第一阶段
目标跟踪分为两个部分,一个是对指定目标寻找可以跟踪的特征,常用的有颜色,轮廓,特征点,轨迹等,另一个是对目标特征进行跟踪。
1、静态背景
1)背景差: 对背景的光照变化、噪声干扰以及周期性运动等进行建模。通过当前帧减去背景图来捕获运动物体的过程。
2)帧差: 由于场景中的目标在运动,目标的影像在不同图像帧中的位置不同。该类算法对时间上连续的两帧或三帧图像进行差分运算,不同帧对应的像素点相减,判断灰度差的绝对值,当绝对值超过一定阈值时,即可判断为运动目标,从而实现目标的检测功能。
与二帧差分法不同的是,三帧差分法(交并运算)去除了重影现象,可以检测出较为完整的物体。帧间差分法的原理简单,计算量小,能够快速检测出场景中的运动目标。但帧间差分法检测的目标不完整,内部含有“空洞”,这是因为运动目标在相邻帧之间的位置变化缓慢,目标内部在不同帧图像中相重叠的部分很难检测出来。帧间差分法通常不单独用在目标检测中,往往与其它的检测算法结合使用。
3)Codebook
算法为图像中每一个像素点建立一个码本,每个码本可以包括多个码元(对应阈值范围),在学习阶段,对当前像素点进行匹配,如果该像素值在某个码元的学习阈值内,也就是说与之前出现过的某种历史情况偏离不大,则认为该像素点符合背景特征,需要更新对应点的学习阈值和检测阈值。
如果新来的像素值与每个码元都不匹配,则可能是由于动态背景导致,这种情况下,我们需要为其建立一个新的码元。每个像素点通过对应多个码元,来适应复杂的动态背景。
在应用时,每隔一段时间选择K帧通过更新算法建立CodeBook背景模型,并且删除超过一段时间未使用的码元。
4)GMM
混合高斯模型(Gaussian of Micture Models,GMM)是较常用的背景去除方法之一(其他的还有均值法、中值法、滑动平均滤波等)。
首先我们需要了解单核高斯滤波的算法步骤:
混合高斯建模GMM(Gaussian Mixture Model)作为单核高斯背景建模的扩展,是目前使用最广泛的一种方法,GMM将背景模型描述为多个分布,每个像素的R、G、B三个通道像素值的变化分别由一个混合高斯模型分布来刻画,符合其中一个分布模型的像素即为背景像素。作为最常用的一种背景建模方法,GMM有很多改进版本,比如利用纹理复杂度来更新差分阈值,通过像素变化的剧烈程度来动态调整学习率等。
5)ViBe(2011)
ViBe算法主要特点是随机背景更新策略,这和GMM有很大不同。其步骤和GMM类似。具体的思想就是为每个像素点存储了一个样本集,样本集中采样值就是该像素点过去的像素值和其邻居点的像素值,然后将每一个新的像素值和样本集进行比较来判断是否属于背景点。
其中pt(x)为新帧的像素值,R为设定值,p1、p2、p3….为样本集中的像素值,以pt(x)为圆心R为半径的圆被认为成一个集,当样本集与此集的交集大于设定的阈值#min时,可认为此为背景像素点(交集越大,表示新像素点与样本集越相关)。我们可以通过改变#min的值与R的值来改变模型的灵敏度。
Step1:初始化单帧图像中每个像素点的背景模型。假设每一个像素和其邻域像素的像素值在空域上有相似的分布。基于这种假设,每一个像素模型都可以用其邻域中的像素来表示。为了保证背景模型符合统计学规律,邻域的范围要足够大。当输入第一帧图像时,即t=0时,像素的背景模型。其中,NG(x,y)表示空域上相邻的像素值,f(xi,yi)表示当前点的像素值。在N次的初始化的过程中,NG(x,y)中的像素点(xi,yi)被选中的可能次数为L=1,2,3,…,N。
Step2:对后续的图像序列进行前景目标分割操作。当t=k时,像素点(x,y)的背景模型为BKm(x,y),像素值为fk(x,y)。按照下面判断该像素值是否为前景。这里上标r是随机选的;T是预先设置好的阈值。当fk(x,y)满足符合背景#N次时,我们认为像素点fk(x,y)为背景,否则为前景。
Step3:ViBe算法的更新在时间和空间上都具有随机性。每一个背景点有1/ φ的概率去更新自己的模型样本值,同时也有1/ φ的概率去更新它的邻居点的模型样本值。更新邻居的样本值利用了像素值的空间传播特性,背景模型逐渐向外扩散,这也有利于Ghost区域的更快的识别。同时当前景点计数达到临界值时将其变为背景,并有1/ φ的概率去更新自己的模型样本值(为了减少缓慢移动物体的影响和摄像机的抖动)。
可以有如下总结,ViBe中的每一个像素点在更新的时候都有一个时间和空间上随机影响的范围,这个范围很小,大概3x3的样子,这个是考虑到摄像头抖动时会有坐标的轻微来回变化,这样虽然由于ViBe的判别方式仍认为是背景点,但是也会对后面的判别产生影响,为了保证空间的连续性,随机更新减少了这个影响。而在样本值保留在样本集中的概率随着时间的增大而变小,这就保证了像素模型在时间上面的延续特性。
6)光流
光流是由物体或相机的运动引起的图像对象在两个连续帧之间的视在运动模式。它是2D矢量场,其中每个矢量是一个位移矢量,显示点从第一帧到第二帧的移动。
光流实际上是一种特征点跟踪方法,其计算的为向量,基于三点假设:
1、场景中目标的像素在帧间运动时亮度(像素值或其衍生值)不发生变化;2、帧间位移不能太大;3、同一表面上的邻近点都在做相同的运动;
光流跟踪过程:1)对一个连续视频帧序列进行处理;2)对每一帧进行前景目标检测;3)对某一帧出现的前景目标,找出具有代表性的特征点(Harris角点);4)对于前后帧做像素值比较,寻找上一帧在当前帧中的最佳位置,从而得到前景目标在当前帧中的位置信息;5)重复上述步骤,即可实现目标跟踪
2、运动场(分为相机固定,但是视角变化和相机是运动的)
1)运动建模(如视觉里程计运动模型、速度运动模型等)
运动学是对进行刚性位移的相机进行构型,一般通过6个变量来描述,3个直角坐标,3个欧拉角(横滚、俯仰、偏航)。
Ⅰ、对相机的运动建模
由于这个不是我们本次所要讨论的重点,但是在《概率机器人》一书中提出了很多很好的方法,相机的运动需要对图像内的像素做位移矩阵和旋转矩阵的坐标换算。除了对相机建立传统的速度运动模型外,也可以用视觉里程计等通关过置信度的更新来得到概率最大位置。
Ⅱ、对于跟踪目标的运动建模
该方法需要提前通过先验知识知道所跟踪的目标对象是什么,比如车辆、行人、人脸等。通过对要跟踪的目标进行建模,然后再利用该模型来进行实际的跟踪。该方法必须提前知道要跟踪的目标对象是什么,然后再去跟踪指定的目标,这是它的局限性,因而其推广性相对比较差。(比如已知跟踪的物体是羽毛球,那很容易通过前几帧的取点,来建立整个羽毛球运动的抛物线模型)
2)核心搜索算法(常见的预测算法有Kalman(卡尔曼)滤波、扩展卡尔曼滤波、粒子滤波)
Ⅰ、Kalman 滤波
Kalman滤波器是通过前一状态预测当前状态,并使用当前观测状态进行校正,从而保证输出状态平稳变化,可有效抵抗观测误差。因此在运动目标跟踪中也被广泛使用。
在视频处理的运动目标跟踪里,每个目标的状态可表示为(x,y,w,h),x和y表示目标位置,w和h表示目标宽高。一般地认为目标的宽高是不变的,而其运动速度是匀速,那么目标的状态向量就应该扩展为(x,y,w,h,dx,dy),其中dx和dy是目标当前时刻的速度。通过kalman滤波器来估计每个时刻目标状态的大致过程为:
对视频进行运动目标检测,通过简单匹配方法来给出目标的第一个和第二个状态,从第三个状态开始,就先使用kalman滤波器预测出当前状态,再用当前帧图像的检测结果作为观测值输入给kalman滤波器,得到的校正结果就被认为是目标在当前帧的真实状态。(其中,Zt为测量值,为预测值,ut为控制量,Kt为增益。)
Ⅱ、扩展卡尔曼滤波(EKF)和无迹卡尔曼滤波(UKF)
由于卡尔曼滤波的假设为线性问题,无法直接用在非线性问题上,EKF和UKF解决了这个问题(这个线性问题体现在用测量量来计算预测量的过程中)。EKF是通过构建线性函数g(x),与非线性函数相切,并对每一时刻所求得的g(x)做KF,如下图所示。
UKF与EKF去求解雅可比矩阵拟合线性方程的方法不同,通过对那个先验分布中的采集点,来线性化随机变量的非线性函数。与EKF所用的方法不同,UKF产生的高斯分布和实际高斯分布更加接近,其引起的近似误差也更小。
Ⅲ、粒子滤波
1、初始状态:基于粒子滤波的目标追踪方法是一种生成式跟踪方法,所以要有一个初始化的阶段。对于第一帧图像,人工标定出待检测的目标,对该目标区域提出特征;
2、搜索阶段:现在已经知道了目标的特征,然后就在目标的周围撒点(particle), 如:a)均匀的撒点;b)按高斯分布撒点,就是近的地方撒得多,远的地方撒的少。论文里使用的是后一种方法。每一个粒子都计算所在区域内的颜色直方图,如初始化提取特征一样,然后对所有的相似度进行归一化。文中相似性使用的是巴氏距离;
3、重采样:根据粒子权重对粒子进行筛选,筛选过程中,既要大量保留权重大的粒子,又要有一小部分权重小的粒子;
4、状态转移:将重采样后的粒子带入状态转移方程得到新的预测粒子;
5、测量及更新:对目标点特征化,并计算各个粒子和目标间的巴氏距离,更新粒子的权重;
6、决策阶段:每个粒子都获得一个和目标的相似度,相似度越高,目标在该范围出现的可能性越高,将保留的所有粒子通过相似度加权后的结果作为目标可能的位置。
3)Meanshift算法
MeanShift算法属于核密度估计法,它不需要任何先验知识而完全依靠特征空间中样本点的计算其密度函数值。对于一组采样数据,直方图法通常把数据的值域分成若干相等的区间,数据按区间分成若干组,每组数据的个数与总参数个数的比率就是每个单元的概率值;核密度估计法的原理相似于直方图法,只是多了一个用于平滑数据的核函数。采用核函数估计法,在采样充分的情况下,能够渐进地收敛于任意的密度函数,即可以对服从任何分布的数据进行密度估计。
Meanshift算法步骤
1、通过对初始点(或者上一帧的目标点)为圆心,绘制一个半径为R的圆心,寻找特征和该点相似的点所构成的向量;
2、所有向量相加,可以获得一个向量叠加,这个向量指向特征点多的方向;
3、取步骤二的向量终点为初始点重复步骤一、二,直到得到的向量小于一定的阈值,也就是说明当前位置是特征点密度最密集的地方,停止迭代,认为该点为当前帧的目标点;
4)Camshift算法
Camshift算法是MeanShift算法的改进,称为连续自适应的MeanShift算法。Camshift 是由Meanshift 推导而来 Meanshift主要是用在单张影像上,但是独立一张影像分析对追踪而言并无意义,Camshift 就是利用MeanShift的方法,对影像串行进行分析。
1、首先在影像串行中选择目标区域。
2、计算此区域的颜色直方图(特征提取)。
3、用MeanShift算法来收敛欲追踪的区域。
4、通过目标点的位置和向量信息计算新的窗口大小,并标示之。
5、以此为参数重复步骤三、四。
Camshift 关键就在于当目标的大小发生改变的时候,此算法可以自适应调整目标区域继续跟踪。
3、小结
第一阶段的单目标追踪算法基本上都是传统方法,计算量小,在嵌入式等设备中落地较多,opencv中也预留了大量的接口。通过上面的两节的介绍,我们不难发现,目标检测算法的步骤分为两部分,一部分是对指定目标寻找可以跟踪的特征,常用的有颜色,轮廓,特征点,轨迹等,另一部分是对目标特征进行跟踪,如上文所提及的方法。所以目标检测方法的发展,也可总结为两个方面,一个是如何去获得更加具有区分性的可跟踪的稳定特征,另一个是如何建立帧与帧之间的数据关联,保证跟踪目标是正确的。
随着以概率为基础的卡尔曼滤波、粒子滤波或是以Meanshift为代表向量叠加方法在目标检测的运用,使得目标检测不再需要假设自身的一个状态为静止的,而是可以是运动的,更加符合复杂场景中的目标跟踪。
C. 光线跟踪算法的例子
为了说明光线跟踪所用的基本原理,我们来看计算一个光线与球体交点的例子。用 I 表示球面上的点,C 表示球心,r 表示半径,那么球面的公式为 . 如果定义一条线的起点即光线起点是 S,方向是 d,那么线上的每个点都可以表示为
其中 t 是定义线上与起点距离的常数,为了简化起见,通常 d 定义为单位矢量。那么,在这种情况下已知 S、d、C 以及 r,于是代入 I 得到:
简化 ,那么
那么二次方程的解是
这只是直线光线与球体交点的所用的数学公式,当然对于通用的光线跟踪来说是远远不够的,但是它至少表示了这个算法如何使用的一个实例。
D. 计算机图形学, 光线跟踪算法的过程是什么
光线跟踪思路:从视点出发,通过图像平面上每个像素中心向场景发出一条光线,光线的起点为视点,方向为像素中心和视点连线单位向量。光线与离视点最近的场景物体表面交点有三种可能:
当前交点所在的物体表面为理想漫射面,跟踪结束。
当前交点所在的物体表面为理想镜面,光线沿其镜面发射方向继续跟踪。
当前交点所在的物体表面为规则透射面,光线沿其规则透射方向继续跟踪。
伪代码:
void TraceRay(const Vec3& start, const Vec3& direction, int depth, Color& color)
{
Vec3 intersectionPoint, reflectedDirection, transmittedDirection;
Color localColor, reflectedColor, transmittedColor;
if (depth >= MAX_DEPTH) {
color = Black; //#000
}
else {
Ray ray(start, direction); //取start起点,方向direction为跟踪射线;
if ( !scene->HasIntersection(ray) )
color = BackgroundColor;
else {
计算理起始点start最近的交点intersectionPoint,
记录相交物体intersectionObject,
// #1
Shade(intersectionObject, intersectionPoint, localColor);
// #2
if ( intersectionPoint所在面为镜面 ) {
计算跟踪光想S在intersectionPoint处的反射光线方向reflectedDirection,
TraceRay(intersectionPoint, reflectedDirection, depth+1, reflectedColor);
}
// #3
if ( intersectionPoint所在的表面为透明面 ) {
计算跟踪光线S在intersectionPoint处的规则透射光线方向transmittedDirection,
TraceRay(intersectionPoint, transmittedDirection, depth+1, transmittedColor);
}
// #summarize
color = localColor + Ks * reflectedColor + Kt * transmittedColor;
}// else
} //else
}
// 局部光照模型计算交点intersectionPoint处的局部光亮度localColor
void Shade(const Object& intersectionObj, const Vec3& intersectionPoint, Color& localColor)
{
确定intersectionObj在intersectionPoint处的单位法向量N,
漫反射系数Kd,
镜面反射系数Ks,
环境反射系数Ka;
localColor = Ka * Ia; //Ia为环境光亮度
for ( 每一个点光源PointLight ) {
计算入射光线单位向量L和虚拟镜面法向单位向量H,
// 由Phong模型计算光源PointLight在intersectionPoint处的漫反射和镜面反射光亮度
localColor += ( Ipointlight * ( Kd * (N.dot(L)) + Ks * (N.dot(H))^n ) );
}
}
E. 光流(Optical Flow)
光流是由观察者和场景之间的[相对运动]引起的视觉场景中物体、表面和边缘的运动模式。一般而言,光流是由于场景中前景目标本身的移动、观测者运动,或者两者的共同运动所产生的。
光流在很多领域中都被用到,例如视频中的运动目标检测,视频压缩等等。
在分析光流时,需要用到两个重要假设: 1.对象的像素强度在连续帧之间不会改变。2.相邻像素具有相似的运动。 下面我们运用这两个假设来推导光流公式。
光流法实际是通过检测图像像素点的强度随时间的变化进而推断出物体移动速度及方向的方法。假设该移动很小,假设该移动很小,那么可以根据泰勒级数得出:
根据假设1,由于移动很小,此时这时对象位移不会改变对象的像素强度:
进一步得出:
最终得出光流公式:
这里的 , 是x, y方向上的速率,或称为I(x, y, t)的光流。而 , , 则是图像(x, y, t)在对应方向上的偏导数。可以看到光流公式中有两个未知量 , , 无法直接求解。因此我们需要更多的外部条件来求解方程。
Lucas-Kanade 方法
求解光流方程有很多方法,其中最着名的便是 Lucas-Kanade方法。它应用了之前提到的第二个假设,即所有相邻像素都将具有相似的运动。对于每一个像素,Lucas-Kanade 方法选取与它相邻的8个像素进行分析。根据假设,所有 9 个像素都有相同的运动。所以现在我们的问题求解 只有2个未知变量的9个方程组。
......
这样的方程组没有唯一解,这里我们使用最 最小二乘拟合 方法获得一个最优近似解。
Lucas-Kanade方法的局限性
由于上述假设和分析都是针对较小的运动,光流算法会受到突然移动的影响。如果两帧图像之间的运动太大,光流法可能会失效。对于这个问题我们可以通过图像金字塔来解决,当我们使用更高层的金字塔图像时,小的运动被去除,大的运动变成小运动。然后再应用 Lucas-Kanade,就可以得到正确的光流。
OpenCV 光流演示:https://docs.opencv.org/3.4/d4/dee/tutorial_optical_flow.html