This is very interesting !
I tried to use cubemaps too for this a few months ago.
This is very interesting !
I tried to use cubemaps too for this a few months ago.
Turns out there has already been some research ACCV 2018 Deep Upright Adjustment of 360 Panoramas Using Multiple Roll Estimations. My original approach extracts 4 sides/views and calculates the angle of each of them then averages them. The approach from the publication uses narrow 36 views with FOV of 60 degrees at 10 degrees apart, predicts the angles then map the ‘waviness’ of the tilt in the photo, exclude the outliers and finally use the results to level the photo.
It does significantly help with the tree/wall too close to the camera problem where those angle predictions differ from reality and can be excluded as outliers. The research publication doesn’t have code available, so I re-used what I already worked on. The current model which I still haven’t yet trained is producing too many outliers needed for sufficient levelling. It does mean almost all photos are level to within several degrees. I must be expecting too much.
but…
I started looking into newer projects. From 2024, there is cvg/GeoCalib: GeoCalib: Learning Single-image Calibration with Geometric Optimization (ECCV 2024) which combines deep learning, visual cues and geometric features. Even though it was trained on panoramic images, it isn’t built to input panoramic images. It was very easy to install and once again, I took the 36-view idea and tested with random Panoramax photos on bicycle. The results are very exciting, about 97% were suitably upright and without needing to do my own training! It did very well with all of the challenging photos from my first attempt.
Just as I was about to be done for the day, I saw one more very recent 2026 publication 360Anything: Geometry-Free Lifting of Images and Videos to 360°. They also use GeoCalib to upright panoramas by using only 8 views.
It would be very nice not needing to feel dizzy browsing Panoramax!
Updated proof of concept replacing it with the Panoramax viewer: https://leveltest.seen.one/
Some patience will be needed; i’m using an old laptop with only the CPU to calculates the corrections on the fly. It will feel very slow to load per 360° photo because it takes about 10 seconds to process (fast Nvidia GPU takes about 2 seconds). Ideally the corrections would have been pre-calculated, but for this demo, this should show great benefits to the bicycle mappers/viewers!
Time for an actual horizon correction API like the blurring API?
Very nice job ! The result is impressive !
On non 360° picture, a common error is full rotations (+/- 90° or event 180°).
I’ve quickly created a dataset and trained a model to detect them by rotating pictures myself :
It is a classifier which works on very small images (224x224 pixels) meaning we can run it on thumbnails and it takes around 10ms for an inference.
After fine-tuning, a YOLO26 “small” gives 99.7% accuracy and uses only around 500MB of VRAM.
I’ve tested with Geocalib on 4 reprojected cube faces.
Original :
First pass :
Second pass :
A second pass seems necessary to get a better result when the pitch+roll angles on the first pass are high (above 10° ?). Most of the time this second pass increases the first pass angles.
I’ve tested with the full and low resolution of the same pictures (16Mpx vs 2Mpx / 2048 pixels wide) and the results are similar, but the processing time is of course better.
On low res, one pass takes less than a second, two passes less than 2 seconds.
Geocalib inference uses less than 1GB of GPU VRAM.
Geocalib allows to provide the focal length when known, and here we know it because we choose it when generating cube faces from the equirectangular picture.
I’ve used 30° and tested other value with similar results.
The generated faces are 256x256 pixels :
More tests should be done on less urban pictures…
Ça a l’air très bien tout ça ! Dans quelle mesure est-ce industrialisable ?
Vu que ça consomme quand même pas mal de ressources, ce n’est pas un traitement que je verrai appliqué systématiquement, mais plutôt à la demande, soit du contributeur au moment de l’envoi ou après, soit quand quelqu’un signalerai des photos à remettre à l’horizontale.
Une possibilité serait de s’appuyer sur les tags sémantiques:
fixme:pitch=yes et/ou fixme:roll=yes)La correction manuelle parait une option envisageable, en effet. On peut imaginer une review collaborative des séquences existantes.
Et pourquoi pas corriger seulement la première photo d’une séquence (ou plutôt une des premières photos, pour éviter la première photo parfois ratée). Si la correction apparaît comme significative, alors et seulement alors on retraite toute la séquence.
Il faudrait aussi ajouter “photos de travers” dans les choix proposés au signalement d’une photo, comme il y a “photo surfloutée”.
J’ai signalé tout un lot de photos “pivotées” en cochant “autre” la semaine passée, et en détaillant chaque fois image de travers, etc.
Le grand “public”/commun des mortels non tech aura le réflexe de signaler la photo plutôt que de se lancer dans des tags.
In addition, I was thinking we can use existing tags/metadata to identify sequences that likely need correction like transport=bike and GoPro Max. Professional setups would less likely need correction like transport=car and imajbox/Ladybug/Trimble 360 cameras.
Yes and if there is already XMP tags with pitch/roll info, we can consider they are ok.
I see the manual tagging + bot solution as a first step, then if we can do more it could be nice after some feedback of the first step results.
On pourrait aussi intégrer ça côté poste utilisateur soit au moment du prétraitement (lancement d’un docker/app image) ou à postériori (quand l’API permettra de modifier les exifs).
Est ce que vous avez des tests sur des routes/voies inclinées (+5%) ? Dans les corrections manuelles que je fais c’est pas évident de trouver le bon référentiel avec une voie qui n’est pas parallèle à l’horizon.
Le même cas avec des pins d’alep (ces grands arbres qui ne sont jamais verticaux) semble un bon challenge pour pousser la correction dans ses retranchements ![]()
Oui, on peut très bien le faire avant versement, mais pour beaucoup de monde ce sera quand même pas évident et cela dépend beaucoup de comment on fait les prises de vues.
Effectivement il y a sûrement des cas moins évidents… il faut trouver des photos dans ce genre de situation pour faire des tests.
J’ai déjà rajouté une série d’images en forêt à mon lot de test dans ce but.
Q&D way to fix the images EXIFs with Docker based on @seen-one work
I was thinking of doing something similar ![]()
deep-image-orientation-angle-detection was the older project that I initially tested. GeoCalib is more accurate and is what is currently being used on leveltest.seen.one and what cquest is testing with their development.
Similar proof of concept code based on the initial repo I posted can be seen below. Sorry I should have posted the link earlier.
I’m doing the first test of a bot which :
fixme:roll=yes tagfixme:roll=yes into fixme:roll=fixed (WIP)Here is the first one processed like that:
Before the fix:
More before/after samples done by the bot with Geocalib:
It is currently running on some full sequences of several hundreds/thousands of pictures.
Some heuristic added to the bot… after fixing a picture:
This allows a single fixme:roll=yes added manually to start fixing many pictures.
Time to process one picture is around 1.3s on my workstation, which includes:
Currently more than 33k have been processed.