Converting 360 degree view to equirectangular in node js?
在过去的两天里,我一直在尝试将 360 度相机、单鱼眼图像转换为节点 js 中的 equirectangular 查看器。在 stackoverflow 中,同样的问题在伪代码中被询问和回答。我一直在尝试将伪代码转换为节点 js 并清除了一些错误。现在项目运行没有错误,但输出图像是空白的。
从那个伪,我不知道polar_w,polar_h和geo_w,geo_h,geo和极坐标值,所以,它给出了静态值来显示输出。这是我将伪代码转换为节点 js 的链接。
如何将球坐标转换为等角投影坐标?.
这是我尝试将球面图像转换为 equirectangular 查看器的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | 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 模块进行读写。但不幸的是,没有正确检测到汽车。
这是我用于切片图像的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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。未正确检测到汽车。这是我用于检测汽车的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | 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 度鱼眼投影转换为 equirectangular 投影。
为了做到这一点,对于鱼眼图像上的每个像素,您需要知道在输出图像上放置的位置。
您的输入图像是 1920x1080,让我们假设您要将其输出到相同大小的 equirectangular 投影。
输入圆映射定义为:
1 2 3 | cx = 960; // center of circle on X-axis cy = 540; // center of circle on Y-axis radius = 540; // radius of circle |
如果输入图像中有一个像素位于
1 2 3 4 5 6 | 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; |
因此,我们将鱼眼图像中的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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'); }); }); |
请注意,您仍然需要将像素与相邻像素混合(与调整图像大小时的原因相同)。
此外,在您的情况下,您只有一半的球体(您看不到天空中的太阳)。所以你需要使用
还要注意,给定的实现可能根本没有效率,最好直接使用缓冲区。
无论如何,在没有混合的情况下,我得出了如下所示的结果:
因此,为了进行混合,您可以使用最简单的方法,即最近邻方法。在这种情况下,您应该反转上面示例中的公式。无需将输入图像中的像素移动到输出图像中的正确位置,您可以遍历输出图像中的每个像素并询问我们可以使用哪个输入像素。这将避免黑色像素,但仍可能显示伪影:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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'); }); }); |
仅供参考,以便在笛卡尔坐标系和球坐标系之间进行转换。这些是公式(取自此处)。请注意,在您的情况下,
这是生成的输出图像:
由于我不再在您的问题中看到您的原始输入图像,为了让任何人测试此答案中的代码,您可以使用以下图像:
运行代码:
1 2 3 4 5 6 7 8 | 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 |
注意:为了进一步改进混合,您应该删除