001/* 002 * Copyright (C) Photon Vision. 003 * 004 * This program is free software: you can redistribute it and/or modify 005 * it under the terms of the GNU General Public License as published by 006 * the Free Software Foundation, either version 3 of the License, or 007 * (at your option) any later version. 008 * 009 * This program is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 012 * GNU General Public License for more details. 013 * 014 * You should have received a copy of the GNU General Public License 015 * along with this program. If not, see <https://www.gnu.org/licenses/>. 016 */ 017 018package org.photonvision.vision.calibration; 019 020import com.fasterxml.jackson.annotation.JsonAlias; 021import com.fasterxml.jackson.annotation.JsonCreator; 022import com.fasterxml.jackson.annotation.JsonIgnore; 023import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 024import com.fasterxml.jackson.annotation.JsonProperty; 025import java.util.Arrays; 026import java.util.List; 027import org.opencv.core.Mat; 028import org.opencv.core.MatOfDouble; 029import org.opencv.core.Size; 030import org.photonvision.vision.opencv.ImageRotationMode; 031import org.photonvision.vision.opencv.Releasable; 032 033@JsonIgnoreProperties(ignoreUnknown = true) 034public class CameraCalibrationCoefficients implements Releasable { 035 @JsonProperty("resolution") 036 public final Size unrotatedImageSize; 037 038 @JsonProperty("cameraIntrinsics") 039 public final JsonMatOfDouble cameraIntrinsics; 040 041 @JsonProperty("distCoeffs") 042 @JsonAlias({"distCoeffs", "distCoeffs"}) 043 public final JsonMatOfDouble distCoeffs; 044 045 @JsonProperty("observations") 046 public final List<BoardObservation> observations; 047 048 @JsonProperty("calobjectWarp") 049 public final double[] calobjectWarp; 050 051 @JsonProperty("calobjectSize") 052 public final Size calobjectSize; 053 054 @JsonProperty("calobjectSpacing") 055 public final double calobjectSpacing; 056 057 @JsonProperty("lensmodel") 058 public final CameraLensModel lensmodel; 059 060 /** 061 * Contains all camera calibration data for a particular resolution of a camera. Designed for use 062 * with standard opencv camera calibration matrices. For details on the layout of camera 063 * intrinsics/distortion matrices, see: 064 * https://docs.opencv.org/4.x/d9/d0c/group__calib3d.html#ga3207604e4b1a1758aa66acb6ed5aa65d 065 * 066 * @param resolution The resolution this applies to. We don't assume camera binning or try 067 * rescaling calibration 068 * @param cameraIntrinsics Camera intrinsics parameters matrix, in the standard opencv form. 069 * @param distCoeffs Camera distortion coefficients array. Variable length depending on order of 070 * distortion model 071 * @param calobjectWarp Board deformation parameters, for calibrators that can estimate that. See: 072 * https://mrcal.secretsauce.net/formulation.html#board-deformation 073 * @param observations List of snapshots used to construct this calibration 074 * @param calobjectSize Dimensions of the object used to calibrate, in # of squares in 075 * width/height 076 * @param calobjectSpacing Spacing between adjacent squares, in meters 077 */ 078 @JsonCreator 079 public CameraCalibrationCoefficients( 080 @JsonProperty("resolution") Size resolution, 081 @JsonProperty("cameraIntrinsics") JsonMatOfDouble cameraIntrinsics, 082 @JsonProperty("distCoeffs") JsonMatOfDouble distCoeffs, 083 @JsonProperty("calobjectWarp") double[] calobjectWarp, 084 @JsonProperty("observations") List<BoardObservation> observations, 085 @JsonProperty("calobjectSize") Size calobjectSize, 086 @JsonProperty("calobjectSpacing") double calobjectSpacing, 087 @JsonProperty("lensmodel") CameraLensModel lensmodel) { 088 this.unrotatedImageSize = resolution; 089 this.cameraIntrinsics = cameraIntrinsics; 090 this.distCoeffs = distCoeffs; 091 this.calobjectWarp = calobjectWarp; 092 this.calobjectSize = calobjectSize; 093 this.calobjectSpacing = calobjectSpacing; 094 this.lensmodel = lensmodel; 095 096 // Legacy migration just to make sure that observations is at worst empty and never null 097 if (observations == null) { 098 observations = List.of(); 099 } 100 this.observations = observations; 101 } 102 103 public CameraCalibrationCoefficients rotateCoefficients(ImageRotationMode rotation) { 104 if (rotation == ImageRotationMode.DEG_0) { 105 return this; 106 } 107 Mat rotatedIntrinsics = getCameraIntrinsicsMat().clone(); 108 Mat rotatedDistCoeffs = getDistCoeffsMat().clone(); 109 double cx = getCameraIntrinsicsMat().get(0, 2)[0]; 110 double cy = getCameraIntrinsicsMat().get(1, 2)[0]; 111 double fx = getCameraIntrinsicsMat().get(0, 0)[0]; 112 double fy = getCameraIntrinsicsMat().get(1, 1)[0]; 113 114 // only adjust p1 and p2 the rest are radial distortion coefficients 115 116 double p1 = getDistCoeffsMat().get(0, 2)[0]; 117 double p2 = getDistCoeffsMat().get(0, 3)[0]; 118 119 Size rotatedImageSize = null; 120 121 // A bunch of horrifying opaque rotation black magic. See image-rotation.md for more details. 122 switch (rotation) { 123 case DEG_0: 124 break; 125 case DEG_270_CCW: 126 // FX 127 rotatedIntrinsics.put(0, 0, fy); 128 // FY 129 rotatedIntrinsics.put(1, 1, fx); 130 131 // CX 132 rotatedIntrinsics.put(0, 2, unrotatedImageSize.height - cy); 133 // CY 134 rotatedIntrinsics.put(1, 2, cx); 135 136 // P1 137 rotatedDistCoeffs.put(0, 2, p2); 138 // P2 139 rotatedDistCoeffs.put(0, 3, -p1); 140 141 // The rotated image size is the same as the unrotated image size, but the width and height 142 // are flipped 143 rotatedImageSize = new Size(unrotatedImageSize.height, unrotatedImageSize.width); 144 break; 145 case DEG_180_CCW: 146 // CX 147 rotatedIntrinsics.put(0, 2, unrotatedImageSize.width - cx); 148 // CY 149 rotatedIntrinsics.put(1, 2, unrotatedImageSize.height - cy); 150 151 // P1 152 rotatedDistCoeffs.put(0, 2, -p1); 153 // P2 154 rotatedDistCoeffs.put(0, 3, -p2); 155 156 // The rotated image size is the same as the unrotated image size 157 rotatedImageSize = unrotatedImageSize; 158 break; 159 case DEG_90_CCW: 160 // FX 161 rotatedIntrinsics.put(0, 0, fy); 162 // FY 163 rotatedIntrinsics.put(1, 1, fx); 164 165 // CX 166 rotatedIntrinsics.put(0, 2, cy); 167 // CY 168 rotatedIntrinsics.put(1, 2, unrotatedImageSize.width - cx); 169 170 // P1 171 rotatedDistCoeffs.put(0, 2, -p2); 172 // P2 173 rotatedDistCoeffs.put(0, 3, p1); 174 175 // The rotated image size is the same as the unrotated image size, but the width and height 176 // are flipped 177 rotatedImageSize = new Size(unrotatedImageSize.height, unrotatedImageSize.width); 178 break; 179 } 180 181 JsonMatOfDouble newIntrinsics = JsonMatOfDouble.fromMat(rotatedIntrinsics); 182 183 JsonMatOfDouble newDistCoeffs = JsonMatOfDouble.fromMat(rotatedDistCoeffs); 184 185 rotatedIntrinsics.release(); 186 rotatedDistCoeffs.release(); 187 188 return new CameraCalibrationCoefficients( 189 rotatedImageSize, 190 newIntrinsics, 191 newDistCoeffs, 192 calobjectWarp, 193 observations, 194 calobjectSize, 195 calobjectSpacing, 196 lensmodel); 197 } 198 199 @JsonIgnore 200 public Mat getCameraIntrinsicsMat() { 201 return cameraIntrinsics.getAsMatOfDouble(); 202 } 203 204 @JsonIgnore 205 public MatOfDouble getDistCoeffsMat() { 206 return distCoeffs.getAsMatOfDouble(); 207 } 208 209 @JsonIgnore 210 public double[] getIntrinsicsArr() { 211 return cameraIntrinsics.data; 212 } 213 214 @JsonIgnore 215 public double[] getDistCoeffsArr() { 216 return distCoeffs.data; 217 } 218 219 @JsonIgnore 220 public List<BoardObservation> getPerViewErrors() { 221 return observations; 222 } 223 224 @Override 225 public void release() { 226 cameraIntrinsics.release(); 227 distCoeffs.release(); 228 } 229 230 @Override 231 public String toString() { 232 return "CameraCalibrationCoefficients [resolution=" 233 + unrotatedImageSize 234 + ", cameraIntrinsics=" 235 + cameraIntrinsics 236 + ", distCoeffs=" 237 + distCoeffs 238 + ", observationslen=" 239 + observations.size() 240 + ", calobjectWarp=" 241 + Arrays.toString(calobjectWarp) 242 + "]"; 243 } 244 245 public UICameraCalibrationCoefficients cloneWithoutObservations() { 246 return new UICameraCalibrationCoefficients( 247 unrotatedImageSize, 248 cameraIntrinsics, 249 distCoeffs, 250 calobjectWarp, 251 observations, 252 calobjectSize, 253 calobjectSpacing, 254 lensmodel); 255 } 256}