如何在高海拔照片中有效地找到地平线?

时间:2014-03-05 02:20:10

标签: javascript python c algorithm image-processing

我试图检测从高海拔拍摄的图像中的地平线,以确定相机的方向。我也试图快速运行 - 理想情况下,我希望能够在Raspberry Pi上实时处理帧(即每秒几帧)。到目前为止我采取的方法是基于这样一个事实,即在高海拔地区,天空很暗,就像这样:

Earth from space

我尝试过的是从整个图像中取样并将它们分成浅色和深色样本,并在它们之间画一条线。然而,由于大气层的模糊性,这将地平线置于其实际位置之上:

这是我的代码(在Javascript中为了便于网络演示):

function mag(arr) {
    return Math.sqrt(arr[0]*arr[0]+arr[1]*arr[1]+arr[2]*arr[2])
}
// return (a, b) that minimize
// sum_i r_i * (a*x_i+b - y_i)^2
function linear_regression( xy ) {
    var i,
        x, y,
        sumx=0, sumy=0, sumx2=0, sumy2=0, sumxy=0, sumr=0,
        a, b;
    for(i=0;i<xy.length;i++) {
        x = xy[i][0]; y = xy[i][2];
        r = 1
        sumr += r;
        sumx += r*x;
        sumx2 += r*(x*x);
        sumy += r*y;
        sumy2 += r*(y*y);
        sumxy += r*(x*y);
    }
    b = (sumy*sumx2 - sumx*sumxy)/(sumr*sumx2-sumx*sumx);
    a = (sumr*sumxy - sumx*sumy)/(sumr*sumx2-sumx*sumx);
    return [a, b];
}


var vals = []
for (var i=0; i<resolution; i++) {
            vals.push([])
            for (var j=0; j<resolution; j++) {
                    x = (canvas.width/(resolution+1))*(i+0.5)
                    y = (canvas.height/(resolution+1))*(j+0.5)
                    var pixelData = cr.getImageData(x, y, 1, 1).data;
                    vals[vals.length-1].push([x,y,pixelData])
                    cr.fillStyle="rgb("+pixelData[0]+","+pixelData[1]+","+pixelData[2]+")"
                    cr.strokeStyle="rgb(255,255,255)"
                    cr.beginPath()
                    cr.arc(x,y,10,0,2*Math.PI)
                   cr.fill()
                    cr.stroke()
            }
    }
    var line1 = []
    var line2 = []
    for (var i in vals) {
            i = parseInt(i)
            for (var j in vals[i]) {
                    j = parseInt(j)
                    if (mag(vals[i][j][3])<minmag) {
                            if ((i<(vals.length-2) ? mag(vals[i+1][j][4])>minmag : false)
                             || (i>0 ? mag(vals[i-1][j][5])>minmag : false)
                             || (j<(vals[i].length-2) ? mag(vals[i][j+1][6])>minmag : false)
                             || (j>0 ? mag(vals[i][j-1][7])>minmag : false)) {
                                    cr.strokeStyle="rgb(255,0,0)"
                                    cr.beginPath()
                                    cr.arc(vals[i][j][0],vals[i][j][8],10,0,2*Math.PI)
                                    cr.stroke()
                                    line1.push(vals[i][j])
                            }
                    }
                    else if (mag(vals[i][j][9])>minmag) {
                            if ((i<(vals.length-2) ? mag(vals[i+1][j][10])<minmag : false)
                             || (i>0 ? mag(vals[i-1][j][11])<minmag : false)
                             || (j<(vals[i].length-2) ? mag(vals[i][j+1][12])<minmag : false)
                             || (j>0 ? mag(vals[i][j-1][13])<minmag : false)) {
                                    cr.strokeStyle="rgb(0,0,255)"
                                    cr.beginPath()
                                    cr.arc(vals[i][j][0],vals[i][j][14],10,0,2*Math.PI)
                                    cr.stroke()
                                    line2.push(vals[i][j])
                            }
                    }
            }
        }
        eq1 = linear_regression(line1)
        cr.strokeStyle = "rgb(255,0,0)"
        cr.beginPath()
        cr.moveTo(0,eq1[1])
        cr.lineTo(canvas.width,canvas.width*eq1[0]+eq1[1])
        cr.stroke()
        eq2 = linear_regression(line2)
        cr.strokeStyle = "rgb(0,0,255)"
        cr.beginPath()
        cr.moveTo(0,eq2[1])
        cr.lineTo(canvas.width,canvas.width*eq2[0]+eq2[1])
        cr.stroke()
        eq3 = [(eq1[0]+eq2[0])/2,(eq1[1]+eq2[1])/2]
        cr.strokeStyle = "rgb(0,255,0)"
        cr.beginPath()
        cr.moveTo(0,eq3[1])
        cr.lineTo(canvas.width,canvas.width*eq3[0]+eq3[1])
        cr.stroke()

