求助:将Python OpenCV中的Procrustes变换代码转换为Java
Hey there, I’ve spent some time working through this exact problem—converting that Procrustes-based affine transformation function to Java. The core logic (centroid subtraction, scaling, SVD for rotation) translates directly, but we’ll use Apache Commons Math for matrix operations since Java’s standard library doesn’t have built-in SVD or robust matrix utilities.
Step-by-Step Implementation
First, add the Apache Commons Math dependency to your project (if using Maven):
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-math3</artifactId> <version>3.6.1</version> </dependency>
Here’s the full Java implementation, mirroring the Python logic you described:
import org.apache.commons.math3.linear.*; public class ProcrustesAlignment { public static RealMatrix transformationFromPoints(RealMatrix points1, RealMatrix points2) { // Validate input dimensions (expect 68x2 matrices) if (points1.getRowDimension() != 68 || points1.getColumnDimension() != 2 || points2.getRowDimension() != 68 || points2.getColumnDimension() != 2) { throw new IllegalArgumentException("Input points must be 68x2 matrices"); } // Step 1: Calculate centroids of both point sets RealVector centroid1 = calculateCentroid(points1); RealVector centroid2 = calculateCentroid(points2); // Step 2: Shift points to be relative to their centroids (center at origin) RealMatrix centered1 = centerPoints(points1, centroid1); RealMatrix centered2 = centerPoints(points2, centroid2); // Step 3: Normalize point sets by their root-mean-square scale double scale1 = calculateScale(centered1); double scale2 = calculateScale(centered2); RealMatrix normalized1 = centered1.scalarMultiply(1 / scale1); RealMatrix normalized2 = centered2.scalarMultiply(1 / scale2); // Step 4: Use SVD to find optimal rotation matrix SingularValueDecomposition svd = new SingularValueDecomposition(normalized2.transpose().multiply(normalized1)); RealMatrix u = svd.getU(); RealMatrix vt = svd.getVT(); RealMatrix rotation = u.multiply(vt); // Ensure rotation has positive determinant (avoid reflection) if (rotation.getDeterminant() < 0) { RealMatrix v = svd.getV(); v.setColumn(1, v.getColumnVector(1).mapMultiply(-1)); rotation = u.multiply(v.transpose()); } // Step 5: Compute overall scaling factor double scale = scale2 / scale1; // Step 6: Calculate translation vector T = centroid2 - s*R*centroid1 RealVector translation = centroid2.subtract(rotation.multiply(centroid1).mapMultiply(scale)); // Assemble 3x3 affine transformation matrix [s*R | T; 0 0 1] RealMatrix transform = MatrixUtils.createRealMatrix(3, 3); // Fill scaled rotation portion for (int i = 0; i < 2; i++) { for (int j = 0; j < 2; j++) { transform.setEntry(i, j, rotation.getEntry(i, j) * scale); } } // Fill translation portion transform.setEntry(0, 2, translation.getEntry(0)); transform.setEntry(1, 2, translation.getEntry(1)); // Homogeneous coordinate row transform.setEntry(2, 2, 1.0); return transform; } private static RealVector calculateCentroid(RealMatrix points) { int numPoints = points.getRowDimension(); double xMean = points.getColumnVector(0).getSum() / numPoints; double yMean = points.getColumnVector(1).getSum() / numPoints; return MatrixUtils.createRealVector(new double[]{xMean, yMean}); } private static RealMatrix centerPoints(RealMatrix points, RealVector centroid) { RealMatrix centered = points.copy(); for (int i = 0; i < points.getRowDimension(); i++) { centered.setRowVector(i, centered.getRowVector(i).subtract(centroid)); } return centered; } private static double calculateScale(RealMatrix centeredPoints) { // Compute root mean square of all point coordinates double sumSquares = 0; int numPoints = centeredPoints.getRowDimension(); for (int i = 0; i < numPoints; i++) { sumSquares += centeredPoints.getRowVector(i).getNormSq(); } return Math.sqrt(sumSquares / numPoints); } // Example usage public static void main(String[] args) { // Populate with your actual 68x2 point data RealMatrix points1 = MatrixUtils.createRealMatrix(68, 2); RealMatrix points2 = MatrixUtils.createRealMatrix(68, 2); RealMatrix transform = transformationFromPoints(points1, points2); System.out.println("Affine Transformation Matrix:"); System.out.println(transform); } }
Key Details
- Centroid Shifting: We move both point sets to be centered at the origin to eliminate translation differences before computing rotation.
- Normalization: Scaling each point set to have a root-mean-square size of 1 ensures we isolate rotation from scale differences.
- Rotation via SVD: The optimal rotation comes from multiplying the U and V^T matrices from the SVD of
normalized2^T * normalized1. We flip a column in V if the determinant is negative to avoid reflection matrices. - Affine Matrix: The final 3x3 matrix combines scaled rotation, translation, and a homogeneous row to support standard 2D affine transformation operations.
This implementation will produce the same result as your Python function, minimizing the sum of squared distances sum ||s*R*p1,i + T - p2,i||².
内容的提问来源于stack exchange,提问作者Oga Emma

