Exploring Rotation Challenges in Photogrammetry Reference Cameras within a Three-Dimensional Environment Using THREE.JS and Reality Capture

Greetings to everyone who has taken the time to read through my post. I am hopeful that not only will this post be beneficial for me, but also for others who may come across it!

A Brief Overview

Currently, my focus is on a project involving point clouds created using photogrammetry techniques. These point clouds are a combination of photos and laser scans, generated using Reality Capture software. Apart from exporting the point cloud, one can also export "Internal/External camera parameters," which allows for retrieving the specific photos used to create a particular 3D point in the point cloud. Since Reality Capture lacks comprehensive online documentation, I have also reached out on their forum regarding camera variables. Perhaps this information could help resolve the current issue at hand?

For now, only a selected few variables listed in the camera parameters file are pertinent in referencing camera positioning, such as filename, x, y, altitude for location, heading, pitch, and roll for rotation.

https://i.sstatic.net/WxKxj.png

The generated point cloud is currently being loaded into a browser-compatible THREE.JS viewer, followed by loading the camera parameters .csv file. For each known photo, a 'PerspectiveCamera' is instantiated with a green cube representing it. An example is illustrated below:

https://i.sstatic.net/eaQcM.png

The Challenge Ahead

As you might have deduced from the preceding image (or perhaps the title of this post), the orientation of the cameras is all wrong. To visually showcase this discrepancy, I have crudely depicted vectors indicating the correct direction (marked in red) and the current orientation (in green).

https://i.sstatic.net/NiLo1.png https://i.sstatic.net/8KWSr.png

Row 37, 'DJI_0176.jpg,' represents the furthest right camera with a red reference line; row 38 corresponds to DJI_0177.jpg, and so forth. The last image (Row 48, DJI_189.jpg) aligns with the leftmost image within the clustered images.

Upon copying the data provided below into an Excel sheet, the display should present accurately ^^

#name   x   y   alt heading pitch   roll    f   px  py  k1  k2  k3  k4  t1  t2
DJI_0174.JPG    ...
...
...
DJI_0189.JPG    ...

Exploration Thus Far

An interesting discovery was made that the exported model appeared mirrored from reality; however, this mirroring did not impact the placement of the camera references, as they aligned perfectly. Attempts were made to mirror the referenced cameras, point cloud, and viewport camera, yet this did not rectify the ongoing issue (hence, the usage of 'camera.applyMatrix4(new THREE.Matrix4().makeScale(-1, 1, 1));').

Thus far, we have experimented with loading Euler angles, setting angles directly, or converting and applying Quaternions, unfortunately without yielding satisfactory results. The camera reference file is parsed utilizing the following logic:

// Code snippet showcasing parsing of CSV file
...

Below, the code snippet demonstrates the instantiation of a camera along with its position and rotation. Comments have been included elaborating on the process, while some commented-out lines reveal additional attempted strategies:

private createCamera(fileName: string, xPos: number, yPos: number, zPos: number, xDeg: number, yDeg: number, zDeg: number, f: number, isRadianFormat = false) : void {
// Camera creation function 
...
...
}

The cameras generated above are then processed by the subsequent piece of code, passing them to the camera manager and rendering a CameraHelper (depicted in both 3D viewer images). This section operates within an async function awaiting the loading of the CSV file before initializing the cameras.

private initializeCameraPoses(url: string, csvLoader: CSVLoader) {
// Initializing camera poses function
...
...
}

Marquizzo's Intervention

The modified code snippet provided by Marquizzo appears to bring us closer to a solution. The cameras now seem properly oriented, although there seems to be a slight discrepancy in the pitch. I will include an image of DJI_0189.jpg below for reference. Please note that, for this example, the FOV is not set, as rendering a camera helper for every camera position may appear cluttered. Only the DJI_0189 camera helper is rendered for demonstration purposes.

https://i.sstatic.net/dU7ww.jpg

Incorporating the adjustment recommended by Marquizzo to invert the pitch (

const rotX = deg2rad(photo.pitch * -1);
) resulted in the intersection point appearing slightly lower than expected: https://i.sstatic.net/GxCah.png