结果(绿线是检测到的地平线,红色和蓝色是在界外估计的):

enter image description here

我该如何改进?有没有更有效的方法呢?最终的程序可能用Python编写,如果太慢,可以用C编写。

2 个答案:

答案 0 :(得分:3)

考虑一些基本的通道混合和阈值处理,然后是@Spektre建议的垂直样本。 [根据@ Spektre的评论编辑改为2 * R-B而不是R + G-B]

以下是频道混音的一些选项:

multiplechoice

  1. 原始
  2. 扁平单混合物R + G + B
  3. 红色频道
  4. 2 * R - B
  5. R + G - B
  6. 看起来#4是最清晰的地平线(感谢@Spektre让我更仔细地检查它),以比例混合颜色[红色2:绿色0:蓝色-1],你得到这个单色图像:< / p>

    mixed channel mono

    设置蓝色阴性表示地平线上的蓝色阴霾用于消除那里的模糊性。事实证明这比使用红色和/或绿色更有效(尝试使用GIMP中的通道混合器)。

    然后我们可以进一步澄清,如果你愿意,可以通过阈值处理(虽然你可以在采样后这样做),这里的灰度为25%:

    25pc thresholded

    使用Spektre垂直采样图像的方法,只需向下扫描,直到看到值超过25%。有3条线,你应该获得3 x,y对,从而重建曲线,知道它是抛物线。

    为了获得更强大的效果,请使用3个以上的样本并丢弃异常值。

答案 1 :(得分:2)

