First DJI OSMO 360 impressions in better lighting conditions.
8k 25ffs
Lens alignment sideways to the vehicle.
First DJI OSMO 360 impressions in better lighting conditions.
8k 25ffs
Lens alignment sideways to the vehicle.
The quality is really stunning.
Maybe even better than the GoPro MAX2! I just do not know whether this is due to the Mapillary processing pipeline botching MAX2 imagery, MAX2 quality being slightly worse, or just the weather.
Anyhow, one can though clearly see that the wide fixed aperture really does not do the OSMO any good. DJI would have been well advised to tighten the aperture because this would have also de facto increased the OSMO’s effective scanning range. Imho digital sharpening cannot compensate for true physical sharpness. AI sharpening will not help here either. You just cannot argue with physics. ![]()
The timestamps on the sequence you shared clearly indicate that the video has been captured at 25 fps. Good job! ![]()
Now, since you have a tool for it and have been modifying point density in GPX tracks, why don’t you increase point density to match every frame? This should give best results on Mapillary.
I made a mistake and simply tried to transfer my workflow that worked perfectly with the Insta360 X3 straight to the DJI Osmo 360. It didn’t work at all. I quickly realized that with the DJI I was losing every single microsecond in the timing data.I stayed up all night yesterday, coding until 2 a.m. The first upload result was disappointing, but my current workflow is now looking extremely promising.
A new upload with microsecond-accurate GPX timestamps is running right now.
I’m now getting far more usable GPX points by drastically reducing the number of nodes the camera outputs – currently by an astonishing factor of 20. Before, almost all nodes were identical duplicates. This reduction finally gives me a GPX track that’s actually editable, which is absolutely essential with this camera.The next challenge is figuring out how to deal with the sometimes massive outliers in the GPX track. If I can’t solve that, the whole investment in this camera will unfortunately have been for nothing. Yes, the image quality is fantastic – a real joy to watch. But if I can’t eliminate these GPX errors, the DJI Osmo will end up being just my secondary camera for quick shots during driving breaks.
Right now, that means I have to remove the roof camera each time, take the quick shots, and then re-mount it on the rig before continuing. Instead, I’d then just put an Insta360 X5 on the car roof.I love driving through tunnels – and that’s exactly where the risk of a rollover is much higher when the camera restarts recording every 14 minutes (DJI) instead of only every 30 minutes like with the Insta360.On the plus side, the big advantage of the DJI is that it starts recording the moment I drive off and only stops after 5 hours – of course powered via USB-C the whole time. That would finally allow me to mount it permanently on the roof together with the remote.
I’m trying to streamline the rather tedious prep work needed to get images from this camera into Mapillary. Right now, the beautiful results are the only thing that makes it worth the hassle.
A process to make DJI OSMO 360 recordings usable for Streetview purposes.
EXPERIMENTAL
Starting Point 1
The Excel VBA macro Sub C_BatchReduceGPX_28_XML() uses the UL2GSV tool OSV2GPX to reduce GPX data from raw recordings obtained with a DJI OSMO 360 camera by a factor of 28 (the optimal reduction value still needs to be determined). The macro requires a directory containing GPX data for processing. The reduced GPX data is then exported to a directory named “reduced”.
Intermediate Step Missing GPX points (for example, in tunnels) are checked and added using the JOSM Editor. Ensure an even distribution of added points; I use a consistent 30-meter spacing. Drive through tunnels at a constant speed.
Starting Point 2
The macro Sub D_BatchProcessFüller() imports GPX data from the “reduced” directory, including any data that has been processed or supplemented with JOSM. It then adds missing timestamps via interpolation and finally exports the GPX data to a directory named “Export”. The GPX datasets also receive the suffix “_dated” (since Mapillary requires identical .gpx and .mp4 file pairs).
2025-11-19T12:32:47.956376Z.Date-Objekt.ParseIsoTimeToDateAndUs erzeugt.
Sub C_BatchReduceGPX_28_XML()
Dim fso As Object, folderPath As String, file As Object
Dim xmlDoc As Object, trkpts As Object, newDoc As Object
Dim root As Object, trkNode As Object, trksegNode As Object
Dim inputFile As String, outputFolder As String, outputPath As String
Dim i As Long, hasTime As Boolean
With Application.FileDialog(msoFileDialogFolderPicker)
.Title = "GPX-Verzeichnis auswählen"
If .Show <> -1 Then Exit Sub
folderPath = .SelectedItems(1)
End With
Set fso = CreateObject("Scripting.FileSystemObject")
Dim parentFolder As String
parentFolder = fso.GetParentFolderName(folderPath)
outputFolder = parentFolder & "\GPX_Reduced_" & Format(Now, "yyyymmdd_hhnnss")
If Not fso.FolderExists(outputFolder) Then fso.CreateFolder outputFolder
For Each file In fso.GetFolder(folderPath).Files
If LCase(fso.GetExtensionName(file.Name)) = "gpx" Then
inputFile = file.Path
Set xmlDoc = CreateObject("MSXML2.DOMDocument.6.0")
xmlDoc.async = False
xmlDoc.Load inputFile
If xmlDoc.ParseError.ErrorCode <> 0 Then GoTo NextFile
xmlDoc.SetProperty "SelectionNamespaces", _
"xmlns:gpx='http://www.topografix.com/GPX/1/1'"
Set trkpts = xmlDoc.SelectNodes("//gpx:trkpt")
If trkpts Is Nothing Then GoTo NextFile
Set newDoc = CreateObject("MSXML2.DOMDocument.6.0")
newDoc.appendChild newDoc.createProcessingInstruction("xml", "version='1.0' encoding='UTF-8'")
Set root = newDoc.createElement("gpx")
root.setAttribute "xmlns", "http://www.topografix.com/GPX/1/1"
root.setAttribute "version", "1.1"
root.setAttribute "creator", "GPX Reducer"
' Optional: SchemaLocation
root.setAttribute "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"
root.setAttribute "xsi:schemaLocation", "http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"
newDoc.appendChild root
Set trkNode = newDoc.createElement("trk")
root.appendChild trkNode
Set trksegNode = newDoc.createElement("trkseg")
trkNode.appendChild trksegNode
For i = 0 To trkpts.Length - 1
hasTime = Not trkpts.Item(i).SelectSingleNode("gpx:time") Is Nothing
If hasTime Then
If i Mod 28 = 0 Then
trksegNode.appendChild trkpts.Item(i).CloneNode(True)
End If
Else
trksegNode.appendChild trkpts.Item(i).CloneNode(True)
End If
Next i
outputPath = outputFolder & "\" & file.Name
newDoc.Save outputPath
End If
NextFile:
Next file
MsgBox "Alle GPX-Dateien erfolgreich reduziert: " & vbCrLf & outputFolder, vbInformation
End Sub
Sub D_BatchProcessFüller()
Dim fso As Object, folderPath As String, file As Object
Dim ws As Worksheet
Dim fileName As String, ext As String
Dim Pfad_und_Datei As String
With Application.FileDialog(msoFileDialogFolderPicker)
.Title = "GPX-Verzeichnis auswählen"
If .Show <> -1 Then Exit Sub
folderPath = .SelectedItems(1)
End With
Set fso = CreateObject("Scripting.FileSystemObject")
Sheets("GPX_Import").Select
Set ws = Worksheets("GPX_Import")
For Each file In fso.GetFolder(folderPath).Files
fileName = file.Name
ext = LCase(fso.GetExtensionName(fileName))
' DJI-Dateien beginnen mit "CAM_" – Bedingung darauf, plus .gpx
If ext = "gpx" And Left$(fileName, 4) = "CAM_" Then
Pfad_und_Datei = file.Path
ws.Cells.Clear
ws.Range("K5").value = fileName
ws.Range("K6").value = Pfad_und_Datei
ImportAndFixGPX Pfad_und_Datei
ExportGPXWithBounds
End If
Next file
Set fso = Nothing
Set file = Nothing
MsgBox "Alle GPX-Dateien wurden verarbeitet und exportiert.", vbInformation
End Sub
Sub ImportAndFixGPX(Pfad_und_Datei As String)
Dim xmlDoc As Object, trkpts As Object, boundsNode As Object
Dim i As Long
Dim ws As Worksheet
Dim lastRow As Long
Dim ergänzteZeitpunkte As Long
Dim boundsText As String
Dim prevDt As Date, prevUs As Long
Dim nextDt As Date, nextUs As Long
Dim gapStart As Long, gapEnd As Long
Dim gapCount As Long
Dim prevSec As Double, nextSec As Double
Dim stepSec As Double, newSec As Double
Dim dtOut As Date, usOut As Long
ergänzteZeitpunkte = 0
Sheets("GPX_Import").Select
Set ws = Worksheets("GPX_Import")
ws.Cells.Clear
' Datei-Infos
Dim fso As Object
Set fso = CreateObject("Scripting.FileSystemObject")
ws.Range("K5").value = fso.GetFileName(Pfad_und_Datei)
ws.Range("K6").value = Pfad_und_Datei
' XML laden
Set xmlDoc = CreateObject("MSXML2.DOMDocument.6.0")
xmlDoc.async = False: xmlDoc.Load Pfad_und_Datei
If xmlDoc.ParseError.ErrorCode <> 0 Then
MsgBox "Fehler beim Laden der GPX-Datei: " & xmlDoc.ParseError.Reason, vbCritical
Exit Sub
End If
xmlDoc.SetProperty "SelectionNamespaces", "xmlns:gpx='http://www.topografix.com/GPX/1/1'"
' Bounds
Set boundsNode = xmlDoc.SelectSingleNode("//gpx:bounds")
If Not boundsNode Is Nothing Then
boundsText = "<bounds minlat=""" & boundsNode.getAttribute("minlat") & _
""" minlon=""" & boundsNode.getAttribute("minlon") & _
""" maxlon=""" & boundsNode.getAttribute("maxlon") & _
""" maxlat=""" & boundsNode.getAttribute("maxlat") & """ />"
ws.Range("K2").value = boundsText
Else
ws.Range("K2").value = "Keine Bounds gefunden"
End If
' Trackpunkte extrahieren
Set trkpts = xmlDoc.SelectNodes("//gpx:trkpt")
ws.Cells(1, 1).value = "Lat"
ws.Cells(1, 2).value = "Lon"
ws.Cells(1, 3).value = "Ele"
ws.Cells(1, 4).value = "Time (raw)"
ws.Cells(1, 5).value = "Time (Date)"
ws.Cells(1, 6).value = "Time (ISO us)"
ws.Cells(1, 7).value = "Microseconds"
Dim timeFound As Boolean
timeFound = False
For i = 0 To trkpts.Length - 1
ws.Cells(i + 2, 1).value = trkpts.Item(i).getAttribute("lat")
ws.Cells(i + 2, 2).value = trkpts.Item(i).getAttribute("lon")
If Not trkpts.Item(i).SelectSingleNode("gpx:ele") Is Nothing Then
ws.Cells(i + 2, 3).value = trkpts.Item(i).SelectSingleNode("gpx:ele").Text
End If
If Not trkpts.Item(i).SelectSingleNode("gpx:time") Is Nothing Then
Dim rawTime As String, dt As Date, us As Long
rawTime = trkpts.Item(i).SelectSingleNode("gpx:time").Text
ws.Cells(i + 2, 4).value = rawTime
dt = ParseIsoTimeToDateAndUs(rawTime, us)
ws.Cells(i + 2, 5).value = dt
ws.Cells(i + 2, 5).NumberFormat = "yyyy-mm-dd hh:mm:ss"
ws.Cells(i + 2, 7).value = us
ws.Cells(i + 2, 6).value = FormatDateWithMicroseconds(dt, us)
timeFound = True
End If
Next i
If Not timeFound Then
MsgBox "Keine Zeitinformationen gefunden, Import abgebrochen.", vbCritical
Exit Sub
End If
' Interpolation mit Mikrosekunden startet hier ...
' Interpolation mit Mikrosekunden
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
prevDt = 0: prevUs = 0
For i = 2 To lastRow
Dim hasDate As Boolean
hasDate = IsDate(ws.Cells(i, 5).value)
If hasDate Then
' Zeitwert vorhanden ? direkt übernehmen
Dim curDt As Date, curUs As Long
curDt = ws.Cells(i, 5).value
curUs = CLng(Val(ws.Cells(i, 7).value))
' Ordnung sicherstellen: keine Rückwärtszeit
If DateUsToSeconds(curDt, curUs) < DateUsToSeconds(prevDt, prevUs) Then
Dim adjSec As Double, dtAdj As Date, usAdj As Long
adjSec = DateUsToSeconds(prevDt, prevUs) + 0.000001 ' +1 µs
SecondsToDateUs prevDt, adjSec, dtAdj, usAdj
curDt = dtAdj: curUs = usAdj
ws.Cells(i, 5).value = curDt
ws.Cells(i, 7).value = curUs
End If
' ISO-Zeit mit Mikrosekunden schreiben
ws.Cells(i, 6).value = FormatDateWithMicroseconds(curDt, curUs)
prevDt = curDt: prevUs = curUs
Else
' Lücke erkennen
gapStart = i - 1
gapEnd = i
Do While gapEnd <= lastRow And Not IsDate(ws.Cells(gapEnd, 5).value)
gapEnd = gapEnd + 1
Loop
If gapEnd <= lastRow And IsDate(ws.Cells(gapEnd, 5).value) Then
' Zeitpunkte vor und nach der Lücke holen
prevDt = ws.Cells(gapStart, 5).value
prevUs = CLng(Val(ws.Cells(gapStart, 7).value))
nextDt = ws.Cells(gapEnd, 5).value
nextUs = CLng(Val(ws.Cells(gapEnd, 7).value))
' Sekundenbasis berechnen
prevSec = DateUsToSeconds(prevDt, prevUs)
nextSec = DateUsToSeconds(nextDt, nextUs)
gapCount = gapEnd - gapStart - 1
stepSec = (nextSec - prevSec) / (gapCount + 1)
' Interpolierte Werte einfügen
For j = gapStart + 1 To gapEnd - 1
newSec = prevSec + stepSec * (j - gapStart)
SecondsToDateUs prevDt, newSec, dtOut, usOut
ws.Cells(j, 5).value = dtOut
ws.Cells(j, 5).NumberFormat = "yyyy-mm-dd hh:mm:ss"
ws.Cells(j, 7).value = usOut
ws.Cells(j, 6).value = FormatDateWithMicroseconds(dtOut, usOut)
ergänzteZeitpunkte = ergänzteZeitpunkte + 1
Next j
' Schleifenindex vorziehen und prev aktualisieren
i = gapEnd - 1
prevDt = ws.Cells(i, 5).value
prevUs = CLng(Val(ws.Cells(i, 7).value))
End If
End If
Next i
' Anzahl der ergänzten Zeitpunkte ausgeben
ws.Range("K4").value = ergänzteZeitpunkte
' Spaltenbreite anpassen
ws.Columns(4).AutoFit
ws.Columns(5).AutoFit
ws.Columns(6).AutoFit
ws.Columns(7).AutoFit
End Sub
'======================================================================================================
Sub ExportGPXWithBounds()
Dim ws As Worksheet
Dim i As Long, lastRow As Long
Dim minLat As Double, maxLat As Double, minLon As Double, maxLon As Double
Dim boundsCalculated As String
Dim gpxText As String, trkptText As String
Dim boundsText As String, fileName As String
Dim exportFolder As String, outputPath As String
Dim TimeZoneOffset As Integer
Dim currentTime As String
Dim fso As Object
Dim originalPath As String, originalFolder As String, parentFolder As String
Dim exportBaseName As String
Dim folderItem As Object
Dim exportFound As Boolean
Set ws = Worksheets("GPX_Import")
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
' === Schritt 6: Eigene Bounds berechnen ===
minLat = 90: maxLat = -90: minLon = 180: maxLon = -180
For i = 2 To lastRow
If IsNumeric(ws.Cells(i, 1).value) And IsNumeric(ws.Cells(i, 2).value) Then
If ws.Cells(i, 1).value < minLat Then minLat = ws.Cells(i, 1).value
If ws.Cells(i, 1).value > maxLat Then maxLat = ws.Cells(i, 1).value
If ws.Cells(i, 2).value < minLon Then minLon = ws.Cells(i, 2).value
If ws.Cells(i, 2).value > maxLon Then maxLon = ws.Cells(i, 2).value
End If
Next i
boundsCalculated = "<bounds minlat=""" & Format(minLat, "0.000000000000000") & _
""" minlon=""" & Format(minLon, "0.000000000000000") & _
""" maxlon=""" & Format(maxLon, "0.000000000000000") & _
""" maxlat=""" & Format(maxLat, "0.000000000000000") & """ />"
ws.Range("K2").value = boundsCalculated
' === Zeitstempel für Header ===
TimeZoneOffset = 2 ' UTC+2 für MESZ
currentTime = Format(Now - (TimeZoneOffset / 24), "yyyy-mm-dd\THH:NN:SS") & "Z"
' === Bounds aus Zelle K2 übernehmen und systemabhängig korrigieren ===
boundsText = Replace(ws.Range("K2").value, Application.International(xlDecimalSeparator), ".")
' === Dateiname aus Zelle K5 übernehmen ===
fileName = ws.Range("K5").value
If fileName = "" Then
MsgBox "Kein Dateiname in Zelle K5 gefunden.", vbCritical
Exit Sub
End If
' === Pfad zur GPX-Datei aus Zelle K6 übernehmen ===
originalPath = ws.Range("K6").value
Set fso = CreateObject("Scripting.FileSystemObject")
originalFolder = fso.GetParentFolderName(originalPath)
' === Übergeordnetes Verzeichnis ermitteln ===
On Error Resume Next
parentFolder = fso.GetParentFolderName(originalFolder)
On Error GoTo 0
If parentFolder = "" Or parentFolder = originalFolder Then parentFolder = originalFolder
' === Export#<Datum>-Verzeichnis suchen oder erzeugen ===
exportBaseName = "Export#" & Format(Date, "yyyymmdd")
exportFolder = parentFolder & "\" & exportBaseName
exportFound = False
For Each folderItem In fso.GetFolder(parentFolder).SubFolders
If folderItem.Name = exportBaseName Then
exportFolder = folderItem.Path
exportFound = True
Exit For
End If
Next folderItem
If Not exportFound Then
If Not fso.FolderExists(exportFolder) Then fso.CreateFolder exportFolder
End If
' === Exportpfad festlegen ===
' outputPath = exportFolder & "\" & fileName
' === Exportpfad festlegen mit Appendix "_dated" ===
Dim baseName As String, extName As String
' Dateiname in Basis und Erweiterung zerlegen
baseName = fso.GetBaseName(fileName) ' z.B. CAM_20251119151440_0016_D
extName = fso.GetExtensionName(fileName) ' z.B. gpx
' neuen Namen zusammensetzen
fileName = baseName & "_dated." & extName
outputPath = exportFolder & "\" & fileName
' === GPX-Header aufbauen ===
gpxText = "<?xml version=""1.0"" encoding=""UTF-8""?>" & vbCrLf & _
"<gpx xmlns=""http://www.topografix.com/GPX/1/1"" creator=""Insta360 Studio"" version=""1.1"">" & vbCrLf & _
"<metadata>" & vbCrLf & _
"<link href=""https://www.insta360.com"">" & vbCrLf & _
"<text>Insta360 GPS Dashboard</text>" & vbCrLf & _
"</link>" & vbCrLf & _
"<time>" & currentTime & "</time>" & vbCrLf & _
boundsText & vbCrLf & _
"</metadata>" & vbCrLf & _
"<trk>" & vbCrLf & _
"<name>Insta360 GPS Data</name>" & vbCrLf & _
"<trkseg>" & vbCrLf
' === Trackpunkte erzeugen ===
For i = 2 To lastRow
If ws.Cells(i, 1).value <> "" And ws.Cells(i, 2).value <> "" Then
trkptText = "<trkpt lat=""" & FormatDecimalForGPX(ws.Cells(i, 1).value) & _
""" lon=""" & FormatDecimalForGPX(ws.Cells(i, 2).value) & """>" & vbCrLf
If ws.Cells(i, 3).value <> "" Then
trkptText = trkptText & "<ele>" & FormatDecimalForGPX(ws.Cells(i, 3).value) & "</ele>" & vbCrLf
End If
' Zeitstempel aus Spalte 5 (Date) + Spalte 7 (Microseconds)
If IsDate(ws.Cells(i, 5).value) Then
Dim dtExp As Date, usExp As Long
dtExp = ws.Cells(i, 5).value
usExp = CLng(Val(ws.Cells(i, 7).value))
trkptText = trkptText & "<time>" & FormatDateWithMicroseconds(dtExp, usExp) & "</time>" & vbCrLf
End If
trkptText = trkptText & "</trkpt>" & vbCrLf
gpxText = gpxText & trkptText
End If
Next i
' === GPX-Ende anhängen ===
gpxText = gpxText & "</trkseg>" & vbCrLf & "</trk>" & vbCrLf & "</gpx>"
' === Datei schreiben ===
Dim stream As Object
Set stream = CreateObject("ADODB.Stream")
stream.Type = 2
stream.Charset = "utf-8"
stream.Open
stream.WriteText gpxText
stream.SaveToFile outputPath, 2
stream.Close
Sheets("Start").Select
End Sub
' === Funktion zum automatischen Anpassen des Trennzeichens ===
Function FormatDecimalForGPX(value As Variant) As String
Dim systemSeparator As String
systemSeparator = Application.International(xlDecimalSeparator)
FormatDecimalForGPX = Replace(Format(CDbl(value), "0.000000"), systemSeparator, ".")
End Function
' Zeitstring parsen und Mikrosekunden extrahieren
Public Function ParseIsoTimeToDateAndUs(iso As String, ByRef usOut As Long) As Date
Dim basePart As String, fracPart As String
Dim pDot As Long, pZ As Long
usOut = 0
If Len(iso) = 0 Then Exit Function
pZ = InStr(iso, "Z")
If pZ = 0 Then pZ = Len(iso) + 1
pDot = InStrRev(iso, ".")
If pDot > 0 And pDot < pZ Then
basePart = Left$(iso, pDot - 1)
fracPart = Mid$(iso, pDot + 1, pZ - pDot - 1)
' Nur die ersten 6 Ziffern nehmen (Mikrosekunden)
If Len(fracPart) > 6 Then fracPart = Left$(fracPart, 6)
If IsNumeric(fracPart) Then
usOut = CLng(fracPart)
End If
Else
basePart = Left$(iso, pZ - 1)
End If
ParseIsoTimeToDateAndUs = CDate(Replace(basePart, "T", " "))
End Function
' Sekunden + Mikrosekunden innerhalb eines Tages
Public Function DateUsToSeconds(dt As Date, us As Long) As Double
DateUsToSeconds = (Hour(dt) * 3600# + Minute(dt) * 60# + Second(dt)) + (us / 1000000#)
End Function
Public Sub SecondsToDateUs(baseDate As Date, secVal As Double, ByRef dtOut As Date, ByRef usOut As Long)
Dim wholeSec As Long, frac As Double
Dim h As Long, m As Long, s As Long
' Ganze Sekunden und Bruchteil trennen
wholeSec = Fix(secVal)
frac = secVal - wholeSec
' Zerlegen in Stunden, Minuten, Sekunden
h = wholeSec \ 3600
m = (wholeSec Mod 3600) \ 60
s = wholeSec Mod 60
' Datum + Uhrzeit zusammensetzen
dtOut = DateSerial(Year(baseDate), Month(baseDate), Day(baseDate)) + TimeSerial(h, m, s)
' Mikrosekunden berechnen
usOut = CLng(frac * 1000000#)
If usOut < 0 Then usOut = 0
If usOut > 999999 Then usOut = 999999
End Sub
Public Function FormatDateWithMicroseconds(dt As Date, us As Long) As String
If us < 0 Then us = 0
If us > 999999 Then us = 999999
FormatDateWithMicroseconds = Format(dt, "yyyy-mm-dd\THH:nn:ss") & "." & Format(us, "000000") & "Z"
End Function
Project Status Update – Temporary Halt
Several test drives using the DJI Osmo 360° camera together with a DJI remote connected via Bluetooth have shown that the GPX data quality achieved with this setup still falls short of current technological standards. It must be acknowledged that DJI itself does not position its GPX Remote as a GPX Tracker, but rather that this accessory is intended as a remote control, and its GPX capabilities are intended for generating stats in videos.
Even mounting the DJI remote on the car roof to improve GPS reception did not result in any significant improvement.The 360° camera itself delivers excellent image quality and shows great promise, but to make it viable for productive Street View applications, a different approach to positioning is required. For this reason, I am pausing the current setup.
Update:
My order for an alternative GPX Bluetooth remote from STARTRC has arrived. This remote is not described as compatible with the DJI OSMO 360. STARTRC GPS Bluetooth Remote Controller for DJI Action 5 Pro/ Action 4 - StartRC Initial tests with my OSMO 360 show promising results regarding the GPX tracks generated. This remote can control the OSMO 360, but it doesn’t indicate an ongoing recording with a flashing red light. A successful recording start can be inferred by the remote’s status indicator changing from standby to active.
Extensive GPX recording tests will now follow. Since this GPS remote is also sold in slightly different versions as a third-party alternative for the GoPro and Insta360 ecosystems, I’m hoping the GPS reception quality lives up to the standard those brands have set. As mentioned, the first test rides already look very promising.
Operating the StarTRC GPX Remote (already paired):
Turn on the system:
Insert the DJI Osmo 360 battery and press the power button.
Press and hold the StarTRC power button for three seconds.
Wait for Bluetooth pairing to complete (this takes a few seconds).
Now put the camera into readiness mode. To do this, press and hold the red front button on the StarTRC for several seconds. A moon icon will appear on the remote’s display.
Start recording. Briefly press the front button on the StarTRC.
Stop recording. Briefly press the front button on the StarTRC; the moon icon will reappear.
Start next recording. Briefly press the front button on the StarTRC.
Stop recording. Briefly press the front button on the StarTRC; the moon icon will reappear.
Leave the system in readiness mode for a longer period. The camera consumes very little power in this mode.
After a few minutes, the remote’s display goes blank after a few minutes, and the remote enters standby mode.
Reactivate the camera (for example, if you were away for an hour and return to your car).
Press and hold the StarTRC front button for several seconds. After successful pairing, the remote display will be blank. To obtain a definitive system state, press and hold the front button on the TRC again for several seconds. A moon icon will now reappear on the StarTRC display. (The camera is now in a definitive state for the next recording and is ready for the next recording.)
Turn off the system by pressing and holding the left On button on the StarTRC for several seconds. Press the DJI Osmo Power button and remove the camera battery. (If the StarTRC was turned off using the TRC Power button, remote reactivation of the camera using the TRC remote is no longer possible.) The camera must first be manually turned on to turn the system back on.
Background: The bidirectional communication between the DJI Osmo 360 and the GPX Remote StarTRC does not transmit the current camera status to the remote. The remote can detect the following status states: readiness (moon symbol), paired, and disconnected (for example, due to a depleted camera battery).
Switching between photo and video modes is possible by briefly pressing the remote’s power button, but the lack of a display indicator can be confusing regarding the camera’s current mode. Therefore, I advise against briefly pressing the remote’s power button.
The remote moon symbol is particularly relevant here, as briefly pressing the red remote’s front button in this state directly switches the camera to recording mode.
Osmo 360 battery extension rod replacement
I’ve been running controlled lab tests on the external charging contacts of the DJI Osmo 360 to understand how they behave electrically and what is actually required to power the camera through them.
These four contacts are intended for the official DJI Battery Extension Tube. The rear‑right pin is the negative terminal, and the front‑left pin is the positive terminal. When applying 5 V across these two pins, the camera draws a small current of about 200 mA. At 6 V, the camera begins to charge, and once recording starts, it no longer uses the internal battery. As the voltage increases, the current draw gradually decreases. At 13 V, the current flow stops abruptly and the camera enters a protective safety mode, which resets once the power source is removed.
Based on these measurements, the commonly available magnetic clamp mounts (Search term DJI 1/ “6327001336A”) that claim to power the Osmo 360 via the waterproof contacts do not work. They lack a USB‑C Power Delivery (PD) controller, meaning they cannot negotiate or boost the voltage to the levels the camera expects.
I was unable to initiate a fast charging function for the internal battery via the external contacts; the maximum current flow via the Osmo 360 housing contacts is approximately 1,5 A, which suggests that choosing an external voltage source for the housing contacts with 7,5 volts and 3 A should be sufficient. Example: Amazon.de
There’s also a practical issue: the USB‑C plug attached to this magnetic mount protrudes into the 360° field of view, which breaks the “invisible stick” effect and makes it unsuitable for clean Mapillary or Street View captures.
When an OSMO 360 is placed on this magnetic mount, it wobbles slightly. This problem can be fixed by sticking a thin rubber sticker underneath it.
In summary, this Chinese accessory can currently only be used productively with extensive technical modifications. Such work requires electrical expertise, and you risk voiding the DJI warranty if you don’t know exactly what you’re doing.
Ever since I solved the Osmo 360’s electrical quirks by routing power through its external housing contacts, life on the road has become noticeably calmer. The USB‑C connector is gone from the power line now — a small victory, but one that feels surprisingly liberating. I’ve already taken the camera through several drives on salt‑soaked winter roads, the kind that leave a thin white crust on everything they touch.
Because of that, I now travel with a water bag in the car. It’s a simple ritual: whenever the Osmo 360 comes back looking like it survived a minor ocean storm, I dip it into the bag and wash the salt film off its shell. Strange as it sounds, the moment has become part of the workflow — a quiet pause between stretches of road.
If you look closely at my footage, you might notice an old analog multimeter sitting just in front of the steering wheel. It’s not there for decoration. I use it to watch the camera’s current draw in real time. Every now and then — not often, but often enough to matter — the camera stops recording without warning. The needle is my early‑warning system; the slightest drop tells me instantly that something has gone wrong.
The technical fix behind all this is almost comically simple: a steel spring integrated into the DC power line. That, combined with the multimeter I dug out of my workshop — a relic from another era — gives me exactly the feedback I need. I run it on the 100‑millivolt DC range, and it performs its quiet duty with the kind of reliability only old analog instruments seem to have.
I’ve reached an interesting point in this project — several separate ideas and experiments have finally merged into one coherent, reliable workflow. And at this stage, it’s fair to say that what I’m doing is far removed from the simple approach Mapillary officially promotes: buy a GoPro Max 2, record a raw 360° video, and upload it untouched. Anyone who follows that path accepts a long list of compromises.
My old Insta360 X3 never suffered from random crashes; Insta360 really knows how to build stable recording systems. The GoPro universe, on the other hand, lost me years ago after too many frustrating failures with the Hero 8. And now, as much as I enjoy working with the DJI Osmo 360, it’s clear that this camera isn’t fully mature either.
But that’s where the fun begins. With the help of a 1975 analog multimeter — a relic I rediscovered in my workshop — I’ve solved the issue. It now has a new purpose in life, quietly monitoring the Osmo’s current draw and giving me instant feedback whenever the camera misbehaves. It’s a strangely satisfying blend of old‑school instrumentation and modern 360° mapping technology, and it works far better than I ever expected.
Right now, my M1 MacBook is busy rendering .mp4 files in DJI Studio. It takes its time — and without a PC cooling pad underneath, the machine would probably run hot enough to fry breakfast. At least this setup, along with the entire Mapillary upload workflow, has finally reached a point where everything runs reliably.
DJI Studio itself still feels like early‑stage software when it comes to processing speed. It gets the job done, but you can sense that there’s a lot of untapped potential waiting for future updates.