TLD代码学习(转一)
2015-09-23 14:39
183 查看
最近在看视觉跟踪方面的论文,ZK博士的TLD算法作为跟踪算法的state-of-the-art,当然不得不去拜读下了,看完论文后虽然对作者整体的思想有了一个大致了解,但是对于很多细节却也还是无从得知,好在作者将自己的算法源代码全部开源,这也造福了我们这些无知者的胃口,虽然网上有几个c++版本的源码,但是matlab版本作为作者的原始版本,拿来作研究也是极好的。
通览源代码,个人感觉精髓之处无非两个函数,即:
tldInit();
tldProcessFrame();
这二者一个是用来跟踪前的初始化工作,后者当然是run-time的跟踪工作。我个人的阅读注释也将从这两个函数展开。好了,闲话少叙,下面来看看tldInit()这个函数:
进去第一个函数是
[plain] view
plaincopy
%输入:
%tld.source.bb 用户输入的目标标定框
%size(tld.source.im0.input) 输入图像的尺寸
%tld.model.min_win 目标标定框的长或宽的最小尺寸
%输出:
%tld.grid 是一个6Xn(n表示不同有效尺度下共有多少个的网格)的矩阵,前四行组成列向量表示gridbox的4个顶点,5表示索引,6表示横向的分布点数
% tld.scales 有效尺度下gridbox的高和宽,为2Xm矩阵(m表示有效的尺度的个数)
[tld.grid tld.scales] = bb_scan(tld.source.bb,size(tld.source.im0.input),tld.model.min_win);
再让我们继续进入bb_scan()函数:
[plain] view
plaincopy
bbW = round(bb_width(bb) .* SCALE);%bb_width(bb)*(0.1615~6.1917)矩形框变尺度
bbH = round(bb_height(bb) .* SCALE);
bbSHH = SHIFT * min(bbH,bbH);%取bbH和bbH中最小的元素组成新的矩阵,维数和原来相同 <span style="font-family: Arial, Helvetica, sans-serif;">SHIFT =</span>0.1表示变形后的矩形框的移动步长
bbSHW = SHIFT * min(bbH,bbW);
bbF = [2 2 imsize(2) imsize(1)]';%[2 2 320 240]
bbs = {};
sca = [];
idx = 1;
for i = 1:length(SCALE)
if bbW(i) < MINBB || bbH(i) < MINBB, continue; end
left = round(bbF(1):bbSHW(i):bbF(3)-bbW(i)-1);%2开头,0.1* min(bbH(i),bbW(i))为步长,320- bbW(i)-1为结尾
top = round(bbF(2):bbSHH(i):bbF(4)-bbH(i)-1);%2开头,0.1* min(bbH(i),bbH(i))为步长,240- bbH(i)-1为结尾
grid = ntuples(top,left);%重复功能,维度为2 X length(top)*length(left);grid的每个列向量成为网格顶点的坐标点
if isempty(grid), continue; end
bbs{end+1} = [grid(2,:); ...%省略号为续行,bbs前四行的列向量构成一个矩形网格的四个顶点
grid(1,:); ...
grid(2,:)+bbW(i)-1; ...
grid(1,:)+bbH(i)-1; ...
idx*ones(1,size(grid,2));%坐标点对应的编号,从1开始
length(left)*ones(1,size(grid,2));];%记录横向分布多少个点?
sca = [sca [bbH(i); bbW(i)]];%在原来SCA的基础上增加[bbH(i); bbW(i)]列向量,该列向量表示某个尺度因子下的gridbox的高和宽
idx = idx + 1;
end
bb_out = [];
for i = 1:length(bbs)
bb_out = [bb_out bbs{i}];
end
总之,该函数功能就是扫描输入图像得到很多的gridbox。
下面来到了产生特征的函数
[plain] view
plaincopy
%输入:
%tld.model.num_trees:有10棵分类树
%tld.model.num_features:每棵树里有13个特性,这里是会有13个点对的比较
%输出:
%tld.features: 4*13X10的矩阵,为fern分类器选取的随机点对,4表示一个点对的四个坐标值
tld.features = tldGenerateFeatures(tld.model.num_trees,tld.model.num_features,1);
还是进入一看究竟:
[plain] view
plaincopy
SHI = 1/5;
SCA = 1;
OFF = SHI;
x = repmat(ntuples(0:SHI:1,0:SHI:1),2,1);%ntuples生成网格点坐标2X36矩阵,repmat表示将生成的矩阵作为初始化元素(还是矩阵)
%生成一个2X1的大矩阵,这里是4X36(6*6)矩阵
x = [x x + SHI/2];%前者4X36矩阵,后者矩阵偏移0.1
k = size(x,2);%k = 36*2
%rand(1,k)随机生成都1Xk维随机分布矩阵,范围0~1
r = x; r(3,:) = r(3,:) + (SCA*rand(1,k)+OFF);%x的第3行加上随机的1X72矩阵(0~1)再加上0.2
l = x; l(3,:) = l(3,:) - (SCA*rand(1,k)+OFF);%x的第3行减去随机的1X72矩阵(0~1)再减去0.2
t = x; t(4,:) = t(4,:) - (SCA*rand(1,k)+OFF);%x的第4行减去随机的1X72矩阵(0~1)再减去0.2
b = x; b(4,:) = b(4,:) + (SCA*rand(1,k)+OFF);%x的第4行加上随机的1X72矩阵(0~1)再加上0.2
x = [r l t b];
idx = all(x([1 2],:) < 1 & x([1 2],:) > 0,1);%idx和X维数相同,如果满足条件相应元素为1,否则为0
x = x(:,idx);%挑选满足条件的所有X的列
x(x > 1) = 1;%x元素中大于1的则用1代替此元素
x(x < 0) = 0;%x元素中小于0的则用0代替此元素
numF = size(x,2);%看看现在X还有多少列
x = x(:,randperm(numF));%randperm(numF)从1~numF的数字序列随机打乱,整个表达式是指把X的列打乱
x = x(:,1:nFEAT*nTREES);%取x的前130列,x目前为4x130矩阵
x = reshape(x,4*nFEAT,nTREES);%把X塑造成4*13 X 10 的矩阵
整个函数就是生成10*13个特征点对,坐标用小数表示,表示在box的相对位置,至于为什么随机我也感觉挺困惑的,求解释啊!!
下面再来到fern(0);没什么好说的,来看下面的吧:
[plain] view
plaincopy
%输入:
%tld.source.im0.input:输入的图像
%tld.grid:6Xn(n表示不同有效尺度下共有多少个的网格)的矩阵,gridbox信息
%tld.features:4*13X10的矩阵,为fern分类器选取的随机点对,4表示一个点对的四个坐标值
%tld.scales: 有效尺度下gridbox的高和宽,为2Xm矩阵(m表示有效的尺度的个数)
%输出:
fern(1,tld.source.im0.input,tld.grid,tld.features,tld.scales); % allocate structures
进入CPP的fern
[cpp] view
plaincopy
iHEIGHT = mxGetM(prhs[1]);//240
iWIDTH = mxGetN(prhs[1]);//320
nTREES = mxGetN(mxGetField(prhs[3],0,"x"));//<span style="font-family: Arial, Helvetica, sans-serif;">10</span>
nFEAT = mxGetM(mxGetField(prhs[3],0,"x")) / 4; // feature has 2 points: x1,y1,x2,y2 13
thrN = 0.5 * nTREES;//等于5
nSCALE = mxGetN(prhs[4]);//获得共有多少种尺度的BOX,即有效尺度的个数
IIMG = (double*) malloc(iHEIGHT*iWIDTH*sizeof(double));//积分图像变量准备
IIMG2 = (double*) malloc(iHEIGHT*iWIDTH*sizeof(double));//平方积分图像变量准备
// BBOX
mBBOX = mxGetM(prhs[2]);//等于6
nBBOX = mxGetN(prhs[2]);//等于在各个有效尺度下有多少个网格
BBOX = create_offsets_bbox(mxGetPr(prhs[2]));//创建保存网格数据索引等数据
//prhs[3]是4*13X10的矩阵
//matlab 代码中有f.x = x;f.type = 'forest';见到下面就不怪了
double *x = mxGetPr(mxGetField(prhs[3],0,"x"));//获得特征点的指针
double *s = mxGetPr(prhs[4]);//各种尺度的BOX的尺寸
OFF = create_offsets(s,x);//记录各个特征点对在各种尺度下box中的具体位置
for (int i = 0; i<nTREES; i++) {
WEIGHT.push_back(vector<double>(pow(2.0,nBIT*nFEAT), 0));//nBIT=1,权重分配权2^13
nP.push_back(vector<int>(pow(2.0,nBIT*nFEAT), 0));//nBIT=1,nP分配2^13
nN.push_back(vector<int>(pow(2.0,nBIT*nFEAT), 0));//nBIT=1,nN分配2^13
}
//static vector<vector <double> > WEIGHT;
//static vector<vector <int> > nP;
//static vector<vector <int> > nN;下面见怪不怪,10X 2^13的容器
for (int i = 0; i<nTREES; i++) {
for (int j = 0; j < WEIGHT[i].size(); j++) {
WEIGHT[i].at(j) = 0;
nP[i].at(j) = 0;
nN[i].at(j) = 0;
}
}
来具体看看create_offset_bbox和creater_offset两个函数
[cpp] view
plaincopy
int *offsets = (int*) malloc(BBOX_STEP*nBBOX*sizeof(int));//7*nBBOX*sizeof(int)
int *off = offsets;
for (int i = 0; i < nBBOX; i++) {//nBBOX表示所有的网格数
double *bb = bb0+mBBOX*i;//偏移到下一个网格的属性的向量
//sub2idx(row,col,height) ((int) (floor((row)+0.5) + floor((col)+0.5)*(height)))
//floor不大于
//bb[0]:left bb[1]top bb[2]right bb[3]bottom iHEIGHT:240 bb[4]索引从1开始 bb[5]表示number of left-right bboxes
//以下记录索引是从左到右,从上到下方式
*off++ = sub2idx(bb[1]-1,bb[0]-1,iHEIGHT);//左上顶点索引
*off++ = sub2idx(bb[3]-1,bb[0]-1,iHEIGHT);//左下顶点索引
*off++ = sub2idx(bb[1]-1,bb[2]-1,iHEIGHT);//右上顶点索引
*off++ = sub2idx(bb[3]-1,bb[2]-1,iHEIGHT);//右下顶点索引
*off++ = (int) ((bb[2]-bb[0])*(bb[3]-bb[1]));//记录当前网格的大小
*off++ = (int) (bb[4]-1)*2*nFEAT*nTREES; // pointer to features for this scale
*off++ = bb[5]; // number of left-right bboxes, will be used for searching neighbours
}
return offsets;
[cpp] view
plaincopy
int *offsets = (int*) malloc(nSCALE*nTREES*nFEAT*2*sizeof(int));//
int *off = offsets;
for (int k = 0; k < nSCALE; k++){//共有多少种尺度的BOX
double *scale = scale0+2*k;//scale0第一种尺度的尺寸信息,*2表示列向量有2维,即高和宽,此表达式表示偏移到下一个box的尺寸向量
for (int i = 0; i < nTREES; i++) {//10
for (int j = 0; j < nFEAT; j++) {//13
//x0 4*13 X 10 的矩阵
double *x = x0 +4*j + (4*nFEAT)*i;//4*j因为每个feature是一个4维列向量,(4*13)*i即下一棵树
//sub2idx(row,col,height) ((int) (floor((row)+0.5) + floor((col)+0.5)*(height)))
//scale[1]宽,scale[0]高,x[0]x坐标,x[1]y坐标
*off++ = sub2idx((scale[0]-1)*x[1],(scale[1]-1)*x[0],iHEIGHT);//记录第一个点在该尺度BOX的具体位置,并转化为索引
*off++ = sub2idx((scale[0]-1)*x[3],(scale[1]-1)*x[2],iHEIGHT);//记录第二个点在该尺度BOX的具体位置,并转化为索引
}
}
}
今天继续,下面是开始要生成正负例来训练分类器了,首先:
[cpp] view
plaincopy
// TRAIN DETECTOR ==========================================================
// Initialize structures
tld.imgsize = size(tld.source.im0.input);
//为fern准备的训练集
tld.X = cell(1,length(tld.source.idx)); //training data for fern
tld.Y = cell(1,length(tld.source.idx));
%为nearest neighbor准备的训练集
tld.pEx = cell(1,length(tld.source.idx)); // training data for NN
tld.nEx = cell(1,length(tld.source.idx));
//输入:
//tld.source.bb:用户目标标定框
//tld.grid: 生成的gridbox信息矩阵
//输出:
// overlap一维行向量,记录GRID中的各个gridbox与用户目标标定框的重叠率
overlap = bb_overlap(tld.source.bb,tld.grid);
进入bb_overlap来看一下:
[cpp] view
plaincopy
// Input
double *bb1 = mxGetPr(prhs[0]); int M1 = mxGetM(prhs[0]); int N1 = mxGetN(prhs[0]);//4X1
double *bb2 = mxGetPr(prhs[1]); int M2 = mxGetM(prhs[1]); int N2 = mxGetN(prhs[1]);//6Xn(n表示gridbox总数)
// Output
if (N1 == 0 || N2 == 0) {
N1 = 0; N2 = 0;
}
plhs[0] = mxCreateDoubleMatrix(N1, N2, mxREAL);//创建输出矩阵,1Xgridbox的数量
double *out = mxGetPr(plhs[0]);
for (int j = 0; j < N2; j++) {//gridbox的数量
for (int i = 0; i < N1; i++) {//1
*out++ = bb_overlap(bb1 + M1*i, bb2 + M2*j);//计算重叠度
}
}
[cpp] view
plaincopy
double bb_overlap(double *bb1, double *bb2) {
if (bb1[0] > bb2[2]) { return 0.0; }//判断如果两个矩形没有相交部分,重叠度就为0;
if (bb1[1] > bb2[3]) { return 0.0; }
if (bb1[2] < bb2[0]) { return 0.0; }
if (bb1[3] < bb2[1]) { return 0.0; }
double colInt = min(bb1[2], bb2[2]) - max(bb1[0], bb2[0]) + 1;//求相交矩形的宽和高
double rowInt = min(bb1[3], bb2[3]) - max(bb1[1], bb2[1]) + 1;
double intersection = colInt * rowInt;//相交面积
double area1 = (bb1[2]-bb1[0]+1)*(bb1[3]-bb1[1]+1);//分别求两个输入矩形的面积
double area2 = (bb2[2]-bb2[0]+1)*(bb2[3]-bb2[1]+1);
return intersection / (area1 + area2 - intersection);//求重叠率
}
再接着
[cpp] view
plaincopy
//输入:
//tld.img{1}.input:输入图像,第一帧
//tld.bb(:,1):用户目标标定框
//输出:
//tld.target:目标标定框中特定的图像
tld.target = img_patch(tld.img{1}.input,tld.bb(:,1));
进入img_patch,这个函数比较庞大,先看其中用到的一部分:
[cpp] view
plaincopy
//如果4个坐标值都是整数
if sum(abs(round(bb)-bb))==0
L = max([1 bb(1)]);
T = max([1 bb(2)]);
R = min([size(img,2) bb(3)]);
B = min([size(img,1) bb(4)]);
patch = img(T:B,L:R);//在不超过画面尺寸和小于1x1的情况下,取出BB框出的画面
% Sub-pixel accuracy
else
cp = 0.5 * [bb(1)+bb(3); bb(2)+bb(4)]-1;//bbox的中心坐标 center point
%[1 0 -cp(1)]
%[0 1 -cp(2)]
%[0 0 1 ]
H = [1 0 -cp(1); 0 1 -cp(2); 0 0 1];
bbW = bb(3,:)-bb(1,:);//宽
bbH = bb(4,:)-bb(2,:);//高
if bbW <= 0 || bbH <= 0
patch = [];
return;
end
box = [-bbW/2 bbW/2 -bbH/2 bbH/2];
if size(img,3) == 3//如果图像有三个通道,即判断图片是否为真彩色
for i = 1:3
P = warp(img(:,:,i),inv(H),box);
patch(:,:,i) = uint8(P);
end
else
patch = warp(img,inv(H),box);//inv(H)=[1 0 cp(1); 0 1 cp(2); 0 0 1];平移变换
patch = uint8(patch);
end
end
上面的函数功能就是对BB区域的图像提取,但是有针对坐标为整数和小数的处理,这里应该只用到整数部分,但至于小数坐标的处理跟踪了一下代码,发现是对图像作了平移的仿射变换,但是至于为什么要这么做,我也不理解,感觉直接舍去小数部分问题应该也不大吧(个人理解,没有看懂)。
好了下面开始产生正训练样本了:
[cpp] view
plaincopy
//输入:
//overlap:一维行向量,记录GRID中的各个gridbox与用户目标标定框的重叠率
//tld.p_par_init:opt.p_par_init= struct('num_closest',10,'num_warps',20,'noise',5,'angle',20,'shift',0.02,'scale',0.02);
//输出:
//pX:10 X length(idxP)*20 (length(idxP)<=10,20为'num_warps',20)的矩阵列向量表示一个gridbox的10棵树上的13位有效的code
//pEx:225X1的列向量,各元素值为原像素值减去像素均值
//bbP:最靠近BBOX的的gridbox,列向量表示该box的4个顶点
[pX,pEx,bbP] = tldGeneratePositiveData(tld,overlap,tld.img{1},tld.p_par_init);
pY = ones(1,size(pX,2));%1 X length(idxP)*20
这个函数也是比较大的,但是还要耐心的往下看啊
[cpp] view
plaincopy
pX = [];
pEx = [];
// Get closest bbox
[~,idxP] = max(overlap);//表示行不管,只取列,整个表达式表示最大overlap<span style="font-family: Arial, Helvetica, sans-serif;">所对应的列,一维</span>
bbP0 = tld.grid(1:4,idxP);//1~4表示矩阵的4个顶点分布在四行,此取最靠近BBOX的的gridbox
// Get overlapping bboxes
idxP = find(overlap > 0.6);//返回overlap > 0.6所对应的列索引
if length(idxP) > p_par.num_closest//如果overlap > 0.6的gridbox数大于10
[~,sIdx] = sort(overlap(idxP),'descend'); //降序排序
idxP = idxP(sIdx(1:p_par.num_closest));//取前p_par.num_closest个最大重叠度的bboxes所在的列
end
bbP = tld.grid(:,idxP);//取出10个最大重叠度的gridboxes
if isempty(bbP), return; end
% Get hull
bbH = bb_hull(bbP);%得到能包围所有bbp中boxes<span style="font-family: Arial, Helvetica, sans-serif;">的最小矩形</span>
cols = bbH(1):bbH(3);
rows = bbH(2):bbH(4);
im1 = im0;
//返回一个225x1(pEx)的列向量,各元素值为原像素值减去像素均值
pEx = tldGetPattern(im1,bbP0,tld.model.patchsize);//
if tld.model.fliplr
pEx = [pEx tldGetPattern(im1,bbP0,tld.model.patchsize,1)];
end
//返回20个正例
for i = 1:p_par.num_warps//p_par.num_warps=20
if i > 1
randomize = rand; // Sets the internal randomizer to the same state
//patch_input = img_patch(im0.input,bbH,randomize,p_par);
//返回将画面进行仿射变换后的patch
patch_blur = img_patch(im0.blur,bbH,randomize,p_par);//bbH包围所有bbp中bboxes的最小矩形
//这个很重要,保证在C调用里的偏移的起始地址可以是一样的
im1.blur(rows,cols) = patch_blur;//把仿射变换后的图像放到原图像对应的位置(<span style="font-family: Arial, Helvetica, sans-serif;">能包围所有bbp中boxes</span><span style="font-family: Arial, Helvetica, sans-serif;">的最小矩形</span>)
//im1.input(rows,cols) = patch_input;
end
// Measures on blured image
//单次返回10Xlength(idxP)的矩阵,列向量表示一个gridbox的10棵树上的13位code,
//最后返回10Xlength(idxP)*20的矩阵
pX = [pX fern(5,im1,idxP,0)];//idxP :overlap > 0.6所对应的列索引
// Measures on input image
//pEx(:,i) = tldGetPattern(im1,bbP0,tld.model.patchsize);
//pEx = [pEx tldGetPattern(im1,tld.grid(1:4,idxP),tld.model.patchsize)];
end
当然这个函数是不能这么草草了事的,还有三大函数需要进一步细看:
1.tldGetPattern()
[cpp] view
plaincopy
nBB = size(bb,2);//得到bbp0(最靠近BBOX的gridbox)的列,值为1
pattern = zeros(prod(patchsize),nBB);//15*15 X 1 矩阵,返回矩阵
if ~exist('flip','var')
flip= 0;
end
// for every bounding box
for i = 1:nBB//1
// sample patch
patch = img_patch(img.input,bb(:,i));//取出对应框中的图像
// flip if needed
if flip
patch = fliplr(patch);
end
// normalize size to 'patchsize' and nomalize intensities to ZMUV
//返回一个225x1的列向量,各元素值为原像素值减去像素均值
pattern(:,i) = tldPatch2Pattern(patch,patchsize);//patch压缩变换到patchsize大小,然后将各个元素减去元素均值
end
切入到tldPatch2Pattern看一眼:
[cpp] view
plaincopy
patch = imresize(patch,patchsize); // 'bilinear' is faster
pattern = double(patch(:));//此时变成225X1的矩阵
pattern = pattern - mean(pattern);//mean(pattern)求各列向量的均值
2.img_patch()(4个传参)
[cpp] view
plaincopy
rand('state',randomize);
randn('state',randomize);
//'noise',5,'angle',20,'shift',0.02,'scale',0.02;
NOISE = p_par.noise;
ANGLE = p_par.angle;
SCALE = p_par.scale;
SHIFT = p_par.shift;
cp = bb_center(bb)-1;//HULL矩形的中心
Sh1 = [1 0 -cp(1); 0 1 -cp(2); 0 0 1];
sca = 1-SCALE*(rand-0.5);%0.99~1.01
//[0.99~1.01 ]
//[ 0.99~1.01 ]
//[ 1 ]
Sca = diag([sca sca 1]);
ang = 2*pi/360*ANGLE*(rand-0.5);//-10 ~ 10度 实际为弧度
ca = cos(ang);
sa = sin(ang);
Ang = [ca, -sa; sa, ca];
Ang(end+1,end+1) = 1;
shR = SHIFT*bb_height(bb)*(rand-0.5);//-0.01~1.01*bb_height(bb)
shC = SHIFT*bb_width(bb)*(rand-0.5);//-0.01~1.01*bb_width(bb)
Sh2 = [1 0 shC; 0 1 shR; 0 0 1];
bbW = bb_width(bb)-1;
bbH = bb_height(bb)-1;
box = [-bbW/2 bbW/2 -bbH/2 bbH/2];
H = Sh2*Ang*Sca*Sh1;
bbsize = bb_size(bb);
patch = uint8(warp(img,inv(H),box) + NOISE*randn(bbsize(1),bbsize(2)));//给图像造成5的高斯噪声
以上的代码注释就少了,因为全都是关于仿射变换的,具体可以参看仿射变换,大体就是作者在论文中提到的(shift+-1%,scale +-1%, in-plane rotation +-10度)用来提高训练样本的多样性。
3.fern()(第一个传参为5,获得模式)
[cpp] view
plaincopy
unsigned char *input = (unsigned char*) mxGetPr(mxGetField(prhs[1],0,"input"));
unsigned char *blur = (unsigned char*) mxGetPr(mxGetField(prhs[1],0,"blur"));//获得仿射变换后的patch
//if (mxGetM(prhs[1])!=iHEIGHT) { mexPrintf("fern: wrong input image.\n"); return; }
// bbox indexes
double *idx = mxGetPr(prhs[2]);//bbp所对应的列索引
int numIdx = mxGetM(prhs[2]) * mxGetN(prhs[2]);//1 X (<=10)
// minimal variance
double minVar = *mxGetPr(prhs[3]);//minVar=0
if (minVar > 0) {
iimg(input,IIMG,iHEIGHT,iWIDTH);//返回IIMG,是图像进行矩形积分后的结果(运行不到这)
iimg2(input,IIMG2,iHEIGHT,iWIDTH);//返回IIMG,是图像进行矩形平方积分后的结果(运行不到这)
}
// output patterns
//创建输出矩阵:10X(<=10)
plhs[0] = mxCreateDoubleMatrix(nTREES,numIdx,mxREAL);
double *patt = mxGetPr(plhs[0]);
//创建输出矩阵:1 X(<=10)
plhs[1] = mxCreateDoubleMatrix(1,numIdx,mxREAL);
double *status = mxGetPr(plhs[1]);
for (int j = 0; j < numIdx; j++) {//(<=10)
if (minVar > 0) {
double bboxvar = bbox_var_offset(IIMG,IIMG2,BBOX+j*BBOX_STEP);//BBOX保存网格数据索引等数据(运行不到这)
//E(p^2)-E^2(p)
if (bboxvar < minVar) { continue; }(运行不到这)
}
status[j] = 1;
double *tPatt = patt + j*nTREES;
for (int i = 0; i < nTREES; i++) {//10
//返回对应gridbox及对应树的13位有效的像素比较码
tPatt[i] = (double) measure_tree_offset(blur, idx[j]-1, i);//idx:bbp
}
}
return;
进入measure_tree_offset
[cpp] view
plaincopy
int index = 0;
int *bbox = BBOX + idx_bbox*BBOX_STEP;//BBOX存储gridbox的索引等信息BBOX_STEP=7(因为grid的行为6)
//OFF + bbox[5],该表达式表示该gridbox的特征点信息在OFF的偏移,bbox[5]表示图像横向上多少个网格点
[cpp] view
plaincopy
//OFF = create_offsets(s,x);//记录各个特征点在各种尺度下box中的具体位置
int *off = OFF + bbox[5] + idx_tree*2*nFEAT;//OFF存储特征点在各个尺度框下的分布位置等
for (int i=0; i<nFEAT; i++) {//13
index<<=1;
//off[0]为特征点的x坐标,off[1]为特征点的y坐标,bbox[0]为该gridbox在图画中的位置
int fp0 = img[off[0]+bbox[0]];
int fp1 = img[off[1]+bbox[0]];
if (fp0>fp1) { index |= 1;}//两个像素点比较并置位相应CODE
off += 2;//移到下一个点对
}
return index;
看完上面,真的有点累啊,算了,把负例也看下好了,简单看了下,代码不算太多:
[cpp] view
plaincopy
// Correct initial bbox
tld.bb(:,1) = bbP(1:4,:);//最靠近BBOX的的gridbox
// Variance threshold
tld.var = var(pEx(:,1)) / 2;//var计算方差,这里即求各个数平方和的平均数
// disp(['Variance : ' num2str(tld.var)]);
// Generate Negative Examples
//nx:patch variance 挑出合适的patches,并提取fern特征赋给nx,
//nEx返回一个225x100(nEx)的矩阵,列向量各元素值为原像素值减去像素均值,100为num_patches
//输入:
//overlap:一维行向量,记录GRID中的各个gridbox与用户目标标定框的重叠率
//输出:
//nx:patch variance 挑出合适的patches,并提取fern特征赋给nx
//nEx:一个225x100(nEx)的矩阵,列向量各元素值为原像素值减去像素均值,100为num_patches
[nX,nEx] = tldGenerateNegativeData(tld,overlap,tld.img{1});
再进
[cpp] view
plaincopy
// Measure patterns on all bboxes that are far from initial bbox
//opt.n_par = struct('overlap',0.2,'num_patches',100);
idxN = find(overlap<tld.n_par.overlap);//overlap < 0.2
[nX,status] = fern(5,img,idxN,tld.var/2);//此函数通过patch variance剔除一批,剩下的进入fern特征码提取
idxN = idxN(status==1); // bboxes far and with big variance,注意C++代码中的status[j] = 1;一句
nX = nX(:,status==1);//选出进入第二级分类器的负样本
// Randomly select 'num_patches' bboxes and measure patches
idx = randvalues(1:length(idxN),tld.n_par.num_patches);//'num_patches',100应该是随机取出100个gridbox
bb = tld.grid(:,idxN(idx));
nEx = tldGetPattern(img,bb,tld.model.patchsize);//不复注解
再进入fern(5,...)因为有tld.var/2,执行稍有不同,请参见上面就行。
好了,至此已经为分类器的训练产生了可用的正例和负例了。
通览源代码,个人感觉精髓之处无非两个函数,即:
tldInit();
tldProcessFrame();
这二者一个是用来跟踪前的初始化工作,后者当然是run-time的跟踪工作。我个人的阅读注释也将从这两个函数展开。好了,闲话少叙,下面来看看tldInit()这个函数:
进去第一个函数是
[plain] view
plaincopy
%输入:
%tld.source.bb 用户输入的目标标定框
%size(tld.source.im0.input) 输入图像的尺寸
%tld.model.min_win 目标标定框的长或宽的最小尺寸
%输出:
%tld.grid 是一个6Xn(n表示不同有效尺度下共有多少个的网格)的矩阵,前四行组成列向量表示gridbox的4个顶点,5表示索引,6表示横向的分布点数
% tld.scales 有效尺度下gridbox的高和宽,为2Xm矩阵(m表示有效的尺度的个数)
[tld.grid tld.scales] = bb_scan(tld.source.bb,size(tld.source.im0.input),tld.model.min_win);
再让我们继续进入bb_scan()函数:
[plain] view
plaincopy
bbW = round(bb_width(bb) .* SCALE);%bb_width(bb)*(0.1615~6.1917)矩形框变尺度
bbH = round(bb_height(bb) .* SCALE);
bbSHH = SHIFT * min(bbH,bbH);%取bbH和bbH中最小的元素组成新的矩阵,维数和原来相同 <span style="font-family: Arial, Helvetica, sans-serif;">SHIFT =</span>0.1表示变形后的矩形框的移动步长
bbSHW = SHIFT * min(bbH,bbW);
bbF = [2 2 imsize(2) imsize(1)]';%[2 2 320 240]
bbs = {};
sca = [];
idx = 1;
for i = 1:length(SCALE)
if bbW(i) < MINBB || bbH(i) < MINBB, continue; end
left = round(bbF(1):bbSHW(i):bbF(3)-bbW(i)-1);%2开头,0.1* min(bbH(i),bbW(i))为步长,320- bbW(i)-1为结尾
top = round(bbF(2):bbSHH(i):bbF(4)-bbH(i)-1);%2开头,0.1* min(bbH(i),bbH(i))为步长,240- bbH(i)-1为结尾
grid = ntuples(top,left);%重复功能,维度为2 X length(top)*length(left);grid的每个列向量成为网格顶点的坐标点
if isempty(grid), continue; end
bbs{end+1} = [grid(2,:); ...%省略号为续行,bbs前四行的列向量构成一个矩形网格的四个顶点
grid(1,:); ...
grid(2,:)+bbW(i)-1; ...
grid(1,:)+bbH(i)-1; ...
idx*ones(1,size(grid,2));%坐标点对应的编号,从1开始
length(left)*ones(1,size(grid,2));];%记录横向分布多少个点?
sca = [sca [bbH(i); bbW(i)]];%在原来SCA的基础上增加[bbH(i); bbW(i)]列向量,该列向量表示某个尺度因子下的gridbox的高和宽
idx = idx + 1;
end
bb_out = [];
for i = 1:length(bbs)
bb_out = [bb_out bbs{i}];
end
总之,该函数功能就是扫描输入图像得到很多的gridbox。
下面来到了产生特征的函数
[plain] view
plaincopy
%输入:
%tld.model.num_trees:有10棵分类树
%tld.model.num_features:每棵树里有13个特性,这里是会有13个点对的比较
%输出:
%tld.features: 4*13X10的矩阵,为fern分类器选取的随机点对,4表示一个点对的四个坐标值
tld.features = tldGenerateFeatures(tld.model.num_trees,tld.model.num_features,1);
还是进入一看究竟:
[plain] view
plaincopy
SHI = 1/5;
SCA = 1;
OFF = SHI;
x = repmat(ntuples(0:SHI:1,0:SHI:1),2,1);%ntuples生成网格点坐标2X36矩阵,repmat表示将生成的矩阵作为初始化元素(还是矩阵)
%生成一个2X1的大矩阵,这里是4X36(6*6)矩阵
x = [x x + SHI/2];%前者4X36矩阵,后者矩阵偏移0.1
k = size(x,2);%k = 36*2
%rand(1,k)随机生成都1Xk维随机分布矩阵,范围0~1
r = x; r(3,:) = r(3,:) + (SCA*rand(1,k)+OFF);%x的第3行加上随机的1X72矩阵(0~1)再加上0.2
l = x; l(3,:) = l(3,:) - (SCA*rand(1,k)+OFF);%x的第3行减去随机的1X72矩阵(0~1)再减去0.2
t = x; t(4,:) = t(4,:) - (SCA*rand(1,k)+OFF);%x的第4行减去随机的1X72矩阵(0~1)再减去0.2
b = x; b(4,:) = b(4,:) + (SCA*rand(1,k)+OFF);%x的第4行加上随机的1X72矩阵(0~1)再加上0.2
x = [r l t b];
idx = all(x([1 2],:) < 1 & x([1 2],:) > 0,1);%idx和X维数相同,如果满足条件相应元素为1,否则为0
x = x(:,idx);%挑选满足条件的所有X的列
x(x > 1) = 1;%x元素中大于1的则用1代替此元素
x(x < 0) = 0;%x元素中小于0的则用0代替此元素
numF = size(x,2);%看看现在X还有多少列
x = x(:,randperm(numF));%randperm(numF)从1~numF的数字序列随机打乱,整个表达式是指把X的列打乱
x = x(:,1:nFEAT*nTREES);%取x的前130列,x目前为4x130矩阵
x = reshape(x,4*nFEAT,nTREES);%把X塑造成4*13 X 10 的矩阵
整个函数就是生成10*13个特征点对,坐标用小数表示,表示在box的相对位置,至于为什么随机我也感觉挺困惑的,求解释啊!!
下面再来到fern(0);没什么好说的,来看下面的吧:
[plain] view
plaincopy
%输入:
%tld.source.im0.input:输入的图像
%tld.grid:6Xn(n表示不同有效尺度下共有多少个的网格)的矩阵,gridbox信息
%tld.features:4*13X10的矩阵,为fern分类器选取的随机点对,4表示一个点对的四个坐标值
%tld.scales: 有效尺度下gridbox的高和宽,为2Xm矩阵(m表示有效的尺度的个数)
%输出:
fern(1,tld.source.im0.input,tld.grid,tld.features,tld.scales); % allocate structures
进入CPP的fern
[cpp] view
plaincopy
iHEIGHT = mxGetM(prhs[1]);//240
iWIDTH = mxGetN(prhs[1]);//320
nTREES = mxGetN(mxGetField(prhs[3],0,"x"));//<span style="font-family: Arial, Helvetica, sans-serif;">10</span>
nFEAT = mxGetM(mxGetField(prhs[3],0,"x")) / 4; // feature has 2 points: x1,y1,x2,y2 13
thrN = 0.5 * nTREES;//等于5
nSCALE = mxGetN(prhs[4]);//获得共有多少种尺度的BOX,即有效尺度的个数
IIMG = (double*) malloc(iHEIGHT*iWIDTH*sizeof(double));//积分图像变量准备
IIMG2 = (double*) malloc(iHEIGHT*iWIDTH*sizeof(double));//平方积分图像变量准备
// BBOX
mBBOX = mxGetM(prhs[2]);//等于6
nBBOX = mxGetN(prhs[2]);//等于在各个有效尺度下有多少个网格
BBOX = create_offsets_bbox(mxGetPr(prhs[2]));//创建保存网格数据索引等数据
//prhs[3]是4*13X10的矩阵
//matlab 代码中有f.x = x;f.type = 'forest';见到下面就不怪了
double *x = mxGetPr(mxGetField(prhs[3],0,"x"));//获得特征点的指针
double *s = mxGetPr(prhs[4]);//各种尺度的BOX的尺寸
OFF = create_offsets(s,x);//记录各个特征点对在各种尺度下box中的具体位置
for (int i = 0; i<nTREES; i++) {
WEIGHT.push_back(vector<double>(pow(2.0,nBIT*nFEAT), 0));//nBIT=1,权重分配权2^13
nP.push_back(vector<int>(pow(2.0,nBIT*nFEAT), 0));//nBIT=1,nP分配2^13
nN.push_back(vector<int>(pow(2.0,nBIT*nFEAT), 0));//nBIT=1,nN分配2^13
}
//static vector<vector <double> > WEIGHT;
//static vector<vector <int> > nP;
//static vector<vector <int> > nN;下面见怪不怪,10X 2^13的容器
for (int i = 0; i<nTREES; i++) {
for (int j = 0; j < WEIGHT[i].size(); j++) {
WEIGHT[i].at(j) = 0;
nP[i].at(j) = 0;
nN[i].at(j) = 0;
}
}
来具体看看create_offset_bbox和creater_offset两个函数
[cpp] view
plaincopy
int *offsets = (int*) malloc(BBOX_STEP*nBBOX*sizeof(int));//7*nBBOX*sizeof(int)
int *off = offsets;
for (int i = 0; i < nBBOX; i++) {//nBBOX表示所有的网格数
double *bb = bb0+mBBOX*i;//偏移到下一个网格的属性的向量
//sub2idx(row,col,height) ((int) (floor((row)+0.5) + floor((col)+0.5)*(height)))
//floor不大于
//bb[0]:left bb[1]top bb[2]right bb[3]bottom iHEIGHT:240 bb[4]索引从1开始 bb[5]表示number of left-right bboxes
//以下记录索引是从左到右,从上到下方式
*off++ = sub2idx(bb[1]-1,bb[0]-1,iHEIGHT);//左上顶点索引
*off++ = sub2idx(bb[3]-1,bb[0]-1,iHEIGHT);//左下顶点索引
*off++ = sub2idx(bb[1]-1,bb[2]-1,iHEIGHT);//右上顶点索引
*off++ = sub2idx(bb[3]-1,bb[2]-1,iHEIGHT);//右下顶点索引
*off++ = (int) ((bb[2]-bb[0])*(bb[3]-bb[1]));//记录当前网格的大小
*off++ = (int) (bb[4]-1)*2*nFEAT*nTREES; // pointer to features for this scale
*off++ = bb[5]; // number of left-right bboxes, will be used for searching neighbours
}
return offsets;
[cpp] view
plaincopy
int *offsets = (int*) malloc(nSCALE*nTREES*nFEAT*2*sizeof(int));//
int *off = offsets;
for (int k = 0; k < nSCALE; k++){//共有多少种尺度的BOX
double *scale = scale0+2*k;//scale0第一种尺度的尺寸信息,*2表示列向量有2维,即高和宽,此表达式表示偏移到下一个box的尺寸向量
for (int i = 0; i < nTREES; i++) {//10
for (int j = 0; j < nFEAT; j++) {//13
//x0 4*13 X 10 的矩阵
double *x = x0 +4*j + (4*nFEAT)*i;//4*j因为每个feature是一个4维列向量,(4*13)*i即下一棵树
//sub2idx(row,col,height) ((int) (floor((row)+0.5) + floor((col)+0.5)*(height)))
//scale[1]宽,scale[0]高,x[0]x坐标,x[1]y坐标
*off++ = sub2idx((scale[0]-1)*x[1],(scale[1]-1)*x[0],iHEIGHT);//记录第一个点在该尺度BOX的具体位置,并转化为索引
*off++ = sub2idx((scale[0]-1)*x[3],(scale[1]-1)*x[2],iHEIGHT);//记录第二个点在该尺度BOX的具体位置,并转化为索引
}
}
}
今天继续,下面是开始要生成正负例来训练分类器了,首先:
[cpp] view
plaincopy
// TRAIN DETECTOR ==========================================================
// Initialize structures
tld.imgsize = size(tld.source.im0.input);
//为fern准备的训练集
tld.X = cell(1,length(tld.source.idx)); //training data for fern
tld.Y = cell(1,length(tld.source.idx));
%为nearest neighbor准备的训练集
tld.pEx = cell(1,length(tld.source.idx)); // training data for NN
tld.nEx = cell(1,length(tld.source.idx));
//输入:
//tld.source.bb:用户目标标定框
//tld.grid: 生成的gridbox信息矩阵
//输出:
// overlap一维行向量,记录GRID中的各个gridbox与用户目标标定框的重叠率
overlap = bb_overlap(tld.source.bb,tld.grid);
进入bb_overlap来看一下:
[cpp] view
plaincopy
// Input
double *bb1 = mxGetPr(prhs[0]); int M1 = mxGetM(prhs[0]); int N1 = mxGetN(prhs[0]);//4X1
double *bb2 = mxGetPr(prhs[1]); int M2 = mxGetM(prhs[1]); int N2 = mxGetN(prhs[1]);//6Xn(n表示gridbox总数)
// Output
if (N1 == 0 || N2 == 0) {
N1 = 0; N2 = 0;
}
plhs[0] = mxCreateDoubleMatrix(N1, N2, mxREAL);//创建输出矩阵,1Xgridbox的数量
double *out = mxGetPr(plhs[0]);
for (int j = 0; j < N2; j++) {//gridbox的数量
for (int i = 0; i < N1; i++) {//1
*out++ = bb_overlap(bb1 + M1*i, bb2 + M2*j);//计算重叠度
}
}
[cpp] view
plaincopy
double bb_overlap(double *bb1, double *bb2) {
if (bb1[0] > bb2[2]) { return 0.0; }//判断如果两个矩形没有相交部分,重叠度就为0;
if (bb1[1] > bb2[3]) { return 0.0; }
if (bb1[2] < bb2[0]) { return 0.0; }
if (bb1[3] < bb2[1]) { return 0.0; }
double colInt = min(bb1[2], bb2[2]) - max(bb1[0], bb2[0]) + 1;//求相交矩形的宽和高
double rowInt = min(bb1[3], bb2[3]) - max(bb1[1], bb2[1]) + 1;
double intersection = colInt * rowInt;//相交面积
double area1 = (bb1[2]-bb1[0]+1)*(bb1[3]-bb1[1]+1);//分别求两个输入矩形的面积
double area2 = (bb2[2]-bb2[0]+1)*(bb2[3]-bb2[1]+1);
return intersection / (area1 + area2 - intersection);//求重叠率
}
再接着
[cpp] view
plaincopy
//输入:
//tld.img{1}.input:输入图像,第一帧
//tld.bb(:,1):用户目标标定框
//输出:
//tld.target:目标标定框中特定的图像
tld.target = img_patch(tld.img{1}.input,tld.bb(:,1));
进入img_patch,这个函数比较庞大,先看其中用到的一部分:
[cpp] view
plaincopy
//如果4个坐标值都是整数
if sum(abs(round(bb)-bb))==0
L = max([1 bb(1)]);
T = max([1 bb(2)]);
R = min([size(img,2) bb(3)]);
B = min([size(img,1) bb(4)]);
patch = img(T:B,L:R);//在不超过画面尺寸和小于1x1的情况下,取出BB框出的画面
% Sub-pixel accuracy
else
cp = 0.5 * [bb(1)+bb(3); bb(2)+bb(4)]-1;//bbox的中心坐标 center point
%[1 0 -cp(1)]
%[0 1 -cp(2)]
%[0 0 1 ]
H = [1 0 -cp(1); 0 1 -cp(2); 0 0 1];
bbW = bb(3,:)-bb(1,:);//宽
bbH = bb(4,:)-bb(2,:);//高
if bbW <= 0 || bbH <= 0
patch = [];
return;
end
box = [-bbW/2 bbW/2 -bbH/2 bbH/2];
if size(img,3) == 3//如果图像有三个通道,即判断图片是否为真彩色
for i = 1:3
P = warp(img(:,:,i),inv(H),box);
patch(:,:,i) = uint8(P);
end
else
patch = warp(img,inv(H),box);//inv(H)=[1 0 cp(1); 0 1 cp(2); 0 0 1];平移变换
patch = uint8(patch);
end
end
上面的函数功能就是对BB区域的图像提取,但是有针对坐标为整数和小数的处理,这里应该只用到整数部分,但至于小数坐标的处理跟踪了一下代码,发现是对图像作了平移的仿射变换,但是至于为什么要这么做,我也不理解,感觉直接舍去小数部分问题应该也不大吧(个人理解,没有看懂)。
好了下面开始产生正训练样本了:
[cpp] view
plaincopy
//输入:
//overlap:一维行向量,记录GRID中的各个gridbox与用户目标标定框的重叠率
//tld.p_par_init:opt.p_par_init= struct('num_closest',10,'num_warps',20,'noise',5,'angle',20,'shift',0.02,'scale',0.02);
//输出:
//pX:10 X length(idxP)*20 (length(idxP)<=10,20为'num_warps',20)的矩阵列向量表示一个gridbox的10棵树上的13位有效的code
//pEx:225X1的列向量,各元素值为原像素值减去像素均值
//bbP:最靠近BBOX的的gridbox,列向量表示该box的4个顶点
[pX,pEx,bbP] = tldGeneratePositiveData(tld,overlap,tld.img{1},tld.p_par_init);
pY = ones(1,size(pX,2));%1 X length(idxP)*20
这个函数也是比较大的,但是还要耐心的往下看啊
[cpp] view
plaincopy
pX = [];
pEx = [];
// Get closest bbox
[~,idxP] = max(overlap);//表示行不管,只取列,整个表达式表示最大overlap<span style="font-family: Arial, Helvetica, sans-serif;">所对应的列,一维</span>
bbP0 = tld.grid(1:4,idxP);//1~4表示矩阵的4个顶点分布在四行,此取最靠近BBOX的的gridbox
// Get overlapping bboxes
idxP = find(overlap > 0.6);//返回overlap > 0.6所对应的列索引
if length(idxP) > p_par.num_closest//如果overlap > 0.6的gridbox数大于10
[~,sIdx] = sort(overlap(idxP),'descend'); //降序排序
idxP = idxP(sIdx(1:p_par.num_closest));//取前p_par.num_closest个最大重叠度的bboxes所在的列
end
bbP = tld.grid(:,idxP);//取出10个最大重叠度的gridboxes
if isempty(bbP), return; end
% Get hull
bbH = bb_hull(bbP);%得到能包围所有bbp中boxes<span style="font-family: Arial, Helvetica, sans-serif;">的最小矩形</span>
cols = bbH(1):bbH(3);
rows = bbH(2):bbH(4);
im1 = im0;
//返回一个225x1(pEx)的列向量,各元素值为原像素值减去像素均值
pEx = tldGetPattern(im1,bbP0,tld.model.patchsize);//
if tld.model.fliplr
pEx = [pEx tldGetPattern(im1,bbP0,tld.model.patchsize,1)];
end
//返回20个正例
for i = 1:p_par.num_warps//p_par.num_warps=20
if i > 1
randomize = rand; // Sets the internal randomizer to the same state
//patch_input = img_patch(im0.input,bbH,randomize,p_par);
//返回将画面进行仿射变换后的patch
patch_blur = img_patch(im0.blur,bbH,randomize,p_par);//bbH包围所有bbp中bboxes的最小矩形
//这个很重要,保证在C调用里的偏移的起始地址可以是一样的
im1.blur(rows,cols) = patch_blur;//把仿射变换后的图像放到原图像对应的位置(<span style="font-family: Arial, Helvetica, sans-serif;">能包围所有bbp中boxes</span><span style="font-family: Arial, Helvetica, sans-serif;">的最小矩形</span>)
//im1.input(rows,cols) = patch_input;
end
// Measures on blured image
//单次返回10Xlength(idxP)的矩阵,列向量表示一个gridbox的10棵树上的13位code,
//最后返回10Xlength(idxP)*20的矩阵
pX = [pX fern(5,im1,idxP,0)];//idxP :overlap > 0.6所对应的列索引
// Measures on input image
//pEx(:,i) = tldGetPattern(im1,bbP0,tld.model.patchsize);
//pEx = [pEx tldGetPattern(im1,tld.grid(1:4,idxP),tld.model.patchsize)];
end
当然这个函数是不能这么草草了事的,还有三大函数需要进一步细看:
1.tldGetPattern()
[cpp] view
plaincopy
nBB = size(bb,2);//得到bbp0(最靠近BBOX的gridbox)的列,值为1
pattern = zeros(prod(patchsize),nBB);//15*15 X 1 矩阵,返回矩阵
if ~exist('flip','var')
flip= 0;
end
// for every bounding box
for i = 1:nBB//1
// sample patch
patch = img_patch(img.input,bb(:,i));//取出对应框中的图像
// flip if needed
if flip
patch = fliplr(patch);
end
// normalize size to 'patchsize' and nomalize intensities to ZMUV
//返回一个225x1的列向量,各元素值为原像素值减去像素均值
pattern(:,i) = tldPatch2Pattern(patch,patchsize);//patch压缩变换到patchsize大小,然后将各个元素减去元素均值
end
切入到tldPatch2Pattern看一眼:
[cpp] view
plaincopy
patch = imresize(patch,patchsize); // 'bilinear' is faster
pattern = double(patch(:));//此时变成225X1的矩阵
pattern = pattern - mean(pattern);//mean(pattern)求各列向量的均值
2.img_patch()(4个传参)
[cpp] view
plaincopy
rand('state',randomize);
randn('state',randomize);
//'noise',5,'angle',20,'shift',0.02,'scale',0.02;
NOISE = p_par.noise;
ANGLE = p_par.angle;
SCALE = p_par.scale;
SHIFT = p_par.shift;
cp = bb_center(bb)-1;//HULL矩形的中心
Sh1 = [1 0 -cp(1); 0 1 -cp(2); 0 0 1];
sca = 1-SCALE*(rand-0.5);%0.99~1.01
//[0.99~1.01 ]
//[ 0.99~1.01 ]
//[ 1 ]
Sca = diag([sca sca 1]);
ang = 2*pi/360*ANGLE*(rand-0.5);//-10 ~ 10度 实际为弧度
ca = cos(ang);
sa = sin(ang);
Ang = [ca, -sa; sa, ca];
Ang(end+1,end+1) = 1;
shR = SHIFT*bb_height(bb)*(rand-0.5);//-0.01~1.01*bb_height(bb)
shC = SHIFT*bb_width(bb)*(rand-0.5);//-0.01~1.01*bb_width(bb)
Sh2 = [1 0 shC; 0 1 shR; 0 0 1];
bbW = bb_width(bb)-1;
bbH = bb_height(bb)-1;
box = [-bbW/2 bbW/2 -bbH/2 bbH/2];
H = Sh2*Ang*Sca*Sh1;
bbsize = bb_size(bb);
patch = uint8(warp(img,inv(H),box) + NOISE*randn(bbsize(1),bbsize(2)));//给图像造成5的高斯噪声
以上的代码注释就少了,因为全都是关于仿射变换的,具体可以参看仿射变换,大体就是作者在论文中提到的(shift+-1%,scale +-1%, in-plane rotation +-10度)用来提高训练样本的多样性。
3.fern()(第一个传参为5,获得模式)
[cpp] view
plaincopy
unsigned char *input = (unsigned char*) mxGetPr(mxGetField(prhs[1],0,"input"));
unsigned char *blur = (unsigned char*) mxGetPr(mxGetField(prhs[1],0,"blur"));//获得仿射变换后的patch
//if (mxGetM(prhs[1])!=iHEIGHT) { mexPrintf("fern: wrong input image.\n"); return; }
// bbox indexes
double *idx = mxGetPr(prhs[2]);//bbp所对应的列索引
int numIdx = mxGetM(prhs[2]) * mxGetN(prhs[2]);//1 X (<=10)
// minimal variance
double minVar = *mxGetPr(prhs[3]);//minVar=0
if (minVar > 0) {
iimg(input,IIMG,iHEIGHT,iWIDTH);//返回IIMG,是图像进行矩形积分后的结果(运行不到这)
iimg2(input,IIMG2,iHEIGHT,iWIDTH);//返回IIMG,是图像进行矩形平方积分后的结果(运行不到这)
}
// output patterns
//创建输出矩阵:10X(<=10)
plhs[0] = mxCreateDoubleMatrix(nTREES,numIdx,mxREAL);
double *patt = mxGetPr(plhs[0]);
//创建输出矩阵:1 X(<=10)
plhs[1] = mxCreateDoubleMatrix(1,numIdx,mxREAL);
double *status = mxGetPr(plhs[1]);
for (int j = 0; j < numIdx; j++) {//(<=10)
if (minVar > 0) {
double bboxvar = bbox_var_offset(IIMG,IIMG2,BBOX+j*BBOX_STEP);//BBOX保存网格数据索引等数据(运行不到这)
//E(p^2)-E^2(p)
if (bboxvar < minVar) { continue; }(运行不到这)
}
status[j] = 1;
double *tPatt = patt + j*nTREES;
for (int i = 0; i < nTREES; i++) {//10
//返回对应gridbox及对应树的13位有效的像素比较码
tPatt[i] = (double) measure_tree_offset(blur, idx[j]-1, i);//idx:bbp
}
}
return;
进入measure_tree_offset
[cpp] view
plaincopy
int index = 0;
int *bbox = BBOX + idx_bbox*BBOX_STEP;//BBOX存储gridbox的索引等信息BBOX_STEP=7(因为grid的行为6)
//OFF + bbox[5],该表达式表示该gridbox的特征点信息在OFF的偏移,bbox[5]表示图像横向上多少个网格点
[cpp] view
plaincopy
//OFF = create_offsets(s,x);//记录各个特征点在各种尺度下box中的具体位置
int *off = OFF + bbox[5] + idx_tree*2*nFEAT;//OFF存储特征点在各个尺度框下的分布位置等
for (int i=0; i<nFEAT; i++) {//13
index<<=1;
//off[0]为特征点的x坐标,off[1]为特征点的y坐标,bbox[0]为该gridbox在图画中的位置
int fp0 = img[off[0]+bbox[0]];
int fp1 = img[off[1]+bbox[0]];
if (fp0>fp1) { index |= 1;}//两个像素点比较并置位相应CODE
off += 2;//移到下一个点对
}
return index;
看完上面,真的有点累啊,算了,把负例也看下好了,简单看了下,代码不算太多:
[cpp] view
plaincopy
// Correct initial bbox
tld.bb(:,1) = bbP(1:4,:);//最靠近BBOX的的gridbox
// Variance threshold
tld.var = var(pEx(:,1)) / 2;//var计算方差,这里即求各个数平方和的平均数
// disp(['Variance : ' num2str(tld.var)]);
// Generate Negative Examples
//nx:patch variance 挑出合适的patches,并提取fern特征赋给nx,
//nEx返回一个225x100(nEx)的矩阵,列向量各元素值为原像素值减去像素均值,100为num_patches
//输入:
//overlap:一维行向量,记录GRID中的各个gridbox与用户目标标定框的重叠率
//输出:
//nx:patch variance 挑出合适的patches,并提取fern特征赋给nx
//nEx:一个225x100(nEx)的矩阵,列向量各元素值为原像素值减去像素均值,100为num_patches
[nX,nEx] = tldGenerateNegativeData(tld,overlap,tld.img{1});
再进
[cpp] view
plaincopy
// Measure patterns on all bboxes that are far from initial bbox
//opt.n_par = struct('overlap',0.2,'num_patches',100);
idxN = find(overlap<tld.n_par.overlap);//overlap < 0.2
[nX,status] = fern(5,img,idxN,tld.var/2);//此函数通过patch variance剔除一批,剩下的进入fern特征码提取
idxN = idxN(status==1); // bboxes far and with big variance,注意C++代码中的status[j] = 1;一句
nX = nX(:,status==1);//选出进入第二级分类器的负样本
// Randomly select 'num_patches' bboxes and measure patches
idx = randvalues(1:length(idxN),tld.n_par.num_patches);//'num_patches',100应该是随机取出100个gridbox
bb = tld.grid(:,idxN(idx));
nEx = tldGetPattern(img,bb,tld.model.patchsize);//不复注解
再进入fern(5,...)因为有tld.var/2,执行稍有不同,请参见上面就行。
好了,至此已经为分类器的训练产生了可用的正例和负例了。
相关文章推荐
- java异常的栈轨迹
- 【Retinex】【Frankle-McCann Retinex】matlab代码注释
- Java中的方法重载的参数是以静态类型为依据
- c语言课程总结
- Spring注解Hibernate对象的实例
- 组合算法的实现,递归,C语言
- C++ new 和Placement New 以及nothrow new
- Spring AOP解析类
- Lua获取数组大小
- BSS段、数据段、代码段、堆与栈
- 抛弃Fastcgi,用uwsgi来部署你的Django程序吧
- LRU缓存实现(Java)
- Java学习笔记【字符串知识要点】
- [原]打造Python开发环境之Python环境
- 深入理解Java的接口和抽象类
- 组合算法实现C语言,非递归
- JDK源代码阅读之CharSequence
- python 版websocket实现
- django 1.8 官方文档翻译:6-3 Django异常
- 【Matlab学习笔记】【函数学习】cat函数