接着上一次的学习。DSO在完成输入图像、计算金字塔等图像处理操作后,会进入初始化流程。初始化对于DSO而言非常重要,选择不同的初始化图像,最后DSO的效果也会有差别。
在探索过程中,我遇到名为makeMaps的函数,虽然这个函数的设计非常巧妙,但是还是有很多地方让我很迷惑。
这里先把测试makeMaps的代码贴上。主要代码引用自JakobEngel/dso,代码注释主要参考alalagong/DSO。
#include
#include
#include
#include
#include
#include
#include
using namespace std;
using namespace cv;
#define PYR_LEVELS 6 //最大金字塔层数
typedef Eigen::Matrix<float,2,1> Vect2f;
enum PixelSelectorStatus {
PIXSEL_VOID=0, PIXSEL_1, PIXSEL_2, PIXSEL_3};//定义枚举类型,占用整型内存
int currentPotential=3;
float* absSquaredGrad[PYR_LEVELS]; //x,y方向梯度的平方和
int wG[PYR_LEVELS], hG[PYR_LEVELS];//图像宽高
int bins[PYR_LEVELS];//梯度直方图大小范围
unsigned char* randomPattern;
int* gradHist;
float* ths;
float* thsSmoothed;
int thsStep;
bool gradHistFrame=false;
Eigen::Vector3f* dI; //图像导数
Eigen::Vector3f* dIp[PYR_LEVELS]; //各金字塔层的图像导数
int makeMaps(float* map_out, float density, int recursionsLeft, bool plot, float thFactor)
{
float numHave=0;
float numWant=density;
float quotia;
int idealPotential = currentPotential;//初始化为3
int w = wG[0];//0层图像宽度
int h = hG[0];
int w32 = w/32;//将整个图像分成小块
int h32 = h/32;//每个block大小为32*32
int thsStep = w32;
// 0, 1, 2层的梯度平方和
float * mapmax0 = absSquaredGrad[0];
float * mapmax1 = absSquaredGrad[1];
float * mapmax2 = absSquaredGrad[2];
{
//遍历每个block
for(int y=0;y<h32;y++)
for(int x=0;x<w32;x++)
{
//1、为每个32*32的block计算梯度直方图
float* map0 = mapmax0+32*x+32*y*w;//地址移动,指向(x,y)block的左上角像素
int* hist0 = gradHist;
//sizeof获取某个数据类型所占用空间的字节数,memset为逐字节填充
memset(hist0,0,sizeof(int)*50);//从gradHist地址开始,用0填充50个int位置
for(int j=0;j<32;j++) for(int i=0;i<32;i++)
{
int it = i+32*x;
int jt = j+32*y;//32*32block中(i,j)坐标对应的图像中的坐标
if(it>w-2 || jt>h-2 || it<1 || jt<1) continue;
int g = sqrtf(map0[i+j*w]);//(x,y)block中的(i,j)对应的像素梯度的模
if(g>48) g=48;//设置梯度上限
hist0[g+1]++;//梯度为g对应的block中的像素个数
hist0[0]++;//计算总个数
}
//2、为block选择梯度阈值
float below = 0.5;
int th = hist0[0]*below+0.5f;//block中的梯度个数的一半
for(int i=0;i<90;i++)
{
th -= hist0[i+1];// 梯度值为0-i的所有像素个数占 below%
if(th<0)
{
ths[x+y*w32]=i+7;//block的值选择梯度中位数位置
break;//原来这里少个break
}
}
if(th>=0)
{
ths[x+y*w32]=97;
cout<<"**************th >= 0"<<endl;
}
}
//3、block梯度阈值平滑
for(int y=0;y<h32;y++)
for(int x=0;x<w32;x++)
{
float sum=0,num=0;
if(x>0)
{
if(y>0) {
num++; sum+=ths[x-1+(y-1)*w32];}
if(y<h32-1) {
num++; sum+=ths[x-1+(y+1)*w32];}
num++; sum+=ths[x-1+(y)*w32];
}
if(x<w32-1)
{
if(y>0) {
num++; sum+=ths[x+1+(y-1)*w32];}
if(y<h32-1) {
num++; sum+=ths[x+1+(y+1)*w32];}
num++; sum+=ths[x+1+(y)*w32];
}
if(y>0) {
num++; sum+=ths[x+(y-1)*w32];}
if(y<h32-1) {
num++; sum+=ths[x+(y+1)*w32];}
num++; sum+=ths[x+y*w32];
thsSmoothed[x+y*w32] = (sum/num) * (sum/num);//用3*3个block求平均值平方作为新的block的值
}
//4、选择像素
Eigen::Vector3f const * const map_0 = dI;
int w = wG[0];
int w1 = wG[1];
int w2 = wG[2];
int h = hG[0];
//随机选这16个对应方向上的梯度和阈值比较
//每个pot里面的方向随机选取的, 防止特征相同, 重复
const Vect2f directions[16] = {
Vect2f(0, 1.0000),
Vect2f(0.3827, 0.9239),
Vect2f(0.1951, 0.9808),
Vect2f(0.9239, 0.3827),
Vect2f(0.7071, 0.7071),
Vect2f(0.3827, -0.9239),
Vect2f(0.8315, 0.5556),
Vect2f(0.8315, -0.5556),
Vect2f(0.5556, -0.8315),
Vect2f(0.9808, 0.1951),
Vect2f(0.9239, -0.3827),
Vect2f(0.7071, -0.7071),
Vect2f(0.5556, 0.8315),
Vect2f(0.9808, -0.1951),
Vect2f(1.0000, 0.0000),
Vect2f(0.1951, -0.9808)};
memset(map_out,0,w*h*sizeof(PixelSelectorStatus));//填充0,float和int都是4个字节
// 金字塔层阈值的减小倍数
float dw1 = 0.75;
float dw2 = dw1*dw1;
int pot = currentPotential;//网格大小为3
int n3=0, n2=0, n4=0;
bool setting_selectDirectionDistribution=true;
//0层2*2个像素对应1层1个像素,1层2*2个像素对应2层1个像素
//同理0层2*2个pot对应1层1个pot,1层2*2个pot对应2层1个pot
//先选择0层合适的像素,在第0层2*2个pot里都不合适,再到第1层选择
//之后第1层选择同理,最后保存被选像素在第0层的坐标
for(int y4=0;y4<h;y4+=(4*pot)) for(int x4=0;x4<w;x4+=(4*pot))
{
int my3 = std::min((4*pot), h-y4);
int mx3 = std::min((4*pot), w-x4);
int bestIdx4=-1; float bestVal4=0;
Vect2f dir4 = directions[randomPattern[n4] & 0xF];// 取低4位, 0-15, 和directions对应
for(int y3=0;y3<my3;y3+=(2*pot)) for(int x3=0;x3<mx3;x3+=(2*pot))
{
int x34 = x3+x4;
int y34 = y3+y4;
int my2 = std::min((2*pot), h-y34);
int mx2 = std::min((2*pot), w-x34);
int bestIdx3=-1; float bestVal3=0;
Vect2f dir3 = directions[randomPattern[n3] & 0xF];
for(int y2=0;y2<my2;y2+=pot) for(int x2=0;x2<mx2;x2+=pot)
{
int x234 = x2+x34;
int y234 = y2+y34;
int my1 = std::min(pot, h-y234);
int mx1 = std::min(pot, w-x234);
int bestIdx2=-1; float bestVal2=0;
Vect2f dir2 = directions[randomPattern[n2] & 0xF];
for(int y1=0;y1<my1;y1+=1) for(int x1=0;x1<mx1;x1+=1)
{
assert(x1+x234 < w);//值为假,则打印信息终止程序
assert(y1+y234 < h);
int xf = x1+x234;//idx像素坐标
int yf = y1+y234;
int idx = xf + w*yf;//第0层4*4个pot里,第(x2,y2)个pot的第(x1,y1)的像素id
if(xf<4 || xf>=w-5 || yf<4 || yf>h-4) continue;//不考虑0层最外一圈4*4的像素
float pixelTH0 = thsSmoothed[(xf>>5) + (yf>>5) * thsStep];//三个阈值
float pixelTH1 = pixelTH0*dw1;
float pixelTH2 = pixelTH1*dw2;
float ag0 = mapmax0[idx];//第0层对应坐标的梯度平方和
if(ag0 > pixelTH0*thFactor)
{
Vect2f ag0d = map_0[idx].tail<2>();//第0层(dx,dy)
float dirNorm = fabsf((float)(ag0d.dot(dir2)));//点乘,计算在dir2上的投影
if(!setting_selectDirectionDistribution) dirNorm = ag0;
if(dirNorm > bestVal2)//记录每个pot在n2方向中的梯度最大值
{
bestVal2 = dirNorm; bestIdx2 = idx; bestIdx3 = -2; bestIdx4 = -2;}
}
//如果同时满足梯度平方大于阈值,在dir2方向上的投影大于存储的值,则检测pot里下一个像素
if(bestIdx3==-2) continue;//初始为-1
float ag1 = mapmax1[(int)(xf*0.5f+0.25f) + (int)(yf*0.5f+0.25f)*w1];//第1层对应的梯度平方和
if(ag1 > pixelTH1*thFactor)
{
Vect2f ag0d = map_0[idx].tail<2>();
float dirNorm = fabsf((float)(ag0d.dot(dir3)));
if(!setting_selectDirectionDistribution) dirNorm = ag1;
if(dirNorm > bestVal3)
{
bestVal3 = dirNorm; bestIdx3 = idx; bestIdx4 = -2;}
}
//如果第0层都不满足,在第1层中寻找合适像素
if(bestIdx4==-2) continue;
//如果第1层都不满足,在第2层中寻找合适像素
float ag2 = mapmax2[(int)(xf*0.25f+0.125) + (int)(yf*0.25f+0.125)*w2];
if(ag2 > pixelTH2*thFactor)
{
Vect2f ag0d = map_0[idx].tail<2>();
float dirNorm = fabsf((float)(ag0d.dot(dir4)));
if(!setting_selectDirectionDistribution) dirNorm = ag2;
if(dirNorm > bestVal4)
{
bestVal4 = dirNorm; bestIdx4 = idx; }
}
}
//如果在第0层找到像素,记录像素位置,改变投影方向
if(bestIdx2>0)
{
map_out[bestIdx2] = 1;
bestVal3 = 1e10;//第0层每2*2个pot里只选择一个像素
n2++;
}
}
//如果在第0层没找到,第1层找到
if(bestIdx3>0)
{
map_out[bestIdx3] = 2;
bestVal4 = 1e10;//第1层每2*2个pot里只选择一个像素
n3++;
}
}
//如果在第1层没找到,第2层找到
if(bestIdx4>0)
{
map_out[bestIdx4] = 4;
n4++;
}
}
Eigen::Vector3i n(n2,n3,n4);//记录第0,1,2层选点的个数
numHave = n[0]+n[1]+n[2];//numHave表示已选像素个数
cout << "numHave = " << numHave << endl;
cout << "n2 = " << n2 << endl;
quotia = numWant / numHave;
//0层1个pot里只选择1个像素,因此K相当于计算已经被占用的像素的个数
//numWant表示目标像素个数
float K = numHave * (currentPotential+1) * (currentPotential+1);
idealPotential = sqrtf(K/numWant)-1;//sqrtf(nH/nW)*(pot+1)-1
cout << "numWant = " << numWant << endl;
// cout << "K = " << K << endl;
cout << "quotia = " << quotia << endl;
if(idealPotential<1) idealPotential=1;
//比例大于1.25,需要缩小pot,增加选点数numHave
if( recursionsLeft>0 && quotia > 1.25 && currentPotential>1)
{
if(idealPotential>=currentPotential)
idealPotential = currentPotential-1;
currentPotential = idealPotential;
return makeMaps(map_out, density, recursionsLeft-1, plot,thFactor);//递归,重采样
}
//比例小于0.25,增大pot,减少选点数
else if(recursionsLeft>0 && quotia < 0.25)
{
if(idealPotential<=currentPotential)
idealPotential = currentPotential+1;
currentPotential = idealPotential;
return makeMaps(map_out, density, recursionsLeft-1, plot,thFactor);//递归,重采样
}
cout << "idealPotential = " << idealPotential << endl;
cout << "currentPotential = " << currentPotential << endl;
//显示0层的梯度直方图
bins[0]=49;//第0层有49个梯度
int hist_w = 392;//直方图宽度
int hist_h = 400;//直方图高度
int bin_w = cvRound((double)hist_w / bins[0]);//每个bin的宽度
Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);//初始化直方图
//归一化直方图数据
for(int i=1; i<bins[0]; i++)
{
rectangle(histImage, Point(bin_w*(i - 1), hist_h - cvRound(400*gradHist[i]/gradHist[0])),
Point(bin_w*(i), hist_h - 1), Scalar(255, 0, 0), 1, 8, 0);//图像,bin坐标左上角,右下角,颜色
}
// 显示直方图
cout << "gradHist[0] = " << gradHist[0] << endl;
cout << "bins[0] = " << bins[0] << endl;
imshow("Histogram Demo", histImage);
}
int numHaveSub = numHave;
if(quotia < 0.95)//选点数超过预定值
{
int wh=wG[0]*hG[0];
int rn=0;
unsigned char charTH = 255*quotia;
//遍历第0层所有像素
for(int i=0;i<wh;i++)
{
if(map_out[i] != 0)//如果被选中
{
if(randomPattern[rn] > charTH )//随机删除点
{
map_out[i]=0;
numHaveSub--;
}
rn++;
}
}
cout <<"rn = "<<rn<<endl;//rn的值也远比后面的大,很奇怪
}
int num_test=0;
for(int y=0; y<h;y++)
for(int x=0;x<w;x++)
{
if(map_out[x+y*w]!=0)
num_test++;
}
cout <<"num_test = "<<num_test<<endl;
currentPotential = idealPotential;
//画出像素选择结果
// /*
if(plot)//输入false
{
int w=wG[0];int h=hG[0];
Mat img_select_result = Mat::zeros(h, w, CV_8UC3);
Mat map_out_show = Mat::zeros(h, w, CV_8UC3);
for(int y=0; y<h;y++)
for(int x=0;x<w;x++)
{
float c = dI[x+y*w][0]*0.7;
if(c>255) c=255;
img_select_result.at<Vec3b>(y,x) = Vec3b(c,c,c);
// switch (int(map_out[x+y*w]))//值为1的点集中在图像下方
// {
// case 0:map_out_show.at(y,x)=Vec3b(255,255,255);break;
// case 1:map_out_show.at(y,x)=Vec3b(255,0,0);break;
// case 2:map_out_show.at(y,x)=Vec3b(0,255,0);break;
// case 4:map_out_show.at(y,x)=Vec3b(0,0,255);break;
// }
}
imshow("Selector Image", img_select_result);
// imshow("map_out Image", map_out_show);
int num=0;
for(int y=3; y<h-3;y++)
for(int x=3;x<w-3;x++)
{
int pi=x+y*w;
int val;
if(map_out[pi] == 1)
val= 0;
else if(map_out[pi] == 2)
val= 1;
else if(map_out[pi] == 4)
val= 2;
else
continue;
for(int i=-3;i<=3;i++)
{
img_select_result.at<Vec3b>(y+i,x+3)[val] = 255;
img_select_result.at<Vec3b>(y+i,x-3)[val] = 255;
img_select_result.at<Vec3b>(y+i,x+2)[val] = 255;
img_select_result.at<Vec3b>(y+i,x-2)[val] = 255;
img_select_result.at<Vec3b>(y-3,x+i)[val] = 255;
img_select_result.at<Vec3b>(y+3,x+i)[val] = 255;
img_select_result.at<Vec3b>(y-2,x+i)[val] = 255;
img_select_result.at<Vec3b>(y+2,x+i)[val] = 255;
}
num++;
}
cout <<"num = "<< num <<endl;
imshow("Selector Pixels", img_select_result);
}
// */
return numHaveSub;
}
//int makePixelStatus(Eigen::Vector3f* grads, bool* map, int w, int h, float desiredDensity, int recsLeft=5, float THFac = 1)
//{
//}
int main(int argc, char *argv[])
{
Mat img,img_f;
float* color;
int pyrLevelsUsed;
float* img_pyr[PYR_LEVELS];
//step1:读入图像
// /*
if(argc<=3)
img = imread(argv[1], IMREAD_GRAYSCALE);
else
{
cout << "error, input './img_test path/image.png path/camera.txt' " <<endl;
return -1;
}
imshow("raw image", img);
wG[0]=img.cols;hG[0]=img.rows;
int w=wG[0];//0层图像宽度
int h=hG[0];
color = new float[w*h];
// 数据类型转化 unsigned char 转化为 float
img.convertTo(img_f, CV_32F);
memcpy(color, img_f.data, w*h*sizeof(float));//void memcpy(void * _Dst, const void *_Src, size_t _Size);
// */
//step2:初始化金字塔
// /*
//2.1 确定图像金字塔层数
int wlvl=w;
int hlvl=h;
pyrLevelsUsed=1;
while(wlvl%2==0 && hlvl%2==0 && wlvl*hlvl > 5000 && pyrLevelsUsed < PYR_LEVELS)
{
wlvl /=2;
hlvl /=2;
pyrLevelsUsed++;
}
printf("using pyramid levels 0 to %d. coarsest resolution: %d x %d!\n",
pyrLevelsUsed-1, wlvl, hlvl);
if(wlvl>100 && hlvl > 100)
{
printf("\n\n===============WARNING!===================\n "
"using not enough pyramid levels.\n"
"Consider scaling to a resolution that is a multiple of a power of 2.\n");
}
if(pyrLevelsUsed < 3)
{
printf("\n\n===============WARNING!===================\n "
"I need higher resolution.\n"
"I will probably segfault.\n");
}
//2.2 初始化
for (int level = 1; level < pyrLevelsUsed; ++ level)
{
wG[level] = w >> level;//位右移,等效除以2
hG[level] = h >> level;
}
for(int i=0;i<pyrLevelsUsed;i++)
{
dIp[i] = new Eigen::Vector3f[wG[i]*hG[i]];//初始化各层图像导数,new结果是一段3*1向量数组的地址
absSquaredGrad[i] = new float[wG[i]*hG[i]];//xy方向梯度平方和
img_pyr[i] = new float[wG[i]*hG[i]];
}
dI = dIp[0];//图像导数指针,dI指向第0层图像梯度
// */
//step3:生成金字塔
// /*
for(int i=0;i<w*h;i++)
{
dI[i][0] = color[i];//图像按照辐射度数组存储,第0层第一通道存储原图
img_pyr[0][i] = color[i];
}
//3.1 生成图像金字塔
for(int lvl=0; lvl<pyrLevelsUsed; lvl++)
{
int wl = wG[lvl], hl = hG[lvl];//当前层图像尺寸
Eigen::Vector3f* dI_l = dIp[lvl];//当前层图像梯度数组地址
float* dabs_l = absSquaredGrad[lvl];//当前层图像梯度平方和数组地址
if(lvl>0)//第0层不执行
{
int lvlm1 = lvl-1;//上一层ID
int wlm1 = wG[lvlm1];//上一层列数
Eigen::Vector3f* dI_lm = dIp[lvlm1];//上一层梯度数组地址
//像素4合1,生成金字塔
for(int y=0;y<hl;y++)
for(int x=0;x<wl;x++)
{
//(x,y)位置的第一个通道存入灰度值,由上一层4合1得到
dI_l[x + y*wl][0] = 0.25f * (dI_lm[2*x + 2*y*wlm1][0] +
dI_lm[2*x+1 + 2*y*wlm1][0] +
dI_lm[2*x + 2*y*wlm1+wlm1][0] +
dI_lm[2*x+1 + 2*y*wlm1+wlm1][0]);
img_pyr[lvl][x + y*wl] = dI_l[x + y*wl][0];//
}
}
//3.2 对当前层图像求梯度
for(int idx=wl;idx < wl*(hl-1);idx++)//从第2行第1列开始
{
float dx = 0.5f*(dI_l[idx+1][0] - dI_l[idx-1][0]);//x方向梯度(宽度)
float dy = 0.5f*(dI_l[idx+wl][0] - dI_l[idx-wl][0]);
if(!std::isfinite(dx)) dx=0;//当参数不是NaN、Infinity、-Infinity 时,返回true
if(!std::isfinite(dy)) dy=0;
dI_l[idx][1] = dx;//第2通道存储x方向梯度
dI_l[idx][2] = dy;//第3通道存储y方向梯度
dabs_l[idx] = dx*dx+dy*dy;//absSquaredGrad存储梯度平方和
}
}
//3.3 显示金字塔
for(int i=0; i<pyrLevelsUsed; i++)
{
Mat img_pyr_show(hG[i], wG[i], CV_32F);
memcpy(img_pyr_show.data, img_pyr[i], wG[i]*hG[i]*sizeof(float));//
img_pyr_show.convertTo(img_pyr_show, CV_8UC1);
string name = to_string(i);
imshow("P"+name, img_pyr_show);
}
cout << "pyrLevelsUsed = " << pyrLevelsUsed << endl;
// */
//step4:为每层计算内参K
// /*
double fx[PYR_LEVELS], fy[PYR_LEVELS], cx[PYR_LEVELS], cy[PYR_LEVELS];
double k1, k2, p1, p2;
Eigen::Matrix<double,3,3> K[PYR_LEVELS];
ifstream readFile(argv[2]);//打开相机内参数据文件
if (!readFile.is_open())
{
cout << "未成功打开内参文件" << endl;
return -1;
}
readFile >> fx[0] >> fy[0] >> cx[0] >> cy[0]
>> k1 >> k2 >> p1 >> p2;
K[0] << fx[0], 0.0, cx[0], 0.0, fy[0], cy[0], 0.0, 0.0, 1.0;
for (int level = 1; level < pyrLevelsUsed; ++ level)
{
fx[level] = fx[level-1] * 0.5;
fy[level] = fy[level-1] * 0.5;
cx[level] = (cx[0] + 0.5) / ((int)1<<level) - 0.5;
cy[level] = (cy[0] + 0.5) / ((int)1<<level) - 0.5;//左移,相当于乘以2的幂
K[level] << fx[level], 0.0, cx[level], 0.0, fy[level], cy[level], 0.0, 0.0, 1.0;
cout << K[level] << endl;
}
readFile.close(); //关闭文件
// */
//step5:提取特征像素
// /*
float* statusMap = new float[wG[0]*hG[0]];
bool* statusMapB = new bool[wG[0]*hG[0]];
float densities[] = {
0.03,0.05,0.15,0.5,1};//不同层点密度
gradHist = new int[100*(1+wG[0]/32)*(1+hG[0]/32)];//gradHist可能只是为了开辟足够大的空间
ths = new float[(wG[0]/32)*(hG[0]/32)+100];
thsSmoothed = new float[(wG[0]/32)*(hG[0]/32)+100];
//5.1 产生随机数
randomPattern = new unsigned char[wG[0]*hG[0]];
std::srand(3141592); // want to be deterministic.rand()和srand()要一起使用,其中srand()用来初始化随机数种子,rand()用来产生随机数。
for(int i=0;i<wG[0]*hG[0];i++) randomPattern[i] = rand() & 0xFF;
//5.2 遍历金字塔
for(int lvl=0; lvl<pyrLevelsUsed; lvl++)
{
int npts;
// //如果是第0层
if(lvl == 0)
npts = makeMaps(statusMap,densities[lvl]*w*h,1,true,2);//
// else//封印中
// npts = makePixelStatus(firstFrame->dIp[lvl], statusMapB, w[lvl], h[lvl], densities[lvl]*w[0]*h[0]);
}
delete []color;
delete []randomPattern;
delete []gradHist;
delete []ths;
delete []thsSmoothed;
delete []statusMap;
delete []statusMapB;
for(int i=0;i<pyrLevelsUsed;i++)
{
delete []dIp[i];
delete []absSquaredGrad[i];
delete []img_pyr[i];
}
waitKey(0);
return 0;
}
1、直方图归一化我直接用的总数,所以画出的图显得有点大。
2、最大的问题还是特征像素选取上。可以看到为了满足需要的选点个数,第0层的选点(蓝色点)大部分都集中到图像下部,数量占总数的90%。最开始我以为是测试程序出错,但是对DSO项目运行过程中,也观测到这种不合理。(是只有我吗,还是已经有过解释了?)不过DSO项目还是可以正常运行,可能影响不大。
笔记先记录到这了,最后祝大家新年快乐,牛年大吉。
经过各种调试,终于发现问题所在。原来程序每个block的阈值都是96,这个问题再往上看,才发现自己在中位数选值后忘记break,失之毫厘谬以千里啊。
这里贴上修改之后的结果。虽然已经得到和原DSO项目一样的效果,但是图像下部集中了大量的像素(苦笑)。
继续修改,我发现DSO项目程序里有这样一句代码
if(xf<4 || xf>=w-5 || yf<4 || yf>h-4) continue;
刚开始我的理解是选点忽略最外的一圈像素。可是在我继续深究图像下部的像素集中问题时,我发现并不是这样。
xf>>5 和 yf>>5 的值是分别等于w32和h32的,而梯度阈值的序号是从0开始,最大到w32-1,这些多余的像素位阈值为0,必定被选中,所以底下有一条错误选点带。于是修改上面这句代码为
if(xf<4 || xf>=w-17 || yf<4 || yf>h-17) continue;