When adjusting the pitch to

const rotX = deg2rad(photo.pitch * -.5);
, the midpoint intersection closely resembles that of the source image: https://i.sstatic.net/8F8QB.png

I strongly believe that a resolution is within our grasp and that it may ultimately boil down to overlooking a small detail. I eagerly anticipate any feedback and assistance offered. Feel free to seek further details if anything remains unclear. Thank you for delving into this post!

Answer №1

Upon initial inspection, I am presented with three potential scenarios:

  • The issue regarding the createCamera() method remains unclear without a demonstration of how it is being utilized. It’s possible that there might be confusion between pitch and heading. In Three.js, heading represents rotation around the Y-axis, pitch denotes rotation around the X-axis, and roll pertains to rotation around the Z-axis.

  • Another aspect to consider is the sequence in which the heading, pitch, roll measurements were recorded by your sensor. This factor impacts the way you initialize your

    THREE.Euler(xRad, yRad, zRad, 'XYZ')
    , since choosing the appropriate order for applying rotations becomes crucial - options include
    'YZX', 'ZXY', 'XZY', 'YXZ' or 'ZYX'
    .

  • Furthermore, it’s essential to contemplate the interpretation of heading: 0 by the sensor. The meaning could vary between the real world and the coordinate system within Three.js. Within Three.js, a camera devoid of rotation looks directly downwards towards the -Z axis, whereas your sensor may have it oriented towards the +Z or +X axis.

Edit:

I have included a demo below which I believe addresses the requirements portrayed in the screenshots. Notice that I multiplied pitch * -1 to make the cameras "look down", and added +180 to the heading so they align correctly…in terms of heading.

const DATA = [
{name: "DJI_0174.JPG",  x: 3.116820957,     y: -44.25690188,    alt: 14.05258109,   heading: -26.86297007,  pitch: 66.43104338, roll: 1.912026354},
{name: "DJI_0175.JPG",  x: -5.22E-02,       y: -46.97266554,    alt: 14.18056658,   heading: -16.2033133,   pitch: 66.11532302, roll: 3.552072396},
{name: "DJI_0176.JPG",  x: -3.056586953,    y: -49.00754998,    alt: 14.3474763,    heading: 4.270483155,   pitch: 65.35247679, roll: 5.816970677},
{name: "DJI_0177.JPG",  x: -6.909437337,    y: -50.15910066,    alt: 14.38391206,   heading: 19.4459053,    pitch: 64.26828897, roll: 6.685020944},
{name: "DJI_0178.JPG",  x: -11.23696688,    y: -50.36025313,    alt: 14.56924433,   heading: 19.19192622,   pitch: 64.40188316, roll: 6.265995184},
{name: "DJI_0179.JPG",  x: -16.04060554,    y: -49.92320365,    alt: 14.69721478,   heading: 19.39979452,   pitch: 64.85507307, roll: 6.224929846},
{name: "DJI_0180.JPG",  x: -20.95614556,    y: -49.22915437,    alt: 14.92273203,   heading: 20.39327092,   pitch: 65.02028543, roll: 6.164031482},
{name: "DJI_0181.JPG",  x: -25.9335097,     y: -48.45330177,    alt: 15.37330388,   heading: 34.24388008,   pitch: 64.82707628, roll: 6.979877709},
{name: "DJI_0182.JPG",  x: -30.40507957,    y: -47.21269946,    alt: 15.67804925,   heading: 49.98858409,   pitch: 64.29238807, roll: 7.449650513},
{name: "DJI_0183.JPG",  x: -34.64277285,    y: -44.84034207,    alt: 15.89229254,   heading: 65.84203906,   pitch: 62.9109777,  roll: 7.065942792},
{name: "DJI_0184.JPG",  x: -39.17179024,    y: -40.22577764,    alt: 16.28164396,   heading: 65.53938063,   pitch: 63.2592604,  roll: 6.676581293},
{name: "DJI_0185.JPG",  x: -43.549378,      y: -33.09364534,    alt: 16.64130671,   heading: 68.61427166,   pitch: 63.15205908, roll: 6.258411625},
{name: "DJI_0186.JPG",  x: -46.5381556,     y: -24.2992233,     alt: 17.2286956,    heading: 74.42382577,   pitch: 63.75110346, roll: 6.279208736},
{name: "DJI_0187.JPG",  x: -48.18737751,    y: -14.67333218,    alt: 17.85446854,   heading: 79.54477952,   pitch: 63.0503902,  roll: 5.980759013},
{name: "DJI_0188.JPG",  x: -48.48581505,    y: -13.79840485,    alt: 17.84756621,   heading: 93.43316271,   pitch: 61.87561678, roll: 5.110113503},
{name: "DJI_0189.JPG",  x: -48.32815991,    y: -13.88055437,    alt: 17.77818573,   heading: 106.3277582,   pitch: 60.87171036, roll: 4.039469869},
];

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
    45,
    window.innerWidth / window.innerHeight,
    1,
    1000
);
camera.position.z = 100;

