});

Photogrammetry Reconstruction

photogrammetry softwares
The {stack-exchange} post show a free workflow confirmed by {this post}. Among them, Meshroom and {3DF Zephyr Free} seem promising.

Meshroom photogrammetry reconstruction

The graphic programming is saved as json file, thus use Gemini (the basic Free Fast version) to write a minimal reconstruction to get point cloud, i.e., structure from motion, sfm. The nodes are like this

The corresponding script are attached below. Copy and save it as a .mg file then open it as a project in the meshroom GUI. First time running, it may pop upgrade nodes, just agree. There are may dynamic placeholder will be automatically hashed, e.g., {cache}/{nodeType}/{uid0}.
A trick is to clear pending status (in the “⋮” in graphic editor header) and remove all images before passing to Gemini. Because the image info takes a lot of lines.

Source code
{
    "header": {
        "pipelineVersion": "2.2",
        "releaseVersion": "2023.3.0",
        "fileVersion": "1.1",
        "template": false,
        "nodesVersions": {
            "CameraInit": "9.0",
            "StructureFromMotion": "3.3",
            "FeatureExtraction": "1.3",
            "ImageMatching": "2.0",
            "FeatureMatching": "2.0"
        }
    },
    "graph": {
        "CameraInit_1": {
            "nodeType": "CameraInit",
            "position": [0, 0],
            "parallelization": { "blockSize": 0, "size": 0, "split": 1 },
            "uids": { "0": "961e54591174ec5a2457c66da8eadc0cb03d89ba" },
            "internalFolder": "{cache}/{nodeType}/{uid0}/",
            "inputs": {
                "viewpoints": [],
                "intrinsics": [],
                "sensorDatabase": "${ALICEVISION_SENSOR_DB}",
                "defaultFieldOfView": 45.0,
                "viewIdMethod": "metadata"
            },
            "outputs": {
                "output": "{cache}/{nodeType}/{uid0}/cameraInit.sfm"
            }
        },
        "FeatureExtraction_1": {
            "nodeType": "FeatureExtraction",
            "position": [200, 0],
            "parallelization": { "blockSize": 40, "size": 0, "split": 0 },
            "uids": { "0": "7e0174439ccb598ec8c32e23cf2a1da4cd6ab00c" },
            "internalFolder": "{cache}/{nodeType}/{uid0}/",
            "inputs": {
                "input": "{CameraInit_1.output}",
                "describerTypes": ["dspsift"],
                "describerPreset": "normal",
                "describerQuality": "low",
                "forceCpuExtraction": true
            },
            "outputs": {
                "output": "{cache}/{nodeType}/{uid0}/"
            }
        },
        "ImageMatching_1": {
            "nodeType": "ImageMatching",
            "position": [400, 0],
            "parallelization": { "blockSize": 0, "size": 0, "split": 1 },
            "uids": { "0": "896a53c0e4aaf5e02ec421857f1f30439cfe3950" },
            "internalFolder": "{cache}/{nodeType}/{uid0}/",
            "inputs": {
                "input": "{CameraInit_1.output}",
                "featuresFolders": ["{FeatureExtraction_1.output}"],
                "method": "VocabularyTree",
                "tree": "${ALICEVISION_VOCTREE}"
            },
            "outputs": {
                "output": "{cache}/{nodeType}/{uid0}/imageMatches.txt"
            }
        },
        "FeatureMatching_1": {
            "nodeType": "FeatureMatching",
            "position": [600, 0],
            "parallelization": { "blockSize": 20, "size": 0, "split": 0 },
            "uids": { "0": "b446f57fb541abb70e8dc68c47a37e87ec84beff" },
            "internalFolder": "{cache}/{nodeType}/{uid0}/",
            "inputs": {
                "input": "{ImageMatching_1.input}",
                "featuresFolders": "{ImageMatching_1.featuresFolders}",
                "imagePairsList": "{ImageMatching_1.output}",
                "describerTypes": "{FeatureExtraction_1.describerTypes}"
            },
            "outputs": {
                "output": "{cache}/{nodeType}/{uid0}/"
            }
        },
        "StructureFromMotion_1": {
            "nodeType": "StructureFromMotion",
            "position": [800, 0],
            "parallelization": { "blockSize": 0, "size": 0, "split": 1 },
            "uids": { "0": "1d7db1354dab04f156d46106b0b6e27aa0570be3" },
            "internalFolder": "{cache}/{nodeType}/{uid0}/",
            "inputs": {
                "input": "{FeatureMatching_1.input}",
                "featuresFolders": "{FeatureMatching_1.featuresFolders}",
                "matchesFolders": ["{FeatureMatching_1.output}"],
                "describerTypes": "{FeatureMatching_1.describerTypes}",
                "computeStructureColor": true
            },
            "outputs": {
                "output": "{cache}/{nodeType}/{uid0}/sfm.abc"
            }
        }
    }
}Code language: JSON / JSON with Comments (json)

Click Start on top of the panel. The nodes will show different colors: Green – Done; Yellow – Running; Blue – Queueing; Red – Error.
After the SfM node is done, find the reconstructed point cloud in {project-file-dir}/MeshroomCache/StructureFromMotion/{latest-UID}/sfm.abc
The .abc file is efficient for visual effect (VFX) but hard to visualize, so convert it to .ply file using the built-in converter.

# add it to env path in powershell if not yet
$env:ALICEVISION_ROOT = "D:/AYX/Softwares/Meshroom/aliceVision"

## convert .abc to ply
.\aliceVision_exportColoredPointCloud.exe --input "{your_path}\sfm.abc" --output "{your_path}\{file_name}.ply"Code language: PHP (php)

I use python pyvista to visualize them, the colors of the dots are saved under the ‘RGB’ entry.

Python Source Code
import pyvista as pv
import numpy as np
pv.set_jupyter_backend('html')
# Load the PLY you exported
cloud = pv.read(r"{your_file}.ply")

# CRITICAL: Re-center the data to (0,0,0) so you can see it
# We subtract the average position of all points
center = np.mean(cloud.points, axis=0)
cloud.points = cloud.points - center

print(f"Data centered by subtracting: {center}")

# use this to find the color key
print(f'color stored in [{cloud.array_names}]')

# Visualize with PyVista
pl = pv.Plotter()
# Scalars="rgb" ensures the trees and pit look real
pl.add_mesh(cloud, scalars="RGB", rgb=True, point_size=3.0, render_points_as_spheres=True)
pl.add_axes()
pl.show_grid()
pl.show()
pl.export_html(r'{your_output_path}\preview.html')Code language: PHP (php)

Example output show below with an orthophoto for reference. Cameras are colored as green, too. Left Click to orbit, shift + LMouse to pan, Ctrl+LM to pinch rotate, scroll to zoom.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.