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

时间:2018-08-16 04:01:56

标签: javascript node.js image-processing three.js projection

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

根据该伪指令,我不知道 polar_w,polar_h和geo_w,geo_h,geo和polar 的值,因此,它给出了静态值以显示输出。这是我将伪代码转换为节点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');



 });


}

并尝试了另一种方法,方法是将图像分成四个部分以检测汽车。使用image-slice模块和读写jimp模块将图像切成四个部分。但不幸的是,汽车检测不正确。

这是我用于切片图像的代码:

 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 个答案:

答案 0 :(得分: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)。为了不使该实现成为令人恐惧的“阅读者运动”,下面是一些使用OP使用的Jimp库的示例Javascript代码:

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的比率)。如果要获得良好的结果,则必须同时对两个维度执行此操作。但这真的应该是一个新问题,恕我直言。