const renderer = new THREE.WebGLRenderer({
    antialias: true,
    canvas: document.querySelector("#canvas")
});
renderer.setSize(window.innerWidth, window.innerHeight);
const controls = new THREE.OrbitControls( camera, renderer.domElement );

// Helpers
const axesHelper = new THREE.AxesHelper( 20 );
scene.add(axesHelper);
const plane = new THREE.Plane( new THREE.Vector3( 0, 1, 0 ), 0 );
const planeHelper = new THREE.PlaneHelper( plane, 50, 0xffff00 );
scene.add(planeHelper);

let deg2rad = THREE.MathUtils.degToRad;

function createCam(photo) {
    let tempCam = new THREE.PerspectiveCamera(10, 2.0, 1, 30);
    // Altitude is actually y-axis,
    // "y" is actually z-axis 
    tempCam.position.set(photo.x, photo.alt, photo.y);

  // Modify pitch & heading so it matches Three.js coordinates
    const rotX = deg2rad(photo.pitch * -1);
    const rotY = deg2rad(photo.heading + 180);
    const rotZ = deg2rad(photo.roll);
  
    tempCam.rotation.set(rotX, rotY, rotZ, "YXZ");
    let helper = new THREE.CameraHelper(tempCam);
  scene.add(tempCam);
    scene.add(helper);
}

for(let i = 0; i < DATA.length; i++) {
    createCam(DATA[i]);
}

function animate() {
  renderer.render(scene, camera);
  requestAnimationFrame(animate);
}

