Use mrcal for camera-calibration (#1036)
Uses jars built from https://github.com/photonvision/mrcal-java/ See: https://mrcal.secretsauce.net/ and https://docs.photonvision.org/en/latest/docs/calibration/calibration.html#investigating-calibration-data-with-mrcal --------- Co-authored-by: Sriman Achanta <68172138+srimanachanta@users.noreply.github.com>
2
.github/workflows/build.yml
vendored
@@ -78,6 +78,8 @@ jobs:
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: temurin
|
||||
- name: Install mrcal deps
|
||||
run: sudo apt-get update && sudo apt-get install -y libcholmod3 liblapack3 libsuitesparseconfig5
|
||||
- name: Gradle Build
|
||||
run: |
|
||||
chmod +x gradlew
|
||||
|
||||
@@ -93,6 +93,14 @@ Our meeting notes can be found in the wiki section of this repository.
|
||||
* [2020 Meeting Notes](https://github.com/PhotonVision/photonvision/wiki/2020-Meeting-Notes)
|
||||
* [2021 Meeting Notes](https://github.com/PhotonVision/photonvision/wiki/2021-Meeting-Notes)
|
||||
|
||||
## Additional packages
|
||||
|
||||
For now, using mrcal requires installing these additional packages on Linux systems:
|
||||
|
||||
```
|
||||
sudo apt install libcholmod3 liblapack3 libsuitesparseconfig5
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- Our main documentation page: [docs.photonvision.org](https://docs.photonvision.org)
|
||||
|
||||
15
build.gradle
@@ -29,18 +29,21 @@ ext {
|
||||
javalinVersion = "5.6.2"
|
||||
photonGlDriverLibVersion = "dev-v2023.1.0-9-g75fc678"
|
||||
frcYear = "2024"
|
||||
mrcalVersion = "dev-v2024.0.0-7-gc976aaa";
|
||||
|
||||
pubVersion = versionString
|
||||
isDev = pubVersion.startsWith("dev")
|
||||
|
||||
// A list, for legacy reasons, with only the current platform contained
|
||||
String nativeName = wpilibTools.platformMapper.currentPlatform.platformName;
|
||||
if (nativeName == "linuxx64") nativeName = "linuxx86-64";
|
||||
if (nativeName == "winx64") nativeName = "windowsx86-64";
|
||||
if (nativeName == "macx64") nativeName = "osxx86-64";
|
||||
if (nativeName == "macarm64") nativeName = "osxarm64";
|
||||
wpilibNativeName = wpilibTools.platformMapper.currentPlatform.platformName;
|
||||
def nativeName = wpilibNativeName
|
||||
if (wpilibNativeName == "linuxx64") nativeName = "linuxx86-64";
|
||||
if (wpilibNativeName == "winx64") nativeName = "windowsx86-64";
|
||||
if (wpilibNativeName == "macx64") nativeName = "osxx86-64";
|
||||
if (wpilibNativeName == "macarm64") nativeName = "osxarm64";
|
||||
jniPlatform = nativeName
|
||||
println("Building for platform: " + jniPlatform)
|
||||
|
||||
println("Building for platform " + jniPlatform + " wpilib: " + wpilibNativeName)
|
||||
println("Using Wpilib: " + wpilibVersion)
|
||||
println("Using OpenCV: " + openCVversion)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import argparse
|
||||
import base64
|
||||
from dataclasses import dataclass
|
||||
import json
|
||||
import os
|
||||
from typing import Union
|
||||
import cv2
|
||||
import numpy as np
|
||||
import mrcal
|
||||
from wpimath.geometry import Quaternion as _Quat
|
||||
|
||||
|
||||
@dataclass
|
||||
class Resolution:
|
||||
class Size:
|
||||
width: int
|
||||
height: int
|
||||
|
||||
@@ -86,10 +90,98 @@ class Observation:
|
||||
|
||||
@dataclass
|
||||
class CameraCalibration:
|
||||
resolution: Resolution
|
||||
resolution: Size
|
||||
cameraIntrinsics: JsonMatOfDoubles
|
||||
distCoeffs: JsonMatOfDoubles
|
||||
observations: list[Observation]
|
||||
calobjectWarp: list[float]
|
||||
calobjectSize: Size
|
||||
calobjectSpacing: float
|
||||
|
||||
|
||||
def __convert_cal_to_mrcal_cameramodel(
|
||||
cal: CameraCalibration,
|
||||
) -> mrcal.cameramodel | None:
|
||||
if len(cal.distCoeffs.data) == 5:
|
||||
model = "LENSMODEL_OPENCV5"
|
||||
elif len(cal.distCoeffs.data) == 8:
|
||||
model = "LENSMODEL_OPENCV8"
|
||||
else:
|
||||
print("Unknown camera model? giving up")
|
||||
return None
|
||||
|
||||
def opencv_to_mrcal_intrinsics(ocv):
|
||||
return [ocv[0], ocv[4], ocv[2], ocv[5]]
|
||||
|
||||
def pose_to_rt(pose: Pose3d):
|
||||
r = _Quat(
|
||||
w=pose.rotation.quaternion.W,
|
||||
x=pose.rotation.quaternion.X,
|
||||
y=pose.rotation.quaternion.Y,
|
||||
z=pose.rotation.quaternion.Z,
|
||||
).toRotationVector()
|
||||
t = [
|
||||
pose.translation.x,
|
||||
pose.translation.y,
|
||||
pose.translation.z,
|
||||
]
|
||||
return np.concatenate((r, t))
|
||||
|
||||
imagersize = (cal.resolution.width, cal.resolution.height)
|
||||
|
||||
# Always weight=1 for Photon data
|
||||
WEIGHT = 1
|
||||
observations_board = np.array(
|
||||
[
|
||||
# note that we expect row-major observations here. I think this holds
|
||||
np.array(
|
||||
list(map(lambda it: [it.x, it.y, WEIGHT], o.locationInImageSpace))
|
||||
).reshape((cal.calobjectSize.width, cal.calobjectSize.height, 3))
|
||||
for o in cal.observations
|
||||
]
|
||||
)
|
||||
|
||||
optimization_inputs = {
|
||||
"intrinsics": np.array(
|
||||
[
|
||||
opencv_to_mrcal_intrinsics(cal.cameraIntrinsics.data)
|
||||
+ cal.distCoeffs.data
|
||||
],
|
||||
dtype=np.float64,
|
||||
),
|
||||
"extrinsics_rt_fromref": np.zeros((0, 6), dtype=np.float64),
|
||||
"frames_rt_toref": np.array(
|
||||
[pose_to_rt(o.optimisedCameraToObject) for o in cal.observations]
|
||||
),
|
||||
"points": None,
|
||||
"observations_board": observations_board,
|
||||
"indices_frame_camintrinsics_camextrinsics": np.array(
|
||||
[[i, 0, -1] for i in range(len(cal.observations))], dtype=np.int32
|
||||
),
|
||||
"observations_point": None,
|
||||
"indices_point_camintrinsics_camextrinsics": None,
|
||||
"lensmodel": model,
|
||||
"imagersizes": np.array([imagersize], dtype=np.int32),
|
||||
"calobject_warp": np.array(cal.calobjectWarp)
|
||||
if len(cal.calobjectWarp) > 0
|
||||
else None,
|
||||
# We always do all the things
|
||||
"do_optimize_intrinsics_core": True,
|
||||
"do_optimize_intrinsics_distortions": True,
|
||||
"do_optimize_extrinsics": True,
|
||||
"do_optimize_frames": True,
|
||||
"do_optimize_calobject_warp": len(cal.calobjectWarp) > 0,
|
||||
"do_apply_outlier_rejection": True,
|
||||
"do_apply_regularization": True,
|
||||
"verbose": False,
|
||||
"calibration_object_spacing": cal.calobjectSpacing,
|
||||
"imagepaths": np.array([it.snapshotName for it in cal.observations]),
|
||||
}
|
||||
|
||||
return mrcal.cameramodel(
|
||||
optimization_inputs=optimization_inputs,
|
||||
icam_intrinsics=0,
|
||||
)
|
||||
|
||||
|
||||
def convert_photon_to_mrcal(photon_cal_json_path: str, output_folder: str):
|
||||
@@ -132,3 +224,32 @@ def convert_photon_to_mrcal(photon_cal_json_path: str, output_folder: str):
|
||||
vnl_file.write(f"{obs.snapshotName} {corner.x} {corner.y} 0\n")
|
||||
|
||||
vnl_file.flush()
|
||||
|
||||
mrcal_model = __convert_cal_to_mrcal_cameramodel(camera_cal_data)
|
||||
|
||||
with open(f"{output_folder}/camera-0.cameramodel", "w+") as mrcal_file:
|
||||
mrcal_model.write(
|
||||
mrcal_file,
|
||||
note="Generated from PhotonVision calibration file: "
|
||||
+ photon_cal_json_path
|
||||
+ "\nCalobject_warp (m): "
|
||||
+ str(camera_cal_data.calobjectWarp),
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Convert Photon calibration JSON for use with mrcal"
|
||||
)
|
||||
parser.add_argument("input", type=str, help="Path to Photon calibration JSON file")
|
||||
parser.add_argument(
|
||||
"output_folder", type=str, help="Output folder for mrcal VNL file + images"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
convert_photon_to_mrcal(args.input, args.output_folder)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -13,6 +13,7 @@ import PvNumberInput from "@/components/common/pv-number-input.vue";
|
||||
import { WebsocketPipelineType } from "@/types/WebsocketDataTypes";
|
||||
import { getResolutionString, resolutionsAreEqual } from "@/lib/PhotonUtils";
|
||||
import CameraCalibrationInfoCard from "@/components/cameras/CameraCalibrationInfoCard.vue";
|
||||
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
|
||||
|
||||
const settingsValid = ref(true);
|
||||
|
||||
@@ -74,6 +75,15 @@ const squareSizeIn = ref(1);
|
||||
const patternWidth = ref(8);
|
||||
const patternHeight = ref(8);
|
||||
const boardType = ref<CalibrationBoardTypes>(CalibrationBoardTypes.Chessboard);
|
||||
const useMrCalRef = ref(true);
|
||||
const useMrCal = computed<boolean>({
|
||||
get() {
|
||||
return useMrCalRef.value && useSettingsStore().general.mrCalWorking;
|
||||
},
|
||||
set(value) {
|
||||
useMrCalRef.value = value && useSettingsStore().general.mrCalWorking;
|
||||
}
|
||||
});
|
||||
|
||||
const downloadCalibBoard = () => {
|
||||
const doc = new JsPDF({ unit: "in", format: "letter" });
|
||||
@@ -188,7 +198,8 @@ const startCalibration = () => {
|
||||
squareSizeIn: squareSizeIn.value,
|
||||
patternHeight: patternHeight.value,
|
||||
patternWidth: patternWidth.value,
|
||||
boardType: boardType.value
|
||||
boardType: boardType.value,
|
||||
useMrCal: useMrCal.value
|
||||
});
|
||||
// The Start PnP method already handles updating the backend so only a store update is required
|
||||
useCameraSettingsStore().currentCameraSettings.currentPipelineIndex = WebsocketPipelineType.Calib3d;
|
||||
@@ -314,6 +325,23 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
|
||||
:rules="[(v) => v >= 4 || 'Height must be at least 4']"
|
||||
:label-cols="5"
|
||||
/>
|
||||
<pv-switch
|
||||
v-model="useMrCal"
|
||||
label="Try using MrCal over OpenCV"
|
||||
:disabled="!useSettingsStore().general.mrCalWorking || isCalibrating"
|
||||
tooltip="If enabled, Photon will (try to) use MrCal instead of OpenCV for camera calibration."
|
||||
:label-cols="5"
|
||||
/>
|
||||
<v-banner
|
||||
v-show="!useSettingsStore().general.mrCalWorking"
|
||||
rounded
|
||||
color="red"
|
||||
text-color="white"
|
||||
class="mt-3"
|
||||
icon="mdi-alert-circle-outline"
|
||||
>
|
||||
MrCal JNI could not be loaded! Consult journalctl logs for additional details.
|
||||
</v-banner>
|
||||
</v-form>
|
||||
<v-row justify="center">
|
||||
<v-chip
|
||||
|
||||
@@ -241,6 +241,22 @@ const getObservationDetails = (): ObservationDetails[] | undefined => {
|
||||
<td>Diagonal FOV</td>
|
||||
<td>{{ videoFormat.diagonalFOV !== undefined ? videoFormat.diagonalFOV.toFixed(2) + "°" : "-" }}</td>
|
||||
</tr>
|
||||
<!-- Board warp, only shown for mrcal-calibrated cameras -->
|
||||
<tr
|
||||
v-if="
|
||||
useCameraSettingsStore().getCalibrationCoeffs(props.videoFormat.resolution)?.calobjectWarp?.length === 2
|
||||
"
|
||||
>
|
||||
<td>Board warp, X/Y</td>
|
||||
<td>
|
||||
{{
|
||||
useCameraSettingsStore()
|
||||
.getCalibrationCoeffs(props.videoFormat.resolution)
|
||||
?.calobjectWarp?.map((it) => (it * 1000).toFixed(2) + " mm")
|
||||
.join(" / ")
|
||||
}}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</template>
|
||||
</v-simple-table>
|
||||
|
||||
@@ -317,6 +317,7 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
|
||||
patternWidth: number;
|
||||
patternHeight: number;
|
||||
boardType: CalibrationBoardTypes;
|
||||
useMrCal: boolean;
|
||||
},
|
||||
cameraIndex: number = useStateStore().currentCameraIndex
|
||||
) {
|
||||
|
||||
@@ -26,7 +26,8 @@ export const useSettingsStore = defineStore("settings", {
|
||||
version: undefined,
|
||||
gpuAcceleration: undefined,
|
||||
hardwareModel: undefined,
|
||||
hardwarePlatform: undefined
|
||||
hardwarePlatform: undefined,
|
||||
mrCalWorking: true
|
||||
},
|
||||
network: {
|
||||
ntServerAddress: "",
|
||||
@@ -97,7 +98,8 @@ export const useSettingsStore = defineStore("settings", {
|
||||
version: data.general.version || undefined,
|
||||
hardwareModel: data.general.hardwareModel || undefined,
|
||||
hardwarePlatform: data.general.hardwarePlatform || undefined,
|
||||
gpuAcceleration: data.general.gpuAcceleration || undefined
|
||||
gpuAcceleration: data.general.gpuAcceleration || undefined,
|
||||
mrCalWorking: data.general.mrCalWorking
|
||||
};
|
||||
this.lighting = data.lighting;
|
||||
this.network = data.networkSettings;
|
||||
|
||||
@@ -6,6 +6,7 @@ export interface GeneralSettings {
|
||||
gpuAcceleration?: string;
|
||||
hardwareModel?: string;
|
||||
hardwarePlatform?: string;
|
||||
mrCalWorking: boolean;
|
||||
}
|
||||
|
||||
export interface MetricData {
|
||||
@@ -131,6 +132,7 @@ export interface CameraCalibrationResult {
|
||||
cameraIntrinsics: JsonMatOfDouble;
|
||||
distCoeffs: JsonMatOfDouble;
|
||||
observations: BoardObservation[];
|
||||
calobjectWarp?: number[];
|
||||
}
|
||||
|
||||
export interface ConfigurableCameraSettings {
|
||||
|
||||
@@ -16,6 +16,19 @@ dependencies {
|
||||
|
||||
implementation "org.photonvision:photon-libcamera-gl-driver-jni:$photonGlDriverLibVersion:linuxarm64"
|
||||
implementation "org.photonvision:photon-libcamera-gl-driver-java:$photonGlDriverLibVersion"
|
||||
|
||||
implementation "org.photonvision:photon-mrcal-java:$mrcalVersion"
|
||||
|
||||
// Only include mrcal natives on platforms that we build for
|
||||
if (!(jniPlatform in [
|
||||
"osxx86-64",
|
||||
"osxarm64",
|
||||
"linuxarm32"
|
||||
])) {
|
||||
implementation "org.photonvision:photon-mrcal-jni:$mrcalVersion:$wpilibNativeName"
|
||||
}
|
||||
|
||||
testImplementation group: 'org.junit-pioneer' , name: 'junit-pioneer', version: '2.2.0'
|
||||
}
|
||||
|
||||
task writeCurrentVersion {
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.photonvision.PhotonVersion;
|
||||
import org.photonvision.common.hardware.Platform;
|
||||
import org.photonvision.common.networking.NetworkUtils;
|
||||
import org.photonvision.common.util.SerializationUtils;
|
||||
import org.photonvision.mrcal.MrCalJNILoader;
|
||||
import org.photonvision.raspi.LibCameraJNILoader;
|
||||
import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
|
||||
import org.photonvision.vision.processes.VisionModule;
|
||||
@@ -140,6 +141,7 @@ public class PhotonConfiguration {
|
||||
LibCameraJNILoader.isSupported()
|
||||
? "Zerocopy Libcamera Working"
|
||||
: ""); // TODO add support for other types of GPU accel
|
||||
generalSubmap.put("mrCalWorking", MrCalJNILoader.isWorking());
|
||||
generalSubmap.put("hardwareModel", hardwareConfig.deviceName);
|
||||
generalSubmap.put("hardwarePlatform", Platform.getPlatformName());
|
||||
settingsSubmap.put("general", generalSubmap);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package org.photonvision.common.hardware;
|
||||
|
||||
import com.jogamp.common.os.Platform.OSType;
|
||||
import edu.wpi.first.util.RuntimeDetector;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
@@ -27,23 +28,32 @@ import org.photonvision.common.util.ShellExec;
|
||||
@SuppressWarnings("unused")
|
||||
public enum Platform {
|
||||
// WPILib Supported (JNI)
|
||||
WINDOWS_64("Windows x64", false, OSType.WINDOWS, true),
|
||||
LINUX_32("Linux x86", false, OSType.LINUX, true),
|
||||
LINUX_64("Linux x64", false, OSType.LINUX, true),
|
||||
WINDOWS_64("Windows x64", "winx64", false, OSType.WINDOWS, true),
|
||||
LINUX_32("Linux x86", "linuxx64", false, OSType.LINUX, true),
|
||||
LINUX_64("Linux x64", "linuxx64", false, OSType.LINUX, true),
|
||||
LINUX_RASPBIAN32(
|
||||
"Linux Raspbian 32-bit", true, OSType.LINUX, true), // Raspberry Pi 3/4 with a 32-bit image
|
||||
"Linux Raspbian 32-bit",
|
||||
"linuxarm32",
|
||||
true,
|
||||
OSType.LINUX,
|
||||
true), // Raspberry Pi 3/4 with a 32-bit image
|
||||
LINUX_RASPBIAN64(
|
||||
"Linux Raspbian 64-bit", true, OSType.LINUX, true), // Raspberry Pi 3/4 with a 64-bit image
|
||||
LINUX_AARCH64("Linux AARCH64", false, OSType.LINUX, true), // Jetson Nano, Jetson TX2
|
||||
"Linux Raspbian 64-bit",
|
||||
"linuxarm64",
|
||||
true,
|
||||
OSType.LINUX,
|
||||
true), // Raspberry Pi 3/4 with a 64-bit image
|
||||
LINUX_AARCH64(
|
||||
"Linux AARCH64", "linuxarm64", false, OSType.LINUX, true), // Jetson Nano, Jetson TX2
|
||||
|
||||
// PhotonVision Supported (Manual build/install)
|
||||
LINUX_ARM32("Linux ARM32", false, OSType.LINUX, true), // ODROID XU4, C1+
|
||||
LINUX_ARM64("Linux ARM64", false, OSType.LINUX, true), // ODROID C2, N2
|
||||
LINUX_ARM32("Linux ARM32", "linuxarm32", false, OSType.LINUX, true), // ODROID XU4, C1+
|
||||
LINUX_ARM64("Linux ARM64", "linuxarm64", false, OSType.LINUX, true), // ODROID C2, N2
|
||||
|
||||
// Completely unsupported
|
||||
WINDOWS_32("Windows x86", false, OSType.WINDOWS, false),
|
||||
MACOS("Mac OS", false, OSType.MACOS, false),
|
||||
UNKNOWN("Unsupported Platform", false, OSType.UNKNOWN, false);
|
||||
WINDOWS_32("Windows x86", "windowsx64", false, OSType.WINDOWS, false),
|
||||
MACOS("Mac OS", "osxuniversal", false, OSType.MACOS, false),
|
||||
UNKNOWN("Unsupported Platform", "", false, OSType.UNKNOWN, false);
|
||||
|
||||
private enum OSType {
|
||||
WINDOWS,
|
||||
@@ -54,6 +64,7 @@ public enum Platform {
|
||||
|
||||
private static final ShellExec shell = new ShellExec(true, false);
|
||||
public final String description;
|
||||
public final String nativeLibraryFolderName;
|
||||
public final boolean isPi;
|
||||
public final OSType osType;
|
||||
public final boolean isSupported;
|
||||
@@ -62,11 +73,17 @@ public enum Platform {
|
||||
private static final Platform currentPlatform = getCurrentPlatform();
|
||||
private static final boolean isRoot = checkForRoot();
|
||||
|
||||
Platform(String description, boolean isPi, OSType osType, boolean isSupported) {
|
||||
Platform(
|
||||
String description,
|
||||
String nativeLibFolderName,
|
||||
boolean isPi,
|
||||
OSType osType,
|
||||
boolean isSupported) {
|
||||
this.description = description;
|
||||
this.isPi = isPi;
|
||||
this.osType = osType;
|
||||
this.isSupported = isSupported;
|
||||
this.nativeLibraryFolderName = nativeLibFolderName;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
@@ -89,6 +106,10 @@ public enum Platform {
|
||||
}
|
||||
}
|
||||
|
||||
public static String getNativeLibraryFolderName() {
|
||||
return currentPlatform.nativeLibraryFolderName;
|
||||
}
|
||||
|
||||
public static boolean isRoot() {
|
||||
return isRoot;
|
||||
}
|
||||
@@ -214,4 +235,9 @@ public enum Platform {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isWindows() {
|
||||
var p = getCurrentPlatform();
|
||||
return (p == WINDOWS_32 || p == WINDOWS_64);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,11 @@ import org.opencv.highgui.HighGui;
|
||||
import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
|
||||
|
||||
public class TestUtils {
|
||||
private static boolean has_loaded = false;
|
||||
|
||||
public static boolean loadLibraries() {
|
||||
if (has_loaded) return true;
|
||||
|
||||
NetworkTablesJNI.Helper.setExtractOnStaticLoad(false);
|
||||
WPIUtilJNI.Helper.setExtractOnStaticLoad(false);
|
||||
WPIMathJNI.Helper.setExtractOnStaticLoad(false);
|
||||
@@ -61,11 +65,13 @@ public class TestUtils {
|
||||
"cscorejni",
|
||||
"apriltagjni");
|
||||
|
||||
return true;
|
||||
has_loaded = true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
has_loaded = false;
|
||||
}
|
||||
|
||||
return has_loaded;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (C) Photon Vision.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.jni;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import org.photonvision.common.hardware.Platform;
|
||||
import org.photonvision.common.logging.LogGroup;
|
||||
import org.photonvision.common.logging.Logger;
|
||||
|
||||
public abstract class PhotonJNICommon {
|
||||
static boolean libraryLoaded = false;
|
||||
protected static Logger logger = null;
|
||||
|
||||
protected static synchronized void forceLoad(Class<?> clazz, List<String> libraries)
|
||||
throws IOException {
|
||||
if (libraryLoaded) return;
|
||||
if (logger == null) logger = new Logger(clazz, LogGroup.Camera);
|
||||
|
||||
for (var libraryName : libraries) {
|
||||
try {
|
||||
// We always extract the shared object (we could hash each so, but that's a lot of work)
|
||||
var arch_name = Platform.getNativeLibraryFolderName();
|
||||
var nativeLibName = System.mapLibraryName(libraryName);
|
||||
var in = clazz.getResourceAsStream("/nativelibraries/" + arch_name + "/" + nativeLibName);
|
||||
|
||||
if (in == null) {
|
||||
libraryLoaded = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// It's important that we don't mangle the names of these files on Windows at least
|
||||
File temp = new File(System.getProperty("java.io.tmpdir"), nativeLibName);
|
||||
FileOutputStream fos = new FileOutputStream(temp);
|
||||
|
||||
int read = -1;
|
||||
byte[] buffer = new byte[1024];
|
||||
while ((read = in.read(buffer)) != -1) {
|
||||
fos.write(buffer, 0, read);
|
||||
}
|
||||
fos.close();
|
||||
in.close();
|
||||
|
||||
System.load(temp.getAbsolutePath());
|
||||
|
||||
logger.info("Successfully loaded shared object " + temp.getName());
|
||||
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
logger.error("Couldn't load shared object " + libraryName, e);
|
||||
e.printStackTrace();
|
||||
// logger.error(System.getProperty("java.library.path"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
libraryLoaded = true;
|
||||
}
|
||||
|
||||
protected static synchronized void forceLoad(Class<?> clazz, String libraryName)
|
||||
throws IOException {
|
||||
forceLoad(clazz, List.of(libraryName));
|
||||
}
|
||||
|
||||
public static boolean isWorking() {
|
||||
return libraryLoaded;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (C) Photon Vision.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.mrcal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import org.photonvision.common.hardware.Platform;
|
||||
import org.photonvision.common.util.TestUtils;
|
||||
import org.photonvision.jni.PhotonJNICommon;
|
||||
|
||||
public class MrCalJNILoader extends PhotonJNICommon {
|
||||
public static synchronized void forceLoad() throws IOException {
|
||||
// Force load opencv
|
||||
TestUtils.loadLibraries();
|
||||
|
||||
// Library naming is dumb and has "lib" appended for Windows when it ought not to
|
||||
if (Platform.isWindows()) {
|
||||
// Order is correct to match dependencies of libraries
|
||||
forceLoad(
|
||||
MrCalJNILoader.class,
|
||||
List.of(
|
||||
"libamd",
|
||||
"libcamd",
|
||||
"libcolamd",
|
||||
"libccolamd",
|
||||
"openblas",
|
||||
"libgcc_s_seh-1",
|
||||
"libgfortran-5",
|
||||
"liblapack",
|
||||
"libcholmod",
|
||||
"mrcal_jni"));
|
||||
} else {
|
||||
// Nothing else to do on linux
|
||||
forceLoad(MrCalJNILoader.class, List.of("mrcal_jni"));
|
||||
}
|
||||
|
||||
if (!MrCalJNILoader.isWorking()) {
|
||||
throw new IOException("Unable to load mrcal JNI!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,10 @@ import java.io.IOException;
|
||||
import org.photonvision.common.logging.LogGroup;
|
||||
import org.photonvision.common.logging.Logger;
|
||||
|
||||
/**
|
||||
* Helper for extracting photon-libcamera-gl-driver shared library files. TODO: Refactor to use
|
||||
* PhotonJNICommon
|
||||
*/
|
||||
public class LibCameraJNILoader {
|
||||
private static boolean libraryLoaded = false;
|
||||
private static final Logger logger = new Logger(LibCameraJNILoader.class, LogGroup.Camera);
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfDouble;
|
||||
@@ -47,6 +48,12 @@ public class CameraCalibrationCoefficients implements Releasable {
|
||||
@JsonProperty("calobjectWarp")
|
||||
public final double[] calobjectWarp;
|
||||
|
||||
@JsonProperty("calobjectSize")
|
||||
public final Size calobjectSize;
|
||||
|
||||
@JsonProperty("calobjectSpacing")
|
||||
public final double calobjectSpacing;
|
||||
|
||||
@JsonIgnore private final double[] intrinsicsArr = new double[9];
|
||||
@JsonIgnore private final double[] distCoeffsArr = new double[5];
|
||||
|
||||
@@ -64,6 +71,9 @@ public class CameraCalibrationCoefficients implements Releasable {
|
||||
* @param calobjectWarp Board deformation parameters, for calibrators that can estimate that. See:
|
||||
* https://mrcal.secretsauce.net/formulation.html#board-deformation
|
||||
* @param observations List of snapshots used to construct this calibration
|
||||
* @param calobjectSize Dimensions of the object used to calibrate, in # of squares in
|
||||
* width/height
|
||||
* @param calobjectSpacing Spacing between adjacent squares, in meters
|
||||
*/
|
||||
@JsonCreator
|
||||
public CameraCalibrationCoefficients(
|
||||
@@ -71,11 +81,15 @@ public class CameraCalibrationCoefficients implements Releasable {
|
||||
@JsonProperty("cameraIntrinsics") JsonMatOfDouble cameraIntrinsics,
|
||||
@JsonProperty("distCoeffs") JsonMatOfDouble distCoeffs,
|
||||
@JsonProperty("calobjectWarp") double[] calobjectWarp,
|
||||
@JsonProperty("observations") List<BoardObservation> observations) {
|
||||
@JsonProperty("observations") List<BoardObservation> observations,
|
||||
@JsonProperty("calobjectSize") Size calobjectSize,
|
||||
@JsonProperty("calobjectSpacing") double calobjectSpacing) {
|
||||
this.resolution = resolution;
|
||||
this.cameraIntrinsics = cameraIntrinsics;
|
||||
this.distCoeffs = distCoeffs;
|
||||
this.calobjectWarp = calobjectWarp;
|
||||
this.calobjectSize = calobjectSize;
|
||||
this.calobjectSpacing = calobjectSpacing;
|
||||
|
||||
// Legacy migration just to make sure that observations is at worst empty and never null
|
||||
if (observations == null) {
|
||||
@@ -150,11 +164,35 @@ public class CameraCalibrationCoefficients implements Releasable {
|
||||
var cam_jsonmat = new JsonMatOfDouble(3, 3, cam_arr);
|
||||
var distortion_jsonmat = new JsonMatOfDouble(1, 5, dist_array);
|
||||
|
||||
var error = json.get("avg_reprojection_error").asDouble();
|
||||
var width = json.get("img_size").get(0).doubleValue();
|
||||
var height = json.get("img_size").get(1).doubleValue();
|
||||
|
||||
return new CameraCalibrationCoefficients(
|
||||
new Size(width, height), cam_jsonmat, distortion_jsonmat, new double[0], List.of());
|
||||
new Size(width, height),
|
||||
cam_jsonmat,
|
||||
distortion_jsonmat,
|
||||
new double[0],
|
||||
List.of(),
|
||||
new Size(0, 0),
|
||||
0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CameraCalibrationCoefficients [resolution="
|
||||
+ resolution
|
||||
+ ", cameraIntrinsics="
|
||||
+ cameraIntrinsics
|
||||
+ ", distCoeffs="
|
||||
+ distCoeffs
|
||||
+ ", observations="
|
||||
+ observations
|
||||
+ ", calobjectWarp="
|
||||
+ Arrays.toString(calobjectWarp)
|
||||
+ ", intrinsicsArr="
|
||||
+ Arrays.toString(intrinsicsArr)
|
||||
+ ", distCoeffsArr="
|
||||
+ Arrays.toString(distCoeffsArr)
|
||||
+ "]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,4 +127,23 @@ public class JsonMatOfDouble implements Releasable {
|
||||
packet.encode(this.data);
|
||||
return packet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JsonMat [rows="
|
||||
+ rows
|
||||
+ ", cols="
|
||||
+ cols
|
||||
+ ", type="
|
||||
+ type
|
||||
+ ", data="
|
||||
+ Arrays.toString(data)
|
||||
+ ", wrappedMat="
|
||||
+ wrappedMat
|
||||
+ ", wpilibMat="
|
||||
+ wpilibMat
|
||||
+ ", wrappedMatOfDouble="
|
||||
+ wrappedMatOfDouble
|
||||
+ "]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,19 +17,22 @@
|
||||
|
||||
package org.photonvision.vision.pipe.impl;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import org.opencv.calib3d.Calib3d;
|
||||
import org.opencv.core.*;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfDouble;
|
||||
import org.opencv.core.MatOfPoint2f;
|
||||
import org.opencv.core.Size;
|
||||
import org.photonvision.common.logging.LogGroup;
|
||||
import org.photonvision.common.logging.Logger;
|
||||
import org.photonvision.common.util.math.MathUtils;
|
||||
import org.photonvision.mrcal.MrCalJNI;
|
||||
import org.photonvision.mrcal.MrCalJNI.MrCalResult;
|
||||
import org.photonvision.mrcal.MrCalJNILoader;
|
||||
import org.photonvision.vision.calibration.BoardObservation;
|
||||
import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
|
||||
import org.photonvision.vision.calibration.JsonImageMat;
|
||||
@@ -41,19 +44,9 @@ public class Calibrate3dPipe
|
||||
List<FindBoardCornersPipe.FindBoardCornersPipeResult>,
|
||||
CameraCalibrationCoefficients,
|
||||
Calibrate3dPipe.CalibratePipeParams> {
|
||||
// Camera matrix stores the center of the image and focal length across the x and y-axis in a 3x3
|
||||
// matrix
|
||||
private final Mat cameraMatrix = new Mat();
|
||||
// Stores the radical and tangential distortion in a 5x1 matrix
|
||||
private final MatOfDouble distortionCoefficients = new MatOfDouble();
|
||||
|
||||
// For logging
|
||||
private static final Logger logger = new Logger(Calibrate3dPipe.class, LogGroup.General);
|
||||
|
||||
// Translational and rotational matrices
|
||||
private final List<Mat> rvecs = new ArrayList<>();
|
||||
private final List<Mat> tvecs = new ArrayList<>();
|
||||
|
||||
// The Standard deviation of the estimated parameters
|
||||
private final Mat stdDeviationsIntrinsics = new Mat();
|
||||
private final Mat stdDeviationsExtrinsics = new Mat();
|
||||
@@ -62,9 +55,6 @@ public class Calibrate3dPipe
|
||||
// finding the Euclidean distance between the actual corners.
|
||||
private final Mat perViewErrors = new Mat();
|
||||
|
||||
// RMS of the calibration
|
||||
private double calibrationAccuracy;
|
||||
|
||||
/**
|
||||
* Runs the process for the pipe.
|
||||
*
|
||||
@@ -84,6 +74,35 @@ public class Calibrate3dPipe
|
||||
&& it.size != null)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
CameraCalibrationCoefficients ret;
|
||||
var start = System.nanoTime();
|
||||
if (MrCalJNILoader.isWorking() && params.useMrCal) {
|
||||
logger.debug("Calibrating with mrcal!");
|
||||
ret = calibrateMrcal(in);
|
||||
} else {
|
||||
logger.debug("Calibrating with opencv!");
|
||||
ret = calibrateOpenCV(in);
|
||||
}
|
||||
var dt = System.nanoTime() - start;
|
||||
|
||||
if (ret != null)
|
||||
logger.info(
|
||||
"CALIBRATION SUCCESS for res "
|
||||
+ in.get(0).size
|
||||
+ " in "
|
||||
+ dt / 1e6
|
||||
+ "ms! camMatrix: \n"
|
||||
+ Arrays.toString(ret.cameraIntrinsics.data)
|
||||
+ "\ndistortionCoeffs:\n"
|
||||
+ Arrays.toString(ret.distCoeffs.data)
|
||||
+ "\n");
|
||||
else logger.info("Calibration failed! Review log for more details");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected CameraCalibrationCoefficients calibrateOpenCV(
|
||||
List<FindBoardCornersPipe.FindBoardCornersPipeResult> in) {
|
||||
List<Mat> objPoints = in.stream().map(it -> it.objectPoints).collect(Collectors.toList());
|
||||
List<Mat> imgPts = in.stream().map(it -> it.imagePoints).collect(Collectors.toList());
|
||||
if (objPoints.size() != imgPts.size()) {
|
||||
@@ -91,6 +110,14 @@ public class Calibrate3dPipe
|
||||
return null;
|
||||
}
|
||||
|
||||
Mat cameraMatrix = new Mat();
|
||||
MatOfDouble distortionCoefficients = new MatOfDouble();
|
||||
List<Mat> rvecs = new ArrayList<>();
|
||||
List<Mat> tvecs = new ArrayList<>();
|
||||
|
||||
// RMS of the calibration
|
||||
double calibrationAccuracy;
|
||||
|
||||
try {
|
||||
// FindBoardCorners pipe outputs all the image points, object points, and frames to calculate
|
||||
// imageSize from, other parameters are output Mats
|
||||
@@ -116,6 +143,116 @@ public class Calibrate3dPipe
|
||||
JsonMatOfDouble cameraMatrixMat = JsonMatOfDouble.fromMat(cameraMatrix);
|
||||
JsonMatOfDouble distortionCoefficientsMat = JsonMatOfDouble.fromMat(distortionCoefficients);
|
||||
|
||||
var observations =
|
||||
createObservations(in, cameraMatrix, distortionCoefficients, rvecs, tvecs, null);
|
||||
|
||||
cameraMatrix.release();
|
||||
distortionCoefficients.release();
|
||||
rvecs.forEach(Mat::release);
|
||||
tvecs.forEach(Mat::release);
|
||||
|
||||
return new CameraCalibrationCoefficients(
|
||||
in.get(0).size,
|
||||
cameraMatrixMat,
|
||||
distortionCoefficientsMat,
|
||||
new double[0],
|
||||
observations,
|
||||
new Size(params.boardWidth, params.boardHeight),
|
||||
params.squareSize);
|
||||
}
|
||||
|
||||
protected CameraCalibrationCoefficients calibrateMrcal(
|
||||
List<FindBoardCornersPipe.FindBoardCornersPipeResult> in) {
|
||||
List<MatOfPoint2f> corner_locations =
|
||||
in.stream().map(it -> it.imagePoints).map(MatOfPoint2f::new).collect(Collectors.toList());
|
||||
|
||||
int imageWidth = (int) in.get(0).size.width;
|
||||
int imageHeight = (int) in.get(0).size.height;
|
||||
final double FOCAL_LENGTH_GUESS = 1200;
|
||||
|
||||
MrCalResult result =
|
||||
MrCalJNI.calibrateCamera(
|
||||
corner_locations,
|
||||
params.boardWidth,
|
||||
params.boardHeight,
|
||||
params.squareSize,
|
||||
imageWidth,
|
||||
imageHeight,
|
||||
FOCAL_LENGTH_GUESS);
|
||||
|
||||
// intrinsics are fx fy cx cy from mrcal
|
||||
JsonMatOfDouble cameraMatrixMat =
|
||||
new JsonMatOfDouble(
|
||||
3,
|
||||
3,
|
||||
CvType.CV_64FC1,
|
||||
new double[] {
|
||||
// fx 0 cx
|
||||
result.intrinsics[0],
|
||||
0,
|
||||
result.intrinsics[2],
|
||||
// 0 fy cy
|
||||
0,
|
||||
result.intrinsics[1],
|
||||
result.intrinsics[3],
|
||||
// 0 0 1
|
||||
0,
|
||||
0,
|
||||
1
|
||||
});
|
||||
JsonMatOfDouble distortionCoefficientsMat =
|
||||
new JsonMatOfDouble(1, 8, CvType.CV_64FC1, Arrays.copyOfRange(result.intrinsics, 4, 12));
|
||||
|
||||
// Calculate optimized board poses manually. We get this for free from mrcal too, but that's not
|
||||
// JNIed (yet)
|
||||
List<Mat> rvecs = new ArrayList<>();
|
||||
List<Mat> tvecs = new ArrayList<>();
|
||||
for (var o : in) {
|
||||
var rvec = new Mat();
|
||||
var tvec = new Mat();
|
||||
Calib3d.solvePnP(
|
||||
o.objectPoints,
|
||||
o.imagePoints,
|
||||
cameraMatrixMat.getAsMat(),
|
||||
distortionCoefficientsMat.getAsMatOfDouble(),
|
||||
rvec,
|
||||
tvec);
|
||||
rvecs.add(rvec);
|
||||
tvecs.add(tvec);
|
||||
}
|
||||
|
||||
List<BoardObservation> observations =
|
||||
createObservations(
|
||||
in,
|
||||
cameraMatrixMat.getAsMat(),
|
||||
distortionCoefficientsMat.getAsMatOfDouble(),
|
||||
rvecs,
|
||||
tvecs,
|
||||
new double[] {result.warp_x, result.warp_y});
|
||||
|
||||
rvecs.forEach(Mat::release);
|
||||
tvecs.forEach(Mat::release);
|
||||
|
||||
return new CameraCalibrationCoefficients(
|
||||
in.get(0).size,
|
||||
cameraMatrixMat,
|
||||
distortionCoefficientsMat,
|
||||
new double[] {result.warp_x, result.warp_y},
|
||||
observations,
|
||||
new Size(params.boardWidth, params.boardHeight),
|
||||
params.squareSize);
|
||||
}
|
||||
|
||||
private List<BoardObservation> createObservations(
|
||||
List<FindBoardCornersPipe.FindBoardCornersPipeResult> in,
|
||||
Mat cameraMatrix_,
|
||||
MatOfDouble distortionCoefficients_,
|
||||
List<Mat> rvecs,
|
||||
List<Mat> tvecs,
|
||||
double[] calobject_warp) {
|
||||
List<Mat> objPoints = in.stream().map(it -> it.objectPoints).collect(Collectors.toList());
|
||||
List<Mat> imgPts = in.stream().map(it -> it.imagePoints).collect(Collectors.toList());
|
||||
|
||||
// For each observation, calc reprojection error
|
||||
Mat jac_temp = new Mat();
|
||||
List<BoardObservation> observations = new ArrayList<>();
|
||||
@@ -125,14 +262,36 @@ public class Calibrate3dPipe
|
||||
var i_objPts = i_objPtsNative.toList();
|
||||
var i_imgPts = ((MatOfPoint2f) imgPts.get(i)).toList();
|
||||
|
||||
// Apply warp, if set
|
||||
if (calobject_warp != null && calobject_warp.length == 2) {
|
||||
// mrcal warp model!
|
||||
// The chessboard spans [-1, 1] on the x and y axies. We then let z=k_x(1-x^2)+k_y(1-y^2)
|
||||
|
||||
double xmin = 0;
|
||||
double ymin = 0;
|
||||
double xmax = params.boardWidth * params.squareSize;
|
||||
double ymax = params.boardHeight * params.squareSize;
|
||||
double k_x = calobject_warp[0];
|
||||
double k_y = calobject_warp[1];
|
||||
|
||||
// Convert to list, remap z, and back to cv::Mat
|
||||
var list = i_objPtsNative.toArray();
|
||||
for (var pt : list) {
|
||||
double x_norm = MathUtils.map(pt.x, xmin, xmax, -1, 1);
|
||||
double y_norm = MathUtils.map(pt.y, ymin, ymax, -1, 1);
|
||||
pt.z = k_x * (1 - x_norm * x_norm) + k_y * (1 - y_norm * y_norm);
|
||||
}
|
||||
i_objPtsNative.fromArray(list);
|
||||
}
|
||||
|
||||
var img_pts_reprojected = new MatOfPoint2f();
|
||||
try {
|
||||
Calib3d.projectPoints(
|
||||
i_objPtsNative,
|
||||
rvecs.get(i),
|
||||
tvecs.get(i),
|
||||
cameraMatrix,
|
||||
distortionCoefficients,
|
||||
cameraMatrix_,
|
||||
distortionCoefficients_,
|
||||
img_pts_reprojected,
|
||||
jac_temp,
|
||||
0.0);
|
||||
@@ -164,33 +323,24 @@ public class Calibrate3dPipe
|
||||
}
|
||||
jac_temp.release();
|
||||
|
||||
// Standard deviation of results
|
||||
try {
|
||||
// Print calibration successful
|
||||
logger.info(
|
||||
"CALIBRATION SUCCESS for res "
|
||||
+ params.resolution
|
||||
+ " (with accuracy "
|
||||
+ calibrationAccuracy
|
||||
+ ")! camMatrix: \n"
|
||||
+ new ObjectMapper().writeValueAsString(cameraMatrixMat)
|
||||
+ "\ndistortionCoeffs:\n"
|
||||
+ new ObjectMapper().writeValueAsString(distortionCoefficientsMat)
|
||||
+ "\n");
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.error("Failed to parse calibration data to json!", e);
|
||||
}
|
||||
return new CameraCalibrationCoefficients(
|
||||
params.resolution, cameraMatrixMat, distortionCoefficientsMat, new double[0], observations);
|
||||
return observations;
|
||||
}
|
||||
|
||||
public static class CalibratePipeParams {
|
||||
// Only needs resolution to pass onto CameraCalibrationCoefficients object.
|
||||
private final Size resolution;
|
||||
// Size (in # of corners) of the calibration object
|
||||
public int boardHeight;
|
||||
public int boardWidth;
|
||||
// And size of each square
|
||||
public double squareSize;
|
||||
|
||||
public CalibratePipeParams(Size resolution) {
|
||||
// logger.info("res: " + resolution.toString());
|
||||
this.resolution = resolution;
|
||||
public boolean useMrCal;
|
||||
|
||||
public CalibratePipeParams(
|
||||
int boardHeightSquares, int boardWidthSquares, double squareSize, boolean usemrcal) {
|
||||
this.boardHeight = boardHeightSquares - 1;
|
||||
this.boardWidth = boardWidthSquares - 1;
|
||||
this.squareSize = squareSize;
|
||||
this.useMrCal = usemrcal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,7 +261,7 @@ public class FindBoardCornersPipe
|
||||
var outBoardCorners = new MatOfPoint2f();
|
||||
boardCorners.copyTo(outBoardCorners);
|
||||
|
||||
var objPts = new MatOfPoint2f();
|
||||
var objPts = new MatOfPoint3f();
|
||||
objectPoints.copyTo(objPts);
|
||||
|
||||
// Get the size of the inFrame
|
||||
@@ -329,14 +329,14 @@ public class FindBoardCornersPipe
|
||||
|
||||
public static class FindBoardCornersPipeResult implements Releasable {
|
||||
public Size size;
|
||||
public MatOfPoint2f objectPoints;
|
||||
public MatOfPoint3f objectPoints;
|
||||
public MatOfPoint2f imagePoints;
|
||||
|
||||
// Set later only if we need it
|
||||
public Mat inputImage = null;
|
||||
|
||||
public FindBoardCornersPipeResult(
|
||||
Size size, MatOfPoint2f objectPoints, MatOfPoint2f imagePoints) {
|
||||
Size size, MatOfPoint3f objectPoints, MatOfPoint2f imagePoints) {
|
||||
this.size = size;
|
||||
this.objectPoints = objectPoints;
|
||||
this.imagePoints = imagePoints;
|
||||
|
||||
@@ -24,7 +24,6 @@ import java.util.stream.Collectors;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.core.Size;
|
||||
import org.photonvision.common.dataflow.DataChangeService;
|
||||
import org.photonvision.common.dataflow.events.OutgoingUIEvent;
|
||||
import org.photonvision.common.logging.LogGroup;
|
||||
@@ -58,7 +57,7 @@ public class Calibrate3dPipeline
|
||||
private boolean takeSnapshot = false;
|
||||
|
||||
// Output of the corners
|
||||
final List<FindBoardCornersPipeResult> foundCornersList;
|
||||
public final List<FindBoardCornersPipeResult> foundCornersList;
|
||||
|
||||
/// Output of the calibration, getter method is set for this.
|
||||
private CVPipeResult<CameraCalibrationCoefficients> calibrationOutput;
|
||||
@@ -93,7 +92,7 @@ public class Calibrate3dPipeline
|
||||
|
||||
Calibrate3dPipe.CalibratePipeParams calibratePipeParams =
|
||||
new Calibrate3dPipe.CalibratePipeParams(
|
||||
new Size(frameStaticProperties.imageWidth, frameStaticProperties.imageHeight));
|
||||
settings.boardHeight, settings.boardWidth, settings.gridSize, settings.useMrCal);
|
||||
calibrate3dPipe.setParams(calibratePipeParams);
|
||||
}
|
||||
|
||||
@@ -210,7 +209,8 @@ public class Calibrate3dPipeline
|
||||
Units.metersToInches(settings.gridSize),
|
||||
settings.boardWidth,
|
||||
settings.boardHeight,
|
||||
settings.boardType));
|
||||
settings.boardType,
|
||||
settings.useMrCal));
|
||||
|
||||
DataChangeService.getInstance()
|
||||
.publishEvent(OutgoingUIEvent.wrappedOf("calibrationData", state));
|
||||
|
||||
@@ -28,6 +28,7 @@ public class Calibration3dPipelineSettings extends AdvancedPipelineSettings {
|
||||
public double gridSize = Units.inchesToMeters(1.0);
|
||||
|
||||
public Size resolution = new Size(640, 480);
|
||||
public boolean useMrCal = true;
|
||||
|
||||
public Calibration3dPipelineSettings() {
|
||||
super();
|
||||
|
||||
@@ -26,6 +26,7 @@ public class UICalibrationData {
|
||||
public int patternWidth;
|
||||
public int patternHeight;
|
||||
public BoardType boardType;
|
||||
public boolean useMrCal;
|
||||
|
||||
public UICalibrationData() {}
|
||||
|
||||
@@ -37,7 +38,8 @@ public class UICalibrationData {
|
||||
double squareSizeIn,
|
||||
int patternWidth,
|
||||
int patternHeight,
|
||||
BoardType boardType) {
|
||||
BoardType boardType,
|
||||
boolean useMrCal) {
|
||||
this.count = count;
|
||||
this.minCount = minCount;
|
||||
this.videoModeIndex = videoModeIndex;
|
||||
@@ -46,6 +48,7 @@ public class UICalibrationData {
|
||||
this.patternWidth = patternWidth;
|
||||
this.patternHeight = patternHeight;
|
||||
this.boardType = boardType;
|
||||
this.useMrCal = useMrCal;
|
||||
}
|
||||
|
||||
public enum BoardType {
|
||||
|
||||
@@ -351,6 +351,7 @@ public class VisionModule {
|
||||
settings.boardHeight = data.patternHeight;
|
||||
settings.boardWidth = data.patternWidth;
|
||||
settings.boardType = data.boardType;
|
||||
settings.useMrCal = data.useMrCal;
|
||||
settings.resolution = resolution;
|
||||
|
||||
// Disable gain if not applicable
|
||||
|
||||
@@ -22,17 +22,21 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import edu.wpi.first.math.util.Units;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junitpioneer.jupiter.cartesian.CartesianTest;
|
||||
import org.junitpioneer.jupiter.cartesian.CartesianTest.Enum;
|
||||
import org.junitpioneer.jupiter.cartesian.CartesianTest.Values;
|
||||
import org.opencv.calib3d.Calib3d;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.Size;
|
||||
import org.opencv.imgcodecs.Imgcodecs;
|
||||
import org.photonvision.common.logging.LogGroup;
|
||||
import org.photonvision.common.logging.LogLevel;
|
||||
import org.photonvision.common.logging.Logger;
|
||||
import org.photonvision.common.util.TestUtils;
|
||||
import org.photonvision.mrcal.MrCalJNILoader;
|
||||
import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
|
||||
import org.photonvision.vision.camera.QuirkyCamera;
|
||||
import org.photonvision.vision.frame.Frame;
|
||||
@@ -40,226 +44,104 @@ import org.photonvision.vision.frame.FrameDivisor;
|
||||
import org.photonvision.vision.frame.FrameStaticProperties;
|
||||
import org.photonvision.vision.frame.FrameThresholdType;
|
||||
import org.photonvision.vision.opencv.CVMat;
|
||||
import org.photonvision.vision.pipe.impl.Calibrate3dPipe;
|
||||
import org.photonvision.vision.pipe.impl.FindBoardCornersPipe;
|
||||
import org.photonvision.vision.pipe.impl.FindBoardCornersPipe.FindBoardCornersPipeResult;
|
||||
|
||||
public class Calibrate3dPipeTest {
|
||||
@BeforeAll
|
||||
public static void init() {
|
||||
public static void init() throws IOException {
|
||||
TestUtils.loadLibraries();
|
||||
MrCalJNILoader.forceLoad();
|
||||
|
||||
var logLevel = LogLevel.DEBUG;
|
||||
Logger.setLevel(LogGroup.Camera, logLevel);
|
||||
Logger.setLevel(LogGroup.WebServer, logLevel);
|
||||
Logger.setLevel(LogGroup.VisionModule, logLevel);
|
||||
Logger.setLevel(LogGroup.Data, logLevel);
|
||||
Logger.setLevel(LogGroup.Config, logLevel);
|
||||
Logger.setLevel(LogGroup.General, logLevel);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void perViewErrorsTest() {
|
||||
List<Mat> frames = new ArrayList<>();
|
||||
enum CalibrationDatasets {
|
||||
LIFECAM_480("lifecam/2024-01-02_lifecam_480", new Size(640, 480), new Size(11, 11)),
|
||||
LIFECAM_1280("lifecam/2024-01-02_lifecam_1280", new Size(1280, 720), new Size(11, 11));
|
||||
|
||||
File dir = new File(TestUtils.getDotBoardImagesPath().toAbsolutePath().toString());
|
||||
File[] directoryListing = dir.listFiles();
|
||||
for (var file : directoryListing) {
|
||||
frames.add(Imgcodecs.imread(file.getAbsolutePath()));
|
||||
}
|
||||
final String path;
|
||||
final Size size;
|
||||
final Size boardSize;
|
||||
|
||||
FindBoardCornersPipe findBoardCornersPipe = new FindBoardCornersPipe();
|
||||
findBoardCornersPipe.setParams(
|
||||
new FindBoardCornersPipe.FindCornersPipeParams(
|
||||
11, 4, UICalibrationData.BoardType.DOTBOARD, 15, FrameDivisor.NONE));
|
||||
|
||||
List<FindBoardCornersPipeResult> foundCornersList = new ArrayList<>();
|
||||
|
||||
for (var f : frames) {
|
||||
var copy = new Mat();
|
||||
f.copyTo(copy);
|
||||
foundCornersList.add(findBoardCornersPipe.run(Pair.of(f, copy)).output);
|
||||
}
|
||||
|
||||
Calibrate3dPipe calibrate3dPipe = new Calibrate3dPipe();
|
||||
calibrate3dPipe.setParams(new Calibrate3dPipe.CalibratePipeParams(new Size(640, 480)));
|
||||
|
||||
var calibrate3dPipeOutput = calibrate3dPipe.run(foundCornersList);
|
||||
assertTrue(calibrate3dPipeOutput.output.observations.size() > 0);
|
||||
|
||||
for (var f : frames) {
|
||||
f.release();
|
||||
private CalibrationDatasets(String path, Size image, Size chessboard) {
|
||||
this.path = path;
|
||||
this.size = image;
|
||||
this.boardSize = chessboard;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void calibrationPipelineTest() {
|
||||
int startMatCount = CVMat.getMatCount();
|
||||
|
||||
File dir = new File(TestUtils.getDotBoardImagesPath().toAbsolutePath().toString());
|
||||
File[] directoryListing = dir.listFiles();
|
||||
|
||||
Calibrate3dPipeline calibration3dPipeline = new Calibrate3dPipeline(20, "unique_name_lol");
|
||||
calibration3dPipeline.getSettings().boardHeight = 11;
|
||||
calibration3dPipeline.getSettings().boardWidth = 4;
|
||||
calibration3dPipeline.getSettings().boardType = UICalibrationData.BoardType.DOTBOARD;
|
||||
calibration3dPipeline.getSettings().gridSize = 15;
|
||||
calibration3dPipeline.getSettings().resolution = new Size(640, 480);
|
||||
|
||||
for (var file : directoryListing) {
|
||||
calibration3dPipeline.takeSnapshot();
|
||||
var frame =
|
||||
new Frame(
|
||||
new CVMat(Imgcodecs.imread(file.getAbsolutePath())),
|
||||
new CVMat(),
|
||||
FrameThresholdType.NONE,
|
||||
new FrameStaticProperties(640, 480, 60, null));
|
||||
var output = calibration3dPipeline.run(frame, QuirkyCamera.DefaultCamera);
|
||||
// TestUtils.showImage(output.inputAndOutputFrame.processedImage.getMat());
|
||||
output.release();
|
||||
frame.release();
|
||||
}
|
||||
|
||||
assertTrue(
|
||||
calibration3dPipeline.foundCornersList.stream()
|
||||
.map(it -> it.imagePoints)
|
||||
.allMatch(it -> it.width() > 0 && it.height() > 0));
|
||||
|
||||
calibration3dPipeline.removeSnapshot(0);
|
||||
var frame =
|
||||
new Frame(
|
||||
new CVMat(Imgcodecs.imread(directoryListing[0].getAbsolutePath())),
|
||||
new CVMat(),
|
||||
FrameThresholdType.NONE,
|
||||
new FrameStaticProperties(640, 480, 60, null));
|
||||
calibration3dPipeline.run(frame, QuirkyCamera.DefaultCamera).release();
|
||||
frame.release();
|
||||
|
||||
assertTrue(
|
||||
calibration3dPipeline.foundCornersList.stream()
|
||||
.map(it -> it.imagePoints)
|
||||
.allMatch(it -> it.width() > 0 && it.height() > 0));
|
||||
|
||||
var cal = calibration3dPipeline.tryCalibration();
|
||||
calibration3dPipeline.finishCalibration();
|
||||
|
||||
assertNotNull(cal);
|
||||
assertNotNull(cal.observations);
|
||||
System.out.println("Camera Intrinsics: " + cal.cameraIntrinsics.toString());
|
||||
System.out.println("Dist Coeffs: " + cal.distCoeffs.toString());
|
||||
|
||||
// Confirm we didn't get leaky on our mat usage
|
||||
// assertTrue(CVMat.getMatCount() == startMatCount); // TODO Figure out why this doesn't work in
|
||||
// CI
|
||||
System.out.println("CVMats left: " + CVMat.getMatCount() + " Start: " + startMatCount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void calibrateSquares320x240_pi() {
|
||||
/**
|
||||
* Run camera calibration on a given dataset
|
||||
*
|
||||
* @param dataset Location of images and their size
|
||||
* @param useMrCal If we should use mrcal or opencv for camera calibration
|
||||
*/
|
||||
@CartesianTest
|
||||
public void calibrateTestMatrix(
|
||||
@Enum(CalibrationDatasets.class) CalibrationDatasets dataset,
|
||||
@Values(booleans = {true, false}) boolean useMrCal) {
|
||||
// Pi3 and V1.3 camera
|
||||
String base = TestUtils.getSquaresBoardImagesPath().toAbsolutePath().toString();
|
||||
File dir = Path.of(base, "piCam", "320_240_1").toFile();
|
||||
Size sz = new Size(320, 240);
|
||||
calibrateSquaresCommon(sz, dir);
|
||||
File dir = Path.of(base, dataset.path).toFile();
|
||||
calibrateSquaresCommon(dataset.size, dir, dataset.boardSize, useMrCal);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void calibrateSquares640x480_pi() {
|
||||
// Pi3 and V1.3 camera
|
||||
String base = TestUtils.getSquaresBoardImagesPath().toAbsolutePath().toString();
|
||||
File dir = Path.of(base, "piCam", "640_480_1").toFile();
|
||||
Size sz = new Size(640, 480);
|
||||
calibrateSquaresCommon(sz, dir);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void calibrateSquares960x720_pi() {
|
||||
// Pi3 and V1.3 camera
|
||||
String base = TestUtils.getSquaresBoardImagesPath().toAbsolutePath().toString();
|
||||
File dir = Path.of(base, "piCam", "960_720_1").toFile();
|
||||
Size sz = new Size(960, 720);
|
||||
calibrateSquaresCommon(sz, dir);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void calibrateSquares1920x1080_pi() {
|
||||
// Pi3 and V1.3 camera
|
||||
String base = TestUtils.getSquaresBoardImagesPath().toAbsolutePath().toString();
|
||||
File dir = Path.of(base, "piCam", "1920_1080_1").toFile();
|
||||
Size sz = new Size(1920, 1080);
|
||||
calibrateSquaresCommon(sz, dir);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void calibrateSquares320x240_gloworm() {
|
||||
// Gloworm Beta
|
||||
String base = TestUtils.getSquaresBoardImagesPath().toAbsolutePath().toString();
|
||||
File dir = Path.of(base, "gloworm", "320_240_1").toFile();
|
||||
Size sz = new Size(320, 240);
|
||||
Size boardDim = new Size(9, 7);
|
||||
calibrateSquaresCommon(sz, dir, boardDim);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void calibrateSquares_960_720_gloworm() {
|
||||
// Gloworm Beta
|
||||
String base = TestUtils.getSquaresBoardImagesPath().toAbsolutePath().toString();
|
||||
File dir = Path.of(base, "gloworm", "960_720_1").toFile();
|
||||
Size sz = new Size(960, 720);
|
||||
Size boardDim = new Size(9, 7);
|
||||
calibrateSquaresCommon(sz, dir, boardDim);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void calibrateSquares_1280_720_gloworm() {
|
||||
// Gloworm Beta
|
||||
// This image set will return a fairly offset Y-pixel for the optical center point
|
||||
String base = TestUtils.getSquaresBoardImagesPath().toAbsolutePath().toString();
|
||||
File dir = Path.of(base, "gloworm", "1280_720_1").toFile();
|
||||
Size sz = new Size(1280, 720);
|
||||
Size boardDim = new Size(9, 7);
|
||||
calibrateSquaresCommon(sz, dir, boardDim, 640, 192);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void calibrateSquares_1920_1080_gloworm() {
|
||||
// Gloworm Beta
|
||||
// This image set has most samples on the right, and is expected to return a slightly
|
||||
// wonky calibration.
|
||||
String base = TestUtils.getSquaresBoardImagesPath().toAbsolutePath().toString();
|
||||
File dir = Path.of(base, "gloworm", "1920_1080_1").toFile();
|
||||
Size sz = new Size(1920, 1080);
|
||||
Size boardDim = new Size(9, 7);
|
||||
calibrateSquaresCommon(sz, dir, boardDim, 1311, 540);
|
||||
}
|
||||
|
||||
public void calibrateSquaresCommon(Size imgRes, File rootFolder) {
|
||||
calibrateSquaresCommon(imgRes, rootFolder, new Size(8, 8));
|
||||
}
|
||||
|
||||
public void calibrateSquaresCommon(Size imgRes, File rootFolder, Size boardDim) {
|
||||
public static void calibrateSquaresCommon(
|
||||
Size imgRes, File rootFolder, Size boardDim, boolean useMrCal) {
|
||||
calibrateSquaresCommon(
|
||||
imgRes, rootFolder, boardDim, Units.inchesToMeters(1), imgRes.width / 2, imgRes.height / 2);
|
||||
imgRes,
|
||||
rootFolder,
|
||||
boardDim,
|
||||
Units.inchesToMeters(1),
|
||||
imgRes.width / 2,
|
||||
imgRes.height / 2,
|
||||
useMrCal);
|
||||
}
|
||||
|
||||
public void calibrateSquaresCommon(
|
||||
Size imgRes, File rootFolder, Size boardDim, double expectedXCenter, double expectedYCenter) {
|
||||
public static void calibrateSquaresCommon(
|
||||
Size imgRes,
|
||||
File rootFolder,
|
||||
Size boardDim,
|
||||
double expectedXCenter,
|
||||
double expectedYCenter,
|
||||
boolean useMrCal) {
|
||||
calibrateSquaresCommon(
|
||||
imgRes, rootFolder, boardDim, Units.inchesToMeters(1), expectedXCenter, expectedYCenter);
|
||||
imgRes,
|
||||
rootFolder,
|
||||
boardDim,
|
||||
Units.inchesToMeters(1),
|
||||
expectedXCenter,
|
||||
expectedYCenter,
|
||||
useMrCal);
|
||||
}
|
||||
|
||||
public void calibrateSquaresCommon(
|
||||
public static void calibrateSquaresCommon(
|
||||
Size imgRes,
|
||||
File rootFolder,
|
||||
Size boardDim,
|
||||
double boardGridSize_m,
|
||||
double expectedXCenter,
|
||||
double expectedYCenter) {
|
||||
double expectedYCenter,
|
||||
boolean useMrCal) {
|
||||
int startMatCount = CVMat.getMatCount();
|
||||
|
||||
File[] directoryListing = rootFolder.listFiles();
|
||||
|
||||
assertTrue(directoryListing.length >= 25);
|
||||
assertTrue(directoryListing.length >= 12);
|
||||
|
||||
Calibrate3dPipeline calibration3dPipeline = new Calibrate3dPipeline(20, "test_squares_common");
|
||||
Calibrate3dPipeline calibration3dPipeline = new Calibrate3dPipeline(10, "test_squares_common");
|
||||
calibration3dPipeline.getSettings().boardType = UICalibrationData.BoardType.CHESSBOARD;
|
||||
calibration3dPipeline.getSettings().resolution = imgRes;
|
||||
calibration3dPipeline.getSettings().boardHeight = (int) Math.round(boardDim.height);
|
||||
calibration3dPipeline.getSettings().boardWidth = (int) Math.round(boardDim.width);
|
||||
calibration3dPipeline.getSettings().gridSize = boardGridSize_m;
|
||||
calibration3dPipeline.getSettings().streamingFrameDivisor = FrameDivisor.NONE;
|
||||
calibration3dPipeline.getSettings().useMrCal = useMrCal;
|
||||
|
||||
for (var file : directoryListing) {
|
||||
if (file.isFile()) {
|
||||
|
||||
@@ -37,6 +37,7 @@ import org.photonvision.common.logging.Logger;
|
||||
import org.photonvision.common.networking.NetworkManager;
|
||||
import org.photonvision.common.util.TestUtils;
|
||||
import org.photonvision.common.util.numbers.IntegerCouple;
|
||||
import org.photonvision.mrcal.MrCalJNILoader;
|
||||
import org.photonvision.raspi.LibCameraJNILoader;
|
||||
import org.photonvision.server.Server;
|
||||
import org.photonvision.vision.camera.FileVisionSource;
|
||||
@@ -317,6 +318,14 @@ public class Main {
|
||||
logger.error("Failed to load libcamera-JNI!", e);
|
||||
}
|
||||
|
||||
try {
|
||||
MrCalJNILoader.forceLoad();
|
||||
} catch (IOException e) {
|
||||
logger.warn(
|
||||
"Failed to load mrcal-JNI! Camera calibration will fall back to opencv\n"
|
||||
+ e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
if (!handleArgs(args)) {
|
||||
System.exit(0);
|
||||
|
||||
|
After Width: | Height: | Size: 250 KiB |
|
After Width: | Height: | Size: 238 KiB |
|
After Width: | Height: | Size: 242 KiB |
|
After Width: | Height: | Size: 243 KiB |
|
After Width: | Height: | Size: 250 KiB |
|
After Width: | Height: | Size: 244 KiB |
|
After Width: | Height: | Size: 227 KiB |
|
After Width: | Height: | Size: 224 KiB |
|
After Width: | Height: | Size: 244 KiB |
|
After Width: | Height: | Size: 238 KiB |
|
After Width: | Height: | Size: 242 KiB |
|
After Width: | Height: | Size: 241 KiB |
|
After Width: | Height: | Size: 253 KiB |
|
After Width: | Height: | Size: 261 KiB |
|
After Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 110 KiB |
411
test-resources/calibrationSquaresImg/piCam/640_480_1/corners.vnl
Normal file
@@ -0,0 +1,411 @@
|
||||
## generated with mrgingham --jobs 4 --gridn 7 img10.jpg img11.jpg img12.jpg img13.jpg img14.jpg img15.jpg img16.jpg img17.jpg img18.jpg img19.jpg img1.jpg img20.jpg img21.jpg img22.jpg img23.jpg img24.jpg img25.jpg img2.jpg img3.jpg img4.jpg img5.jpg img6.jpg img7.jpg img8.jpg img9.jpg
|
||||
# filename x y level
|
||||
img10.jpg - - -
|
||||
img11.jpg - - -
|
||||
img15.jpg 305.709000 174.707000 0
|
||||
img15.jpg 351.673000 176.160000 0
|
||||
img15.jpg 397.419000 177.562000 0
|
||||
img15.jpg 442.075000 179.037000 0
|
||||
img15.jpg 487.177000 180.891000 0
|
||||
img15.jpg 531.785000 181.860000 0
|
||||
img15.jpg 573.738000 183.557000 0
|
||||
img15.jpg 304.294000 219.620000 0
|
||||
img15.jpg 350.203000 220.724000 0
|
||||
img15.jpg 395.748000 221.699000 0
|
||||
img15.jpg 440.862000 222.973000 0
|
||||
img15.jpg 485.520000 224.850000 0
|
||||
img15.jpg 530.185000 225.869000 0
|
||||
img15.jpg 572.114000 227.503000 0
|
||||
img15.jpg 303.243000 263.590000 0
|
||||
img15.jpg 349.341000 265.627000 0
|
||||
img15.jpg 394.469000 266.043000 0
|
||||
img15.jpg 439.742000 267.237000 0
|
||||
img15.jpg 484.055000 268.790000 0
|
||||
img15.jpg 528.175000 269.724000 0
|
||||
img15.jpg 570.690000 270.726000 0
|
||||
img15.jpg 301.669000 309.033000 0
|
||||
img15.jpg 347.288000 309.528000 0
|
||||
img15.jpg 393.567000 310.660000 0
|
||||
img15.jpg 437.619000 311.441000 0
|
||||
img15.jpg 482.058000 312.254000 0
|
||||
img15.jpg 526.403000 313.246000 0
|
||||
img15.jpg 569.039000 313.931000 0
|
||||
img15.jpg 299.327000 353.836000 0
|
||||
img15.jpg 345.584000 354.487000 0
|
||||
img15.jpg 391.137000 354.882000 0
|
||||
img15.jpg 436.249000 355.728000 0
|
||||
img15.jpg 480.324000 356.082000 0
|
||||
img15.jpg 524.946000 356.456000 0
|
||||
img15.jpg 566.890000 357.050000 0
|
||||
img15.jpg 297.979000 399.116000 0
|
||||
img15.jpg 344.187000 399.653000 0
|
||||
img15.jpg 389.909000 399.152000 0
|
||||
img15.jpg 434.862000 399.209000 0
|
||||
img15.jpg 478.911000 400.062000 0
|
||||
img15.jpg 522.668000 399.882000 0
|
||||
img15.jpg 565.371000 400.272000 0
|
||||
img15.jpg 296.078000 445.016000 0
|
||||
img15.jpg 342.710000 444.040000 0
|
||||
img15.jpg 387.822000 443.536000 0
|
||||
img15.jpg 433.286000 443.428000 0
|
||||
img15.jpg 476.779000 442.870000 0
|
||||
img15.jpg 520.055000 442.343000 0
|
||||
img15.jpg 562.414000 442.205000 0
|
||||
img14.jpg - - -
|
||||
img18.jpg 91.257764 62.341333 0
|
||||
img18.jpg 156.367723 66.974450 0
|
||||
img18.jpg 218.066065 71.650665 0
|
||||
img18.jpg 276.386861 76.251825 0
|
||||
img18.jpg 331.055492 81.147211 0
|
||||
img18.jpg 383.696897 84.814439 0
|
||||
img18.jpg 430.893194 89.012836 0
|
||||
img18.jpg 91.833674 123.430732 0
|
||||
img18.jpg 155.905789 126.344950 0
|
||||
img18.jpg 217.913026 129.702873 0
|
||||
img18.jpg 274.982180 133.319740 0
|
||||
img18.jpg 329.372274 135.975815 0
|
||||
img18.jpg 380.871511 138.540811 0
|
||||
img18.jpg 427.956504 141.086789 0
|
||||
img18.jpg 91.771236 183.897303 0
|
||||
img18.jpg 156.005710 185.474423 0
|
||||
img18.jpg 217.247203 187.258936 0
|
||||
img18.jpg 274.219614 188.919293 0
|
||||
img18.jpg 327.751591 189.691818 0
|
||||
img18.jpg 378.443874 191.387865 0
|
||||
img18.jpg 425.847568 191.850230 0
|
||||
img18.jpg 91.861943 243.611033 0
|
||||
img18.jpg 155.182405 243.511549 0
|
||||
img18.jpg 216.832614 243.122519 0
|
||||
img18.jpg 273.129283 242.355705 0
|
||||
img18.jpg 325.343307 241.717585 0
|
||||
img18.jpg 375.851167 241.553501 0
|
||||
img18.jpg 423.055064 241.803709 0
|
||||
img18.jpg 91.671178 302.440746 0
|
||||
img18.jpg 155.273091 300.177818 0
|
||||
img18.jpg 215.216509 297.399528 0
|
||||
img18.jpg 272.414663 294.579327 0
|
||||
img18.jpg 323.101889 292.983598 0
|
||||
img18.jpg 373.559284 291.323639 0
|
||||
img18.jpg 419.835057 290.277082 0
|
||||
img18.jpg 92.857058 359.214116 0
|
||||
img18.jpg 154.937554 355.849957 0
|
||||
img18.jpg 213.863967 351.613097 0
|
||||
img18.jpg 269.476977 347.721722 0
|
||||
img18.jpg 321.803464 344.059031 0
|
||||
img18.jpg 371.343700 341.393939 0
|
||||
img18.jpg 417.516845 338.833116 0
|
||||
img18.jpg 93.077960 415.613843 0
|
||||
img18.jpg 154.037428 409.923307 0
|
||||
img18.jpg 212.834834 404.066145 0
|
||||
img18.jpg 267.771666 398.702590 0
|
||||
img18.jpg 319.298246 393.980064 0
|
||||
img18.jpg 368.228850 389.593709 0
|
||||
img18.jpg 414.674171 385.356734 0
|
||||
img12.jpg - - -
|
||||
img13.jpg 207.359000 161.061000 0
|
||||
img13.jpg 256.830000 163.237000 0
|
||||
img13.jpg 304.053000 165.752000 0
|
||||
img13.jpg 349.537000 168.300000 0
|
||||
img13.jpg 393.125000 170.923000 0
|
||||
img13.jpg 436.193000 172.818000 0
|
||||
img13.jpg 476.734000 174.922000 0
|
||||
img13.jpg 206.200000 207.683000 0
|
||||
img13.jpg 255.307000 209.547000 0
|
||||
img13.jpg 303.050000 211.483000 0
|
||||
img13.jpg 347.176000 213.290000 0
|
||||
img13.jpg 391.548000 214.998000 0
|
||||
img13.jpg 434.194000 216.182000 0
|
||||
img13.jpg 475.306000 217.711000 0
|
||||
img13.jpg 204.869000 254.591000 0
|
||||
img13.jpg 253.717000 255.146000 0
|
||||
img13.jpg 301.636000 256.939000 0
|
||||
img13.jpg 346.212000 257.436000 0
|
||||
img13.jpg 389.826000 258.667000 0
|
||||
img13.jpg 432.929000 259.004000 0
|
||||
img13.jpg 473.420000 260.297000 0
|
||||
img13.jpg 203.314000 301.767000 0
|
||||
img13.jpg 251.833000 301.487000 0
|
||||
img13.jpg 299.666000 301.357000 0
|
||||
img13.jpg 344.634000 301.545000 0
|
||||
img13.jpg 387.881000 301.493000 0
|
||||
img13.jpg 431.046000 302.380000 0
|
||||
img13.jpg 471.777000 302.712000 0
|
||||
img13.jpg 201.107000 348.792000 0
|
||||
img13.jpg 249.800000 347.677000 0
|
||||
img13.jpg 297.241000 347.004000 0
|
||||
img13.jpg 343.254000 346.381000 0
|
||||
img13.jpg 386.326000 345.487000 0
|
||||
img13.jpg 429.810000 345.230000 0
|
||||
img13.jpg 469.742000 345.034000 0
|
||||
img13.jpg 199.756000 395.295000 0
|
||||
img13.jpg 248.198000 394.029000 0
|
||||
img13.jpg 295.721000 392.398000 0
|
||||
img13.jpg 340.746000 390.831000 0
|
||||
img13.jpg 384.770000 389.311000 0
|
||||
img13.jpg 427.527000 388.627000 0
|
||||
img13.jpg 468.236000 387.648000 0
|
||||
img13.jpg 197.684000 442.702000 0
|
||||
img13.jpg 246.477000 439.342000 0
|
||||
img13.jpg 293.202000 437.257000 0
|
||||
img13.jpg 339.300000 435.403000 0
|
||||
img13.jpg 382.577000 432.917000 0
|
||||
img13.jpg 425.605000 431.302000 0
|
||||
img13.jpg 465.707000 429.225000 0
|
||||
img19.jpg - - -
|
||||
img21.jpg 171.432587 62.910910 0
|
||||
img21.jpg 233.543966 66.989676 0
|
||||
img21.jpg 292.892198 71.025168 0
|
||||
img21.jpg 349.449118 75.488547 0
|
||||
img21.jpg 402.475665 78.823783 0
|
||||
img21.jpg 454.664044 82.293706 0
|
||||
img21.jpg 502.641518 85.776245 0
|
||||
img21.jpg 171.514734 121.993103 0
|
||||
img21.jpg 232.390154 125.308964 0
|
||||
img21.jpg 291.615192 128.775042 0
|
||||
img21.jpg 347.095808 131.455464 0
|
||||
img21.jpg 399.571916 133.920527 0
|
||||
img21.jpg 451.377575 136.702216 0
|
||||
img21.jpg 499.408046 139.005337 0
|
||||
img21.jpg 170.628776 181.228863 0
|
||||
img21.jpg 231.887269 183.495513 0
|
||||
img21.jpg 289.549706 184.943596 0
|
||||
img21.jpg 345.099630 186.724366 0
|
||||
img21.jpg 397.291107 187.669673 0
|
||||
img21.jpg 448.012251 188.937037 0
|
||||
img21.jpg 495.593336 189.964319 0
|
||||
img21.jpg 170.167998 238.958158 0
|
||||
img21.jpg 230.558390 239.418254 0
|
||||
img21.jpg 288.497209 239.644103 0
|
||||
img21.jpg 342.515469 239.491195 0
|
||||
img21.jpg 394.007115 239.553513 0
|
||||
img21.jpg 444.372561 240.019514 0
|
||||
img21.jpg 491.944262 240.141740 0
|
||||
img21.jpg 169.316729 295.157425 0
|
||||
img21.jpg 229.919699 293.846090 0
|
||||
img21.jpg 285.963235 292.935160 0
|
||||
img21.jpg 339.829832 291.170168 0
|
||||
img21.jpg 391.046096 291.017874 0
|
||||
img21.jpg 441.655490 290.262712 0
|
||||
img21.jpg 488.484545 289.606238 0
|
||||
img21.jpg 169.391253 351.146683 0
|
||||
img21.jpg 227.941254 348.429636 0
|
||||
img21.jpg 284.725293 345.328308 0
|
||||
img21.jpg 337.367128 343.363980 0
|
||||
img21.jpg 388.802075 341.231567 0
|
||||
img21.jpg 438.530539 340.104779 0
|
||||
img21.jpg 485.397165 338.584278 0
|
||||
img21.jpg 167.777378 405.478817 0
|
||||
img21.jpg 226.748250 401.012785 0
|
||||
img21.jpg 282.079972 397.147880 0
|
||||
img21.jpg 335.558834 393.843829 0
|
||||
img21.jpg 385.729546 390.894412 0
|
||||
img21.jpg 434.287148 387.675643 0
|
||||
img21.jpg 480.912754 385.395124 0
|
||||
img22.jpg 170.954619 66.207185 0
|
||||
img22.jpg 232.925674 70.329078 0
|
||||
img22.jpg 291.929905 74.309458 0
|
||||
img22.jpg 348.618405 78.647759 0
|
||||
img22.jpg 401.907159 82.127252 0
|
||||
img22.jpg 454.073162 85.908807 0
|
||||
img22.jpg 502.384304 89.612773 0
|
||||
img22.jpg 170.672483 125.415122 0
|
||||
img22.jpg 231.462866 128.773806 0
|
||||
img22.jpg 291.032499 132.398581 0
|
||||
img22.jpg 345.684337 134.915663 0
|
||||
img22.jpg 399.486674 137.530287 0
|
||||
img22.jpg 450.909032 139.320663 0
|
||||
img22.jpg 498.894327 142.166906 0
|
||||
img22.jpg 170.039566 184.623157 0
|
||||
img22.jpg 231.346178 186.659774 0
|
||||
img22.jpg 289.163911 188.055595 0
|
||||
img22.jpg 344.132387 189.912225 0
|
||||
img22.jpg 396.321555 191.204305 0
|
||||
img22.jpg 447.459807 192.313964 0
|
||||
img22.jpg 495.143206 193.384192 0
|
||||
img22.jpg 169.696611 241.964717 0
|
||||
img22.jpg 229.931254 242.875875 0
|
||||
img22.jpg 288.019370 243.058687 0
|
||||
img22.jpg 341.607179 243.256923 0
|
||||
img22.jpg 393.716058 243.405927 0
|
||||
img22.jpg 443.892988 243.204055 0
|
||||
img22.jpg 491.834639 243.220219 0
|
||||
img22.jpg 168.652822 299.444695 0
|
||||
img22.jpg 228.891661 297.586808 0
|
||||
img22.jpg 285.893939 296.780702 0
|
||||
img22.jpg 340.007199 294.868026 0
|
||||
img22.jpg 390.975809 294.235148 0
|
||||
img22.jpg 441.273511 293.692402 0
|
||||
img22.jpg 487.912647 293.046936 0
|
||||
img22.jpg 168.141104 354.722532 0
|
||||
img22.jpg 227.178901 352.045371 0
|
||||
img22.jpg 284.087214 349.402920 0
|
||||
img22.jpg 337.067904 346.926664 0
|
||||
img22.jpg 388.459654 345.137176 0
|
||||
img22.jpg 437.933373 343.278757 0
|
||||
img22.jpg 484.875402 342.180470 0
|
||||
img22.jpg 167.262818 409.231675 0
|
||||
img22.jpg 225.776135 404.722118 0
|
||||
img22.jpg 281.542602 401.181308 0
|
||||
img22.jpg 334.813427 397.384595 0
|
||||
img22.jpg 385.508089 394.555612 0
|
||||
img22.jpg 434.342519 391.542815 0
|
||||
img22.jpg 480.929907 388.713162 0
|
||||
img25.jpg - - -
|
||||
img2.jpg - - -
|
||||
img16.jpg - - -
|
||||
img17.jpg - - -
|
||||
img5.jpg - - -
|
||||
img6.jpg - - -
|
||||
img9.jpg - - -
|
||||
img1.jpg - - -
|
||||
img20.jpg 203.417000 161.504000 0
|
||||
img20.jpg 239.114000 163.886000 0
|
||||
img20.jpg 273.107000 166.449000 0
|
||||
img20.jpg 305.916000 168.563000 0
|
||||
img20.jpg 337.115000 170.991000 0
|
||||
img20.jpg 368.030000 172.639000 0
|
||||
img20.jpg 397.193000 175.197000 0
|
||||
img20.jpg 202.091000 195.309000 0
|
||||
img20.jpg 237.449000 197.254000 0
|
||||
img20.jpg 271.811000 199.126000 0
|
||||
img20.jpg 303.884000 201.339000 0
|
||||
img20.jpg 335.799000 202.775000 0
|
||||
img20.jpg 366.476000 203.962000 0
|
||||
img20.jpg 395.621000 205.477000 0
|
||||
img20.jpg 200.763000 229.055000 0
|
||||
img20.jpg 235.846000 230.374000 0
|
||||
img20.jpg 270.150000 231.701000 0
|
||||
img20.jpg 302.963000 233.051000 0
|
||||
img20.jpg 334.118000 233.940000 0
|
||||
img20.jpg 364.861000 235.145000 0
|
||||
img20.jpg 393.631000 236.292000 0
|
||||
img20.jpg 199.378000 263.126000 0
|
||||
img20.jpg 234.485000 263.471000 0
|
||||
img20.jpg 268.802000 263.894000 0
|
||||
img20.jpg 301.142000 265.078000 0
|
||||
img20.jpg 332.232000 265.327000 0
|
||||
img20.jpg 363.139000 265.668000 0
|
||||
img20.jpg 391.948000 266.685000 0
|
||||
img20.jpg 198.029000 296.998000 0
|
||||
img20.jpg 233.112000 296.261000 0
|
||||
img20.jpg 266.880000 296.323000 0
|
||||
img20.jpg 299.629000 296.302000 0
|
||||
img20.jpg 330.424000 296.424000 0
|
||||
img20.jpg 361.084000 297.002000 0
|
||||
img20.jpg 389.842000 296.814000 0
|
||||
img20.jpg 195.902000 329.523000 0
|
||||
img20.jpg 231.127000 329.233000 0
|
||||
img20.jpg 265.381000 329.073000 0
|
||||
img20.jpg 297.542000 327.951000 0
|
||||
img20.jpg 328.969000 326.898000 0
|
||||
img20.jpg 359.115000 327.240000 0
|
||||
img20.jpg 388.128000 327.274000 0
|
||||
img20.jpg 194.570000 362.996000 0
|
||||
img20.jpg 229.508000 361.475000 0
|
||||
img20.jpg 263.279000 360.993000 0
|
||||
img20.jpg 295.782000 359.146000 0
|
||||
img20.jpg 326.440000 358.773000 0
|
||||
img20.jpg 357.322000 357.793000 0
|
||||
img20.jpg 385.821000 357.147000 0
|
||||
img23.jpg 167.096579 75.563045 0
|
||||
img23.jpg 229.044390 79.769831 0
|
||||
img23.jpg 288.347806 83.685341 0
|
||||
img23.jpg 345.133847 87.745306 0
|
||||
img23.jpg 398.242697 91.371038 0
|
||||
img23.jpg 450.258727 94.791385 0
|
||||
img23.jpg 498.481567 98.006336 0
|
||||
img23.jpg 166.457718 134.764653 0
|
||||
img23.jpg 228.030388 137.434067 0
|
||||
img23.jpg 287.553592 141.303498 0
|
||||
img23.jpg 343.134211 143.527946 0
|
||||
img23.jpg 395.604435 146.123987 0
|
||||
img23.jpg 447.141897 148.657081 0
|
||||
img23.jpg 495.483977 150.939440 0
|
||||
img23.jpg 165.628349 193.670954 0
|
||||
img23.jpg 227.436170 195.524500 0
|
||||
img23.jpg 285.517103 197.434608 0
|
||||
img23.jpg 341.055025 198.983142 0
|
||||
img23.jpg 393.692971 199.687896 0
|
||||
img23.jpg 444.170013 200.967691 0
|
||||
img23.jpg 492.007105 201.736728 0
|
||||
img23.jpg 165.260826 251.970544 0
|
||||
img23.jpg 225.900270 251.973265 0
|
||||
img23.jpg 284.098946 252.060422 0
|
||||
img23.jpg 338.834414 251.788450 0
|
||||
img23.jpg 390.193044 251.929631 0
|
||||
img23.jpg 440.831715 251.885293 0
|
||||
img23.jpg 488.336721 252.191396 0
|
||||
img23.jpg 163.791281 309.386063 0
|
||||
img23.jpg 224.929397 307.232453 0
|
||||
img23.jpg 282.176961 305.725654 0
|
||||
img23.jpg 335.984449 304.607775 0
|
||||
img23.jpg 387.279070 303.196963 0
|
||||
img23.jpg 437.747368 302.540835 0
|
||||
img23.jpg 485.297854 301.494266 0
|
||||
img23.jpg 163.968029 365.050099 0
|
||||
img23.jpg 223.201096 362.148888 0
|
||||
img23.jpg 280.445895 359.351900 0
|
||||
img23.jpg 333.640557 356.491297 0
|
||||
img23.jpg 385.611380 354.094168 0
|
||||
img23.jpg 435.165143 352.729433 0
|
||||
img23.jpg 482.091338 350.966958 0
|
||||
img23.jpg 163.221401 419.674165 0
|
||||
img23.jpg 221.857647 415.074118 0
|
||||
img23.jpg 277.713041 411.798257 0
|
||||
img23.jpg 331.766780 407.068828 0
|
||||
img23.jpg 382.058162 404.138822 0
|
||||
img23.jpg 431.852815 401.145043 0
|
||||
img23.jpg 478.272757 398.091691 0
|
||||
img24.jpg - - -
|
||||
img3.jpg - - -
|
||||
img4.jpg 325.516000 132.934000 0
|
||||
img4.jpg 371.214000 134.351000 0
|
||||
img4.jpg 415.623000 135.342000 0
|
||||
img4.jpg 460.354000 136.823000 0
|
||||
img4.jpg 504.145000 138.109000 0
|
||||
img4.jpg 547.712000 139.650000 0
|
||||
img4.jpg 594.000000 148.683000 0
|
||||
img4.jpg 324.871000 176.873000 0
|
||||
img4.jpg 369.412000 177.909000 0
|
||||
img4.jpg 414.233000 179.545000 0
|
||||
img4.jpg 457.929000 181.193000 0
|
||||
img4.jpg 501.911000 181.665000 0
|
||||
img4.jpg 545.353000 183.286000 0
|
||||
img4.jpg 587.117000 184.587000 0
|
||||
img4.jpg 323.335000 221.308000 0
|
||||
img4.jpg 368.023000 221.689000 0
|
||||
img4.jpg 412.790000 223.232000 0
|
||||
img4.jpg 456.687000 223.741000 0
|
||||
img4.jpg 499.676000 225.028000 0
|
||||
img4.jpg 543.056000 226.144000 0
|
||||
img4.jpg 584.376000 227.355000 0
|
||||
img4.jpg 321.873000 264.356000 0
|
||||
img4.jpg 366.604000 265.474000 0
|
||||
img4.jpg 411.506000 265.928000 0
|
||||
img4.jpg 454.473000 267.156000 0
|
||||
img4.jpg 497.687000 267.316000 0
|
||||
img4.jpg 540.800000 268.549000 0
|
||||
img4.jpg 582.004000 268.906000 0
|
||||
img4.jpg 321.069000 307.494000 0
|
||||
img4.jpg 365.617000 308.399000 0
|
||||
img4.jpg 409.188000 309.055000 0
|
||||
img4.jpg 453.092000 309.161000 0
|
||||
img4.jpg 495.585000 309.516000 0
|
||||
img4.jpg 538.113000 310.626000 0
|
||||
img4.jpg 579.114000 310.916000 0
|
||||
img4.jpg 319.962000 351.063000 0
|
||||
img4.jpg 363.211000 351.180000 0
|
||||
img4.jpg 407.939000 351.029000 0
|
||||
img4.jpg 450.832000 351.136000 0
|
||||
img4.jpg 493.292000 351.660000 0
|
||||
img4.jpg 535.927000 352.151000 0
|
||||
img4.jpg 576.977000 352.415000 0
|
||||
img4.jpg 317.523000 394.612000 0
|
||||
img4.jpg 361.653000 393.122000 0
|
||||
img4.jpg 405.486000 393.690000 0
|
||||
img4.jpg 449.094000 393.107000 0
|
||||
img4.jpg 490.867000 393.069000 0
|
||||
img4.jpg 533.174000 393.251000 0
|
||||
img4.jpg 573.450000 392.904000 0
|
||||
img7.jpg - - -
|
||||
img8.jpg - - -
|
||||
171
test-resources/calibrationSquaresImg/piCam/960_720_1/corners.vnl
Normal file
@@ -0,0 +1,171 @@
|
||||
## generated with mrgingham --jobs 4 --gridn 7 img10.jpg img11.jpg img12.jpg img13.jpg img14.jpg img15.jpg img16.jpg img17.jpg img18.jpg img19.jpg img1.jpg img20.jpg img21.jpg img22.jpg img23.jpg img24.jpg img25.jpg img2.jpg img3.jpg img4.jpg img5.jpg img6.jpg img7.jpg img8.jpg img9.jpg
|
||||
# filename x y level
|
||||
img10.jpg - - -
|
||||
img12.jpg - - -
|
||||
img13.jpg - - -
|
||||
img11.jpg - - -
|
||||
img14.jpg - - -
|
||||
img16.jpg 230.852688 45.677636 0
|
||||
img16.jpg 290.588419 43.637631 0
|
||||
img16.jpg 353.551532 40.791532 0
|
||||
img16.jpg 416.562335 37.824245 0
|
||||
img16.jpg 482.521266 35.024665 0
|
||||
img16.jpg 550.680595 31.314791 0
|
||||
img16.jpg 620.486435 27.662542 0
|
||||
img16.jpg 228.101210 107.169967 0
|
||||
img16.jpg 287.668764 105.636131 0
|
||||
img16.jpg 350.608677 104.196417 0
|
||||
img16.jpg 413.726046 102.876996 0
|
||||
img16.jpg 479.587961 100.961492 0
|
||||
img16.jpg 548.288420 98.337218 0
|
||||
img16.jpg 617.491433 96.202379 0
|
||||
img16.jpg 227.317580 170.380526 0
|
||||
img16.jpg 286.573678 169.723367 0
|
||||
img16.jpg 348.871750 169.077797 0
|
||||
img16.jpg 412.298791 168.400691 0
|
||||
img16.jpg 476.736521 167.424006 0
|
||||
img16.jpg 545.646695 166.382103 0
|
||||
img16.jpg 615.060574 164.978558 0
|
||||
img16.jpg 224.988291 232.652880 0
|
||||
img16.jpg 284.444268 233.052725 0
|
||||
img16.jpg 347.369088 233.269971 0
|
||||
img16.jpg 410.603748 233.382944 0
|
||||
img16.jpg 474.877702 233.449657 0
|
||||
img16.jpg 542.719183 233.403859 0
|
||||
img16.jpg 612.559443 233.450298 0
|
||||
img16.jpg 224.176494 293.917407 0
|
||||
img16.jpg 283.560852 295.883657 0
|
||||
img16.jpg 344.909937 296.351158 0
|
||||
img16.jpg 407.748291 297.773360 0
|
||||
img16.jpg 473.420095 299.484169 0
|
||||
img16.jpg 540.155696 299.594645 0
|
||||
img16.jpg 610.001831 302.013660 0
|
||||
img16.jpg 222.765937 356.286965 0
|
||||
img16.jpg 281.624273 358.045270 0
|
||||
img16.jpg 344.401747 359.708573 0
|
||||
img16.jpg 407.178146 362.510875 0
|
||||
img16.jpg 470.802112 364.398990 0
|
||||
img16.jpg 538.976097 366.428889 0
|
||||
img16.jpg 608.179152 368.768331 0
|
||||
img16.jpg 221.298532 417.483438 0
|
||||
img16.jpg 280.640228 420.040118 0
|
||||
img16.jpg 341.602139 422.765487 0
|
||||
img16.jpg 404.672431 425.770751 0
|
||||
img16.jpg 470.190240 428.866733 0
|
||||
img16.jpg 536.654210 431.757569 0
|
||||
img16.jpg 605.530880 434.718486 0
|
||||
img17.jpg - - -
|
||||
img15.jpg - - -
|
||||
img18.jpg - - -
|
||||
img1.jpg - - -
|
||||
img20.jpg - - -
|
||||
img19.jpg - - -
|
||||
img21.jpg - - -
|
||||
img23.jpg - - -
|
||||
img22.jpg 94.983538 191.554631 0
|
||||
img22.jpg 153.614577 190.963557 0
|
||||
img22.jpg 214.504831 189.801771 0
|
||||
img22.jpg 275.163494 188.729227 0
|
||||
img22.jpg 335.692361 188.261850 0
|
||||
img22.jpg 398.653076 187.468366 0
|
||||
img22.jpg 461.538759 185.425839 0
|
||||
img22.jpg 90.898174 249.378532 0
|
||||
img22.jpg 149.890629 248.474019 0
|
||||
img22.jpg 212.238288 248.464675 0
|
||||
img22.jpg 272.692903 248.378072 0
|
||||
img22.jpg 335.315339 248.340460 0
|
||||
img22.jpg 397.425010 247.241587 0
|
||||
img22.jpg 460.113872 246.273172 0
|
||||
img22.jpg 88.271128 308.883542 0
|
||||
img22.jpg 148.111111 308.903030 0
|
||||
img22.jpg 209.528269 308.368223 0
|
||||
img22.jpg 272.122948 308.566463 0
|
||||
img22.jpg 333.255342 308.516094 0
|
||||
img22.jpg 396.217537 308.400373 0
|
||||
img22.jpg 458.998879 308.298711 0
|
||||
img22.jpg 85.549180 369.109836 0
|
||||
img22.jpg 145.604505 368.636110 0
|
||||
img22.jpg 207.843879 368.708637 0
|
||||
img22.jpg 269.426938 368.718688 0
|
||||
img22.jpg 332.511083 368.696887 0
|
||||
img22.jpg 395.694279 368.662189 0
|
||||
img22.jpg 458.724238 368.692204 0
|
||||
img22.jpg 83.324617 430.237009 0
|
||||
img22.jpg 143.464362 430.395213 0
|
||||
img22.jpg 205.934830 430.915077 0
|
||||
img22.jpg 268.120193 431.258451 0
|
||||
img22.jpg 331.145142 431.202329 0
|
||||
img22.jpg 395.505179 431.334136 0
|
||||
img22.jpg 458.594962 431.563843 0
|
||||
img22.jpg 80.427088 491.525255 0
|
||||
img22.jpg 140.407319 491.866495 0
|
||||
img22.jpg 203.493586 492.298204 0
|
||||
img22.jpg 266.502687 493.153395 0
|
||||
img22.jpg 329.540655 493.438226 0
|
||||
img22.jpg 394.060910 493.977069 0
|
||||
img22.jpg 458.576923 494.657254 0
|
||||
img22.jpg 78.230002 554.336896 0
|
||||
img22.jpg 138.799033 555.027797 0
|
||||
img22.jpg 201.690901 555.788772 0
|
||||
img22.jpg 265.045455 556.634769 0
|
||||
img22.jpg 329.438927 557.467018 0
|
||||
img22.jpg 392.902428 557.683931 0
|
||||
img22.jpg 458.234614 557.851508 0
|
||||
img24.jpg - - -
|
||||
img3.jpg 375.342835 136.998277 0
|
||||
img3.jpg 434.057516 131.808345 0
|
||||
img3.jpg 495.305888 126.738275 0
|
||||
img3.jpg 560.364821 120.983028 0
|
||||
img3.jpg 626.983524 114.899674 0
|
||||
img3.jpg 698.646341 108.293485 0
|
||||
img3.jpg 773.638004 101.614613 0
|
||||
img3.jpg 373.353018 197.423392 0
|
||||
img3.jpg 431.352319 193.725590 0
|
||||
img3.jpg 493.723975 189.762005 0
|
||||
img3.jpg 557.749077 185.410162 0
|
||||
img3.jpg 626.204480 181.344086 0
|
||||
img3.jpg 698.555333 176.339163 0
|
||||
img3.jpg 773.225717 171.736577 0
|
||||
img3.jpg 371.364100 257.700753 0
|
||||
img3.jpg 428.934194 255.808344 0
|
||||
img3.jpg 491.401929 254.033762 0
|
||||
img3.jpg 556.084802 251.522082 0
|
||||
img3.jpg 623.939787 248.292497 0
|
||||
img3.jpg 697.030951 245.615081 0
|
||||
img3.jpg 773.206566 242.407319 0
|
||||
img3.jpg 369.222475 319.052254 0
|
||||
img3.jpg 428.045774 317.745414 0
|
||||
img3.jpg 489.579236 317.512243 0
|
||||
img3.jpg 554.593832 317.152788 0
|
||||
img3.jpg 623.120840 315.677892 0
|
||||
img3.jpg 695.965810 314.573428 0
|
||||
img3.jpg 772.324664 314.552466 0
|
||||
img3.jpg 368.363534 380.504822 0
|
||||
img3.jpg 425.651057 380.679758 0
|
||||
img3.jpg 488.723907 381.062698 0
|
||||
img3.jpg 552.911222 382.782819 0
|
||||
img3.jpg 621.428571 383.677943 0
|
||||
img3.jpg 695.473001 383.910764 0
|
||||
img3.jpg 771.833725 385.933615 0
|
||||
img3.jpg 365.431290 441.297896 0
|
||||
img3.jpg 424.516816 443.794381 0
|
||||
img3.jpg 486.589463 446.591610 0
|
||||
img3.jpg 551.710297 449.411885 0
|
||||
img3.jpg 620.795019 452.041878 0
|
||||
img3.jpg 695.052279 455.202786 0
|
||||
img3.jpg 770.902807 457.918805 0
|
||||
img3.jpg 364.619850 504.218022 0
|
||||
img3.jpg 422.691676 508.208675 0
|
||||
img3.jpg 485.547412 512.388199 0
|
||||
img3.jpg 551.785073 516.002626 0
|
||||
img3.jpg 620.657118 521.103367 0
|
||||
img3.jpg 694.273407 525.069776 0
|
||||
img3.jpg 770.430863 529.703696 0
|
||||
img25.jpg - - -
|
||||
img2.jpg - - -
|
||||
img4.jpg - - -
|
||||
img7.jpg - - -
|
||||
img5.jpg - - -
|
||||
img6.jpg - - -
|
||||
img8.jpg - - -
|
||||
img9.jpg - - -
|
||||