我会这样做:

  1. 转换为BW

  2. 扫描每侧的水平线和垂直线

    垂直线从顶部扫描

    Vertical line scan

    黑线显示线位置。对于选定的一个,绿色箭头显示扫描方向(向下)和颜色强度可视化方向(右)。 白色曲线是颜色强度图(因此您实际上看到了正在发生的事情)

    1. 选择一些网格步骤,这是行之间的64点
    2. 创建临时数组int p[];以存储行
    3. 预处理每一行

      • p[0]是行
      • 的第一个像素的强度
      • p[1,...]x推导Hy推导V直线(仅减去相邻的线像素)

      模糊p[1,...]几次以避免噪音问题(从两侧避免位置偏移)。

    4. 扫描并将其整合回来

      整合只是总结c(i)=p[ 0 ] + p[ 1 ] + ... + p[ i ]。如果c低于阈值,则您处于大气层之外,因此请开始扫描,如果从行首开始是您从右侧扫描的区域。请记住达到阈值A-point的位置并继续扫描,直到达到峰值C-point(第一个负值推导值或实际峰值...局部最大值)。

    5. 计算B-point

      为了方便您可以B = 0.5*(A+C),但如果您想要精确,那么大气强度会呈指数级增长,因此扫描从AC的推导,并从中确定指数函数。如果推导开始时间不同于您B-point,那么请记住所有B-points(对于每一行)。

  3. 现在您已设置B-points

    因此,删除所有无效的B-points(每行应该有2个......从开始到结束)所以具有更大气氛的区域通常是正确的区域,除非您在视图中有一些黑暗的无缝近距离物体。

  4. 通过剩余的B-points

  5. 估算一些曲线

    <强> [注释]

    您无法根据高度移动B-point位置,因为大气的视觉厚度也取决于观察者和光源(太阳)的位置。此外,您应该过滤剩余的B-points,因为视野中的某些星星可能会弄得一团糟。但我认为曲线近似应该足够了。

    [Edit1]为了好玩做了一些事情

    所以我在 BDS2006 C ++ VCL 中做了...所以你必须改变对环境的图像访问

    void find_horizont()
    {
    int i,j,x,y,da,c0,c1,tr0,tr1;
    
    pic1=pic0;      // copy input image pic0 to pic1
    pic1.rgb2i();   // RGB -> BW
    
    struct _atm
        {
        int x,y;    // position of horizont point
        int l;      // found atmosphere thickness
        int id;     // 0,1 - V line; 2,3 - H line;
        };
    _atm p,pnt[256];// horizont points
    int pnts=0;     // num of horizont points
    int n[4]={0,0,0,0}; // count of id-type points for the best option selection
    
    da=32;          // grid step [pixels]
    tr0=4;          // max difference of intensity inside vakuum homogenous area <0,767>
    tr1=10;         // min atmosphere thickness [pixels]
    
    // process V-lines
    for (x=da>>1;x<pic1.xs;x+=da)
        {
        // blur it y little (left p[0] alone)
        for (i=0;i<5;i++)
            {
            for (y=   0;y<pic1.ys-1;y++) pic1.p[y][x].dd=(pic1.p[y][x].dd+pic1.p[y+1][x].dd)>>1;    // this shift left
            for (y=pic1.ys-1;y>   0;y--) pic1.p[y][x].dd=(pic1.p[y][x].dd+pic1.p[y-1][x].dd)>>1;    // this shift right
            }
        // scann from top to bottom
        // - for end of homogenous area
        for (c0=pic1.p[0][x].dd,y=0;y<pic1.ys;y++)
            {
            c1=pic1.p[y][x].dd;
            i=c1-c0; if (i<0) i=-i;
            if (i>=tr0) break;  // non homogenous bump
            }
        p.l=y;
        // - for end of exponential increasing intensity part
        for (i=c1-c0,y++;y<pic1.ys;y++)
            {
            c0=c1; c1=pic1.p[y][x].dd;
            j = i; i =c1-c0;
            if (i*j<=0) break;  // peak
            if (i+tr0<j) break;     // non exponential ... increase is slowing down
            }
        // add horizont point if thick enough atmosphere found
        p.id=0; p.x=x; p.y=y; p.l-=y; if (p.l<0) p.l=-p.l; if (p.l>tr1) { pnt[pnts]=p; pnts++; n[p.id]++; }
        // scann from bottom to top
        // - for end of homogenous area
        for (c0=pic1.p[pic1.ys-1][x].dd,y=pic1.ys-1;y>=0;y--)
            {
            c1=pic1.p[y][x].dd;
            i=c1-c0; if (i<0) i=-i;
            if (i>=tr0) break;  // non homogenous bump
            }
        p.l=y;
        // - for end of exponential increasing intensity part
        for (i=c1-c0,y--;y>=0;y--)
            {
            c0=c1; c1=pic1.p[y][x].dd;
            j = i; i =c1-c0;
            if (i*j<=0) break;  // peak
            if (i+tr0<j) break;     // non exponential ... increase is slowing down
            }
        // add horizont point
        // add horizont point if thick enough atmosphere found
        p.id=1; p.x=x; p.y=y; p.l-=y; if (p.l<0) p.l=-p.l; if (p.l>tr1) { pnt[pnts]=p; pnts++; n[p.id]++; }
        }
    
    // process H-lines
    for (y=da>>1;y<pic1.ys;y+=da)
        {
        // blur it x little (left p[0] alone)
        for (i=0;i<5;i++)
            {
            for (x=   0;x<pic1.xs-1;x++) pic1.p[y][x].dd=(pic1.p[y][x].dd+pic1.p[y][x+1].dd)>>1;    // this shift left
            for (x=pic1.xs-1;x>   0;x--) pic1.p[y][x].dd=(pic1.p[y][x].dd+pic1.p[y][x-1].dd)>>1;    // this shift right
            }
        // scann from top to bottom
        // - for end of homogenous area
        for (c0=pic1.p[y][0].dd,x=0;x<pic1.xs;x++)
            {
            c1=pic1.p[y][x].dd;
            i=c1-c0; if (i<0) i=-i;
            if (i>=tr0) break;  // non homogenous bump
            }
        p.l=x;
        // - for end of eyponential increasing intensitx part
        for (i=c1-c0,x++;x<pic1.xs;x++)
            {
            c0=c1; c1=pic1.p[y][x].dd;
            j = i; i =c1-c0;
            if (i*j<=0) break;  // peak
            if (i+tr0<j) break;     // non eyponential ... increase is slowing down
            }
        // add horizont point if thick enough atmosphere found
        p.id=2; p.y=y; p.x=x; p.l-=x; if (p.l<0) p.l=-p.l; if (p.l>tr1) { pnt[pnts]=p; pnts++; n[p.id]++; }
        // scann from bottom to top
        // - for end of homogenous area
        for (c0=pic1.p[y][pic1.xs-1].dd,x=pic1.xs-1;x>=0;x--)
            {
            c1=pic1.p[y][x].dd;
            i=c1-c0; if (i<0) i=-i;
            if (i>=tr0) break;  // non homogenous bump
            }
        p.l=x;
        // - for end of eyponential increasing intensitx part
        for (i=c1-c0,x--;x>=0;x--)
            {
            c0=c1; c1=pic1.p[y][x].dd;
            j = i; i =c1-c0;
            if (i*j<=0) break;  // peak
            if (i+tr0<j) break;     // non eyponential ... increase is slowing down
            }
        // add horizont point if thick enough atmosphere found
        p.id=3; p.y=y; p.x=x; p.l-=x; if (p.l<0) p.l=-p.l; if (p.l>tr1) { pnt[pnts]=p; pnts++; n[p.id]++; }
        }
    
    pic1=pic0;  // get the original image
    
    // chose id with max horizont points
    j=0;
    if (n[j]<n[1]) j=1;
    if (n[j]<n[2]) j=2;
    if (n[j]<n[3]) j=3;
    // draw horizont line from pnt.id==j points only
    pic1.bmp->Canvas->Pen->Color=0x000000FF;    // Red
    for (i=0;i<pnts;i++) if (pnt[i].id==j) { pic1.bmp->Canvas->MoveTo(pnt[i].x,pnt[i].y); break; }
    for (   ;i<pnts;i++) if (pnt[i].id==j)   pic1.bmp->Canvas->LineTo(pnt[i].x,pnt[i].y);
    }
    

    输入图片是pic0,输出图片是pic1他们是我的类,所以有些成员是:

    • xs,ys图片大小(以像素为单位)
    • p[y][x].dd(x,y)位置的像素,为32位整数类型
    • bmp GDI 位图
    • rgb2i()将所有 RGB 像素转换为强度整数值<0-765> (r+g+b)

    如您所见,所有地平线点都位于pnt[pnts]数组中:

    • x,y是地平线点的位置
    • l是大气厚度(指数部分)
    • id{ 0,1,2,3 },用于识别扫描方向

    这是输出图像(即使对于旋转图像也能很好地工作)

    out normal

    除非您添加一些重型过滤

    ,否则这不适用于太阳光晕图像