草庐IT

javascript - 在 Node js 中将 360 度 View 转换为等距柱状 View ?

coder 2024-12-18 原文

这两天我一直在尝试将 360 度相机、单鱼眼图像转换为 node js 中的 equirectangular viewer。在 stackoverflow 中,同样的问题是用伪代码提出和回答的。我一直在尝试将伪代码转换为 Node js并清除一些错误。现在项目运行没有错误,但输出图像是空白的。

从那个伪,我不知道 polar_w、polar_h 和 geo_w、geo_h、geo 和 polar 值,因此,它给出了静态值来显示输出。这是我用来将伪代码转换为 Node 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);
    };

这里是鱼眼图,需要转成等距柱状图。

非常感谢任何帮助....

最佳答案

您问的是如何将 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

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

无论如何,在没有混合的情况下,我得出了如下所示的结果:


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

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,即所谓的“单位”球体,因此您可以将其排除在方程式之外。您还应该了解,由于相机实际上是在三维空间拍摄照片,因此您还需要公式才能在三维空间工作。

这是生成的输出图像:

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

运行代码:

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

注意:为了进一步改进混合,您应该删除Math.round。因此,例如,如果您需要在 x 处抓取一个像素为 0.75,并且在 x = 0 左侧的像素为白色,而右侧的像素为x = 1 是黑色的。然后您想将两种颜色混合成深灰色(使用比率 0.75)。如果你想要一个好的结果,你必须同时对两个维度执行此操作。但这真的应该是一个新问题恕我直言。

关于javascript - 在 Node js 中将 360 度 View 转换为等距柱状 View ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51869432/

有关javascript - 在 Node js 中将 360 度 View 转换为等距柱状 View ?的更多相关文章

  1. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

    我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

  2. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  3. ruby - 将数组的内容转换为 int - 2

    我需要读入一个包含数字列表的文件。此代码读取文件并将其放入二维数组中。现在我需要获取数组中所有数字的平均值,但我需要将数组的内容更改为int。有什么想法可以将to_i方法放在哪里吗?ClassTerraindefinitializefile_name@input=IO.readlines(file_name)#readinfile@size=@input[0].to_i@land=[@size]x=1whilex 最佳答案 只需将数组映射为整数:@land边注如果你想得到一条线的平均值,你可以这样做:values=@input[x]

  4. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  5. ruby-on-rails - 渲染另一个 Controller 的 View - 2

    我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>

  6. ruby - 将散列转换为嵌套散列 - 2

    这道题是thisquestion的逆题.给定一个散列,每个键都有一个数组,例如{[:a,:b,:c]=>1,[:a,:b,:d]=>2,[:a,:e]=>3,[:f]=>4,}将其转换为嵌套哈希的最佳方法是什么{:a=>{:b=>{:c=>1,:d=>2},:e=>3,},:f=>4,} 最佳答案 这是一个迭代的解决方案,递归的解决方案留给读者作为练习:defconvert(h={})ret={}h.eachdo|k,v|node=retk[0..-2].each{|x|node[x]||={};node=node[x]}node[

  7. ruby-on-rails - 如何在我的 Rails 应用程序 View 中打印 ruby​​ 变量的内容? - 2

    我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby​​中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R

  8. ruby-on-rails - 如何在 Rails View 上显示错误消息? - 2

    我是rails的新手,想在form字段上应用验证。myviewsnew.html.erb.....模拟.rbclassSimulation{:in=>1..25,:message=>'Therowmustbebetween1and25'}end模拟Controller.rbclassSimulationsController我想检查模型类中row字段的整数范围,如果不在范围内则返回错误信息。我可以检查上面代码的范围,但无法返回错误消息提前致谢 最佳答案 关键是您使用的是模型表单,一种显示ActiveRecord模型实例属性的表单。c

  9. ruby-on-rails - Ruby url 到 html 链接转换 - 2

    我正在使用Rails构建一个简单的聊天应用程序。当用户输入url时,我希望将其输出为html链接(即“url”)。我想知道在Ruby中是否有任何库或众所周知的方法可以做到这一点。如果没有,我有一些不错的正则表达式示例代码可以使用... 最佳答案 查看auto_linkRails提供的辅助方法。这会将所有URL和电子邮件地址变成可点击的链接(htmlanchor标记)。这是文档中的代码示例。auto_link("Gotohttp://www.rubyonrails.organdsayhellotodavid@loudthinking.

  10. ruby-on-rails - 使用 ruby​​ 将多个实例变量转换为散列的更好方法? - 2

    我收到格式为的回复#我需要将其转换为哈希值(针对活跃商家)。目前我正在遍历变量并执行此操作:response.instance_variables.eachdo|r|my_hash.merge!(r.to_s.delete("@").intern=>response.instance_eval(r.to_s.delete("@")))end这有效,它将生成{:first="charlie",:last=>"kelly"},但它似乎有点hacky和不稳定。有更好的方法吗?编辑:我刚刚意识到我可以使用instance_variable_get作为该等式的第二部分,但这仍然是主要问题。

随机推荐