animate();
html, body { margin:0; padding:0;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script><script src="https://rawgit.com/mrdoob/three.js/dev/examples/js/controls/OrbitControls.js"></script>

<canvas id="canvas"></canvas>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Troubleshooting JQuery AJAX HTML Problems in Google Chrome and Firefox

I'm facing an issue with my code and I'm not sure what to do. It works perfectly on Internet Explorer, but when I try to open it on Chrome or Mozilla, the links in my menu don't work! I click on them but nothing happens. Can someone please h ...

Encountered a CSS error while trying to compile the project with npm build

When attempting to build the project, I encountered a postcss error. After some debugging, I discovered that the imports below were causing the issue: @import "@material/button/dist/mdc.button.min.css"; /*material text box css*/ @import "@material/float ...

What is causing onbeforeunload to consistently display a dialog box?

I'm facing an issue where my javascript code displays a confirmation dialog even when there is no unsaved data. I have simplified the problem to this bare minimum: window.addEventListener("beforeunload", (e) => { e.returnValue = null; retu ...

An unexpected problem in Mongoose and Typescript

I encountered an issue while trying to update some data within my main interface. The problem arises when I attempt to update the data and receive an error mentioning that .save() is not defined. To address this issue, I created another interface to exten ...

The debate between client-side and server-side video encoding

My knowledge on this topic is quite limited and my Google search didn't provide any clear answers. While reading through this article, the author mentions: In most video workflows, there is usually a transcoding server or serverless cloud function ...

callback function for file upload completion in Angular 4

My Angular 4 app has a simple file upload feature that works well. However, I'm facing an issue with the onSuccessItem callback not triggering every time a file is uploaded. The callback only triggers after the first upload, and I need it to trigger f ...

Ways to differentiate between a desktop browser width of 1024px and a tablet width of 1024px with the help of jquery

So here's the issue I'm dealing with: I have scroll-based animation functions set up for desktop browsers, and different animations in place for tablet browsers. The challenge is to make these functions work on a desktop with a 1024px dimension. ...

Instructions on invoking a function when a button is clicked

I am attempting to trigger a dataUpdate function upon clicking the edit() button I am striving to modify the record Current Version: Angular CLI: 10.0.6 Angular: 10.0.10 https://i.sstatic.net/4MR8P.png registration.component.html <div> ...

Having trouble with toggling/display functionality in Javascript on my mobile device

I encountered an issue while using a script to toggle a div on click. Strangely, the content of the div failed to display properly on mobile devices such as Android and iOS. Despite my attempts to troubleshoot the problem, I was unable to identify the caus ...

MongoDB has encountered an issue where it is unable to create the property '_id' on a string

Currently, I am utilizing Node.js and Express on Heroku with the MongoDB addon. The database connection is functioning correctly as data can be successfully stored, but there seems to be an issue with pushing certain types of data. Below is the database c ...

Exploring Angular and Typescript - attempting to adjust cursor position for multiple child elements within a contenteditable div despite researching numerous articles on the topic

I could use some assistance in resolving this issue. Here is the stackblitz code I am currently working with If you have any workarounds, please share them with me. The caret/cursor keeps moving to the starting position and types backward. Can anyone hel ...

What steps can I take to fix the TS2705 error in my TypeScript code?

Issue TS2705: The use of async functions or methods in ES5/ES3 requires the 'Promise' constructor. Please ensure that you have included a declaration for the 'Promise' constructor or added 'ES2015' to your --lib option. impor ...

Custom date formatting with jQuery table sorting

I have been using a jQuery plugin called Tablesorter to sort a table. I am facing an issue with sorting dates in the yyyy MMM dd format, especially because my date inputs are in French. Here is an example of how the dates are formatted: 2014 janv. 05 20 ...

Swap out AJAX following a successful retrieval and when managing errors

I currently have a basic AJAX load function set up to load a specific URL: <div id="load"> <h1 id="status">Loading...</h1> </div> <script type="text/javascript"> $(document).ready(function(){ $.ajaxSetup({cache: false}); var ...

Having trouble with mapping a localStorage variable containing an Array of Objects, which results in the item disappearing

In my localStorage, I have an array called product_categories that contains various objects, each consisting of a string and a nested array of objects representing products belonging to each category. Despite seeking help from Appery's support team o ...

JavaScript - Attempting to Add Objects to Array Unsuccessful

After seeing this question raised multiple times, I am determined to find a solution. In my current project, I am tasked with displaying a list of orders and implementing a filter by date functionality. However, I keep encountering an error when trying to ...

How to Update a Method on an Input Field Using Datalist in Angular 7

I'm looking for a way to trigger a method when an option is selected from an input field. I have a list of Product[] objects with the following structure: Id: number Name: string For example, an object in this list could have an ID of 2 and Name of ...

What is the process for dynamically declaring a variable within the data function in Vue.js?

Looking for a solution where I can have the ability to dynamically add input fields to a form. Currently, my form consists of three input fields - name, email, and phone. However, I would like users to be able to add more input fields as needed. In order t ...

Trouble encountered when using RxJS zip and pipe together

In my Angular Resolver, I am facing a scenario where I need to wait for two server calls. The catch is that the second server call is optional and can be skipped based on user input. This data retrieval process is crucial for loading the next page seamless ...

When trying to use `express.json()` within a `mongoose.connect()` block, it seems to encounter

I am curious about something. Why does app.use(express.json) not function properly within mongoose.connect? Here is the first code snippet: mongoose.connect(DB, { useNewUrlParser: true, useUnifiedTopology: true }) .then(() => { app.use(&ap ...