代码之家  ›  专栏  ›  技术社区  ›  PvDev

在节点js中将360度视图转换为等矩形?

  •  6
  • PvDev  · 技术社区  · 6 年前

    在过去的两天里,我一直在尝试将360度摄像机、单鱼眼图像转换为node js中的等矩形查看器。在stackoverflow中,同样的问题是用伪代码提出和回答的。我一直在尝试将伪代码转换为节点js并清除一些错误。现在项目运行无误,但输出图像为空。

    从那个伪君子那里,我不知道 极地、极地和极地 值,所以,它给出了静态值来显示输出。下面是一个链接,我跟随它将伪代码转换为节点js。 How to convert spherical coordinates to equirectangular projection coordinates?

    下面是我试图将球面图像转换为等矩形查看器的代码:

     exports.sphereImage=(request, response)=>{
    
    
     var Jimp = require('jimp');
    
    
      // Photo resolution
    
    var img_w_px = 1280;
    var img_h_px = 720;
    
    var polar_w = 1280;
    var polar_h = 720;
    var geo_w = 1280;
    var geo_h = 720;
    
    var img_h_deg = 70;
    var img_w_deg = 30;
    
      // Camera field-of-view angles
    
    var img_ha_deg = 70;
    var img_va_deg = 40;
    
    
      // Camera rotation angles
    
     var  hcam_deg = 230;
     var  vcam_deg = 60;
    
      // Camera rotation angles in radians
    
     var hcam_rad = hcam_deg/180.0*Math.PI;
     var vcam_rad = vcam_rad/180.0*Math.PI;
    
     // Rotation around y-axis for vertical rotation of camera
    
      var rot_y = [
    
       [Math.cos(vcam_rad), 0, Math.sin(vcam_rad)],
       [0, 1, 0],
      [-Math.sin(vcam_rad), 0, Math.cos(vcam_rad)]
    
        ];
    
     // Rotation around z-axis for horizontal rotation of camera
    
      var rot_z = [
    
     [Math.cos(hcam_rad), -Math.sin(hcam_rad), 0],
     [Math.sin(hcam_rad), Math.cos(hcam_rad), 0],
     [0, 0, 1]
    
     ];
    
    
      Jimp.read('./public/images/4-18-2-42.jpg', (err, lenna) => {
    
    
        polar = new Jimp(img_w_px, img_h_px);
        geo = new Jimp(img_w_px, img_h_px);
    
      for(var i=0; i<img_h_px; ++i)
      {
      for(var j=0; j<img_w_px; ++j)
      {
        // var p = img.getPixelAt(i, j);
    
        var p = lenna.getPixelColor(i, j)
        // var p = getPixels(img, { x: i, y: j })
    
        // Calculate relative position to center in degrees
        var p_theta = (j - img_w_px / 2.0) / img_w_px * img_w_deg / 180.0 * Math.PI;
        var p_phi = -(i - img_h_px / 2.0) / img_h_px * img_h_deg / 180.0 *Math. PI;
    
        // Transform into cartesian coordinates
        var p_x = Math.cos(p_phi) * Math.cos(p_theta);
        var p_y = Math.cos(p_phi) * Math.sin(p_theta);
        var p_z = Math.sin(p_phi);
        var p0 = {p_x, p_y, p_z};
    
        // Apply rotation matrices (note, z-axis is the vertical one)
        // First vertically
        var p1 = rot_y[1][2][3] * p0;
        var p2 = rot_z[1][2][3] * p1;
    
        // Transform back into spherical coordinates
        var theta = Math.atan2(p2[1], p2[0]);
        var phi = Math.asin(p2[2]);
    
        // Retrieve longitude,latitude
        var longitude = theta / Math.PI * 180.0;
        var latitude = phi / Math.PI * 180.0;
    
        // Now we can use longitude,latitude coordinates in many different 
        projections, such as:
        // Polar projection
        {
            var polar_x_px = (0.5*Math.PI + phi)*0.5 * Math.cos(theta) 
       /Math.PI*180.0 * polar_w;
            var polar_y_px = (0.5*Math.PI + phi)*0.5 * Math.sin(theta) 
        /Math.PI*180.0 * polar_h;
            polar.setPixelColor(p, polar_x_px, polar_y_px);
        }
        // Geographical (=equirectangular) projection
        {
            var geo_x_px = (longitude + 180) * geo_w;
            var geo_y_px = (latitude + 90) * geo_h;
            // geo.setPixel(geo_x_px, geo_y_px, p.getRGB());
            geo.setPixelColor(p, geo_x_px, geo_y_px);
        }
          // ...
     }
    }
    
      geo.write('./public/images/4-18-2-42-00001.jpg');
     polar.write('./public/images/4-18-2-42-00002.jpg');
    
    
    
     });
    
    
    }
    

    另一种方法

    下面是我用来切片图像的代码:

     exports.sliceImage=(request, response)=>{
    
    var imageToSlices = require('image-to-slices');
    var lineXArray = [540, 540];
    var lineYArray = [960, 960];
    var source = './public/images/4-18-2-42.jpg'; // width: 300, height: 300
    
    imageToSlices(source, lineXArray, lineYArray, {
        saveToDir: './public/images/',
        clipperOptions: {
            canvas: require('canvas')
        }    
    }, function() {
        console.log('the source image has been sliced into 9 sections!');
    });
    
    
     }//sliceImage 
    

    我用opencv4nodejs从图像中检测汽车。车辆检测不正确。这是我用来检测汽车的代码:

       function runDetectCarExample(img=null){
            if(img==null){
    
             img = cv.imread('./public/images/section-1.jpg');
        }else
        {
             img=cv.imread(img);
        }
            const minConfidence = 0.06;
    
            const predictions = classifyImg(img).filter(res => res.confidence > minConfidence && res.className=='car');
    
            const drawClassDetections = makeDrawClassDetections(predictions);
    
            const getRandomColor = () => new cv.Vec(Math.random() * 255, Math.random() * 255, 255);
    
            drawClassDetections(img, 'car', getRandomColor);
            cv.imwrite('./public/images/section-'+Math.random()+'.jpg', img);
            var name="distanceFromCamera";
            var focalLen= 1.6 ;//Focal length in mm
            var realObjHeight=254 ;//Real Height of Object in mm
            var cameraFrameHeight=960;//Height of Image in pxl
            var imgHeight=960;//Image Height in pxl
            var sensorHeight=10;//Sensor height in mm
            var R = 6378.1 //#Radius of the Earth
            var brng = 1.57 //#Bearing is 90 degrees converted to radians.
            var hc=(200/100);//Camera height in m
            predictions
                .forEach((data)=> {
    
                    // imgHeight=img.rows;//Image Height in pxl
                    // realObjHeight=data.rect.height;
                    // data.rect[name]=((focalLen)*(realObjHeight)* 
             (cameraFrameHeight))/((imgHeight)*(sensorHeight));
    
                    var dc=(((data.rect.width * focalLen) / img.cols)*2.54)*100; // meters
                    console.log(Math.floor(parseInt(data.rect.width)));
                    // var dc=((Math.floor(parseInt(data.rect.width)* 0.264583) * focalLen) / img.cols); // mm
    
    
                    var lat1=13.0002855;//13.000356;
                    var lon1=80.2046441;//80.204632;
                    // Gate 13.0002855,80.2046441
                    // Brazil Polsec : -19.860566, -43.969436
                    // var d=Math.sqrt((dc*dc)+(hc*hc));
                    // d=(data.rect[name])/1000;
                    data.rect[name]=d=dc/1000;
                    lat1 =toRadians(lat1);
                    lon1 = toRadians(lon1);
                    brng =toRadians(90);
                    // lat2 = Math.asin( Math.sin(lat1)*Math.cos(d/R) +
                    //      Math.cos(lat1)*Math.sin(d/R)*Math.cos(brng));
    
                    // lon2 = lon1 + 
                 Math.atan2(Math.sin(brng)*Math.sin(d/R)*Math.cos(lat1),
                    //              Math.cos(d/R)-Math.sin(lat1)*Math.sin(lat2));
    
    
            var lat2 = Math.asin(Math.sin(lat1) * Math.cos(d/6371) +
                          Math.cos(lat1) * Math.sin(d/6371) * Math.cos(brng));
    
            var lon2 = lon1 + Math.atan2(Math.sin(brng) * Math.sin(d/6371) * Math.cos(lat1),
                                  Math.cos(d/6371) - Math.sin(lat1) * Math.sin(lat2));
    
                    lat2 = toDegrees(lat2);
                    lon2 = toDegrees(lon2);
                    data.rect['latLong']=lat2+','+lon2;
                    // console.log(brng);
    
                });
    
    
    
    
            response.send(predictions);
            cv.imshowWait('img', img);
        };
    

    这是鱼眼图像,需要转换成等矩形。

    非常感谢您的帮助。。。。

    1 回复  |  直到 6 年前
        1
  •  7
  •   Yeti    6 年前

    您正在询问如何将360度鱼眼投影转换为等矩形投影。

    为了做到这一点,对于鱼眼图像上的每个像素,您需要知道在输出图像上的位置。

    您的输入图像是1920x1080,假设您想将其输出为相同大小的等矩形投影。

    cx = 960; // center of circle on X-axis
    cy = 540; // center of circle on Y-axis
    radius = 540; // radius of circle
    

    如果像素位于 (x,y) 在输入图像中,我们可以使用以下方法计算球面坐标:

    dx = (x - cx) * 1.0 / radius;
    dy = (y - cy) * 1.0 / radius;
    theta_deg = atan2(dy, dx) / MATH_PI * 180;
    phi_deg = acos(sqrt(dx*dx + dy*dy)) / MATH_PI * 180;
    outputx = (theta_deg + 180) / 360.0 * outputwidth_px;
    outputy = (phi_deg + 90) / 180.0 * outputheight_px;
    

    所以我们翻译了 (x,y轴) (outputx,outputy) 在等矩形图像中。为了不让实现成为可怕的“读者练习”,下面是一些示例Javascript代码,它使用了OP使用的Jimp库:

    var jimp = require('jimp');
    var inputfile = 'input.png';
    jimp.read(inputfile, function(err, inputimage)
    {
        var cx = 960;
        var cy = 540;
        var radius = 540;
        var inputwidth = 1920;
        var inputheight = 1080;
        var outputwidth = 1920;
        var outputheight = 1080;
        new jimp(outputwidth, outputheight, 0x000000ff, function(err, outputimage)
        {
            for(var y=0;y<inputheight;++y)
            {
                for(var x=0;x<inputwidth;++x)
                {
                    var color = inputimage.getPixelColor(x, y);
                    var dx = (x - cx) * 1.0 / radius;
                    var dy = (y - cy) * 1.0 / radius;
                    var theta_deg = Math.atan2(dy, dx) / Math.PI * 180;
                    var phi_deg = Math.acos(Math.sqrt(dx*dx + dy*dy)) / Math.PI * 180;
                    var outputx = Math.round((theta_deg + 180) / 360.0 * outputwidth);
                    var outputy = Math.round((phi_deg + 90) / 180.0 * outputheight);
                    outputimage.setPixelColor(color, outputx, outputy);
                }
            }
            outputimage.write('output.png');
        });
    });
    

    另外,在你的情况下,你只有一半的球体(你看不到天空中的太阳)。所以你需要使用 var outputy = Math.round(phi_deg / 90.0 * outputheight) . 为了保持正确的纵横比,您可能需要将高度更改为 540

    还要注意,给定的实现可能根本没有效率,最好直接使用缓冲区。

    不管怎么说,没有混合,我得出的结果如下所示: equirectangular projection


    因此,为了进行混合,可以使用最简单的方法,即最近邻方法。在这种情况下,您应该反转以上示例中的公式。不必将像素从输入图像移动到输出图像的正确位置,您可以遍历输出图像中的每个像素,并询问我们可以使用哪个输入像素。这将避免黑色像素,但仍可能显示伪影:

    var jimp = require('jimp');
    var inputfile = 'input.png';
    jimp.read(inputfile, function(err, inputimage)
    {
        var cx = 960;
        var cy = 540;
        var radius = 540;
        var inputwidth = 1920;
        var inputheight = 1080;
        var outputwidth = 1920;
        var outputheight = 1080/2;
        var blendmap = {};
        new jimp(outputwidth, outputheight, 0x000000ff, function(err, outputimage)
        {
            for(var y=0;y<outputheight;++y)
            {
                for(var x=0;x<outputwidth;++x)
                {
                    var theta_deg = 360 - x * 360.0 / outputwidth - 180;
                    var phi_deg = 90 - y * 90.0 / outputheight;
                    var r = Math.sin(phi_deg * Math.PI / 180)
                    var dx = Math.cos(theta_deg * Math.PI / 180) * r;
                    var dy = Math.sin(theta_deg * Math.PI / 180) * r;
                    var inputx = Math.round(dx * radius + cx);
                    var inputy = Math.round(dy * radius + cy);
                    outputimage.setPixelColor(inputimage.getPixelColor(inputx, inputy), x, y);
                }
            }
            outputimage.write('output.png');
        });
    });
    

    以供参考,以便在笛卡尔坐标系和球面坐标系之间进行转换。这些是公式( taken from here z 在你的例子中只有1,一个所谓的“单位”球体,所以你可以把它排除在方程之外。你还应该明白,由于相机实际上是在三维拍摄照片,你也需要公式在三维工作。

    Spherical to Cartesian and Cartesian to Spherical

    下面是生成的输出图像:

    Output image using nearest neighbour mapping

    由于我在您的问题中看不到您的原始输入图像,因此为了让任何人测试此答案中的代码,您可以使用以下图像:

    Input image

    运行代码时使用:

    mkdir /tmp/test
    cd /tmp/test
    npm install --permanent jimp
    cat <<EOF >/tmp/test/main.js
    ... paste the javascript code from above ...
    EOF
    curl https://i.stack.imgur.com/0zWt6.png > input.png
    node main.js
    

    注意:为了进一步改善混合效果,您应该删除 Math.round . 例如,如果你需要在 x 是0.75,左边的像素在 x = 0 是白色的,右边的像素在 x = 1 是黑色的。然后将两种颜色混合成深灰色(使用比率0.75)。如果你想得到一个好的结果,你必须同时对两个维度都这样做。但这真的应该是一个新的问题。