【问题标题】:How to find horizon line efficiently in a high-altitude photo?如何在高空照片中有效地找到地平线?
【发布时间】:2014-04-06 21:19:13
【问题描述】:

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

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

这是我的代码(为了便于网络演示使用 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()

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

我该如何改进呢?有没有更有效的方法来做到这一点?最终的程序可能会用 Python 编写,如果太慢的话,也可以用 C 编写。

【问题讨论】:

  • 你可以做一个有一定阈值的floodfill。 (并且代码审查可能是正确的站点)
  • 您是否尝试过使用 HSV 颜色并仅使用 V 进行转向?似乎您的地平线是漆黑一片,而其他一切都不是,因此您应该能够获得非常一致的低值读数,并且它应该足够快地运行您所描述的内容。
  • 地平线不一定是漆黑一片。它可能是深蓝色;而我仍在努力寻找地面的边缘,而不是空气的边缘。
  • 我猜你不知道这张照片的拍摄高度?
  • 地球和大气弯曲成同一个角度,也许你可以用它来抵消地平线。

标签: javascript python c algorithm image-processing


【解决方案1】:

考虑一些基本的通道混合和阈值,然后按照@Spektre 的建议进行垂直采样。 [在@Spektre 的评论之后修改为 2*R-B 而不是 R+G-B]

以下是通道混合的一些选项:

  1. 原创
  2. 平面单声道混音 R+G+B
  3. 红色通道
  4. 2*R - B
  5. R + G - B

看起来#4 是最清晰的地平线(感谢@Spektre 让我更仔细地检查),以 [红色 2: 绿色 0: 蓝色 -1] 的比例混合颜色,你会得到这张单色图像:

设置蓝色负片意味着地平线上的蓝色雾霾被用来消除那里的模糊性。事实证明,这比仅使用红色和/或绿色更有效(尝试使用 GIMP 中的通道混合器)。

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

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

为提高稳健性,采集 3 个以上样本并丢弃异常值。

【讨论】:

  • +1 不错...在识别之前进行适当的过滤总是比识别之后更好。当然,在不同的大气中它可能会失去它的特性,但对于地球来说,这真是太棒了......
  • 顺便说一句,你只试过红色频道吗?由于散射,红色穿过我们的大气层时问题最少(这就是卫星主要使用红外摄像机的原因)
  • @Spektre,是的,我在 GIMP 中使用了通道混音器。如果你看一下雾霾,它开始是蓝色的,但最终更接近白色。看红色通道,那里有很轻的雾霾;红色的散射不是零,只是比蓝色少得多。通过减去蓝色,红色通道的雾度被去除,只剩下非蓝色。
  • @Spektre,在您发表评论后,我回去为不同的混合选项制作比较图像,发现 2*RB (#4) 比我的 R+GB (#5) 更清晰'本来就去了。因此,我根据这一点重做了其他图像。感谢您的提示,希望这可以使关于雾霾的选择更加清晰。
  • 确实非常好的过滤器输出用于此目的:) 如果我只知道我的问题的正确物理背景更频繁...会节省我很多时间...
【解决方案2】:

我会这样做:

  1. 转换为 BW

  2. 像这样从每一侧扫描水平线和垂直线

    垂直线从顶部扫描

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

    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近似一些曲线

[备注]

您不能根据高度移动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 位整数类型
  • bmpGDI 位图
  • rgb2i() 将所有 RGB 像素转换为强度整数值 &lt;0-765&gt; (r+g+b)

如您所见,所有水平点都在pnt[pnts] 数组中,其中:

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

这是输出图像(即使是旋转图像也能正常工作)

这不适用于太阳发光图像,除非您添加一些重型过滤

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-05-06
    • 2011-04-27
    • 2021-02-01
    • 1970-01-01
    • 2017-12-19
    • 1970-01-01
    • 1970-01-01
    • 2020-02-19
    相关资源
    最近更新 更多