// ============================================================================
// 文件名: CalibrationProcessor.cs
// 描述: 标定处理器,实现图像坐标系到世界坐标系的转换
// 功能:
// - 基于多点标定计算透视变换矩阵(支持4点及以上)
// - 像素坐标到世界坐标的转换
// - 标定数据的保存和加载(JSON格式)
// - 从CSV文件导入标定点数据
// 算法: 使用DLT(Direct Linear Transformation)方法求解单应性矩阵
// ============================================================================
using Emgu.CV;
using System.IO;
using System.Text.Json;
namespace XP.Camera.Calibration;
///
/// 九点标定处理器
///
public class CalibrationProcessor
{
public class CalibrationPoint
{
public double PixelX { get; set; }
public double PixelY { get; set; }
public double WorldX { get; set; }
public double WorldY { get; set; }
}
public class CalibrationData
{
public List Points { get; set; } = new List();
public double[] TransformMatrix { get; set; } = new double[9];
public DateTime CalibrationTime { get; set; }
}
private Matrix? _transformMatrix;
///
/// 执行九点标定
///
public bool Calibrate(List points)
{
if (points.Count < 4)
return false;
int n = points.Count;
var A = new Matrix(2 * n, 8);
var b = new Matrix(2 * n, 1);
for (int i = 0; i < n; i++)
{
double u = points[i].PixelX;
double v = points[i].PixelY;
double x = points[i].WorldX;
double y = points[i].WorldY;
A[2 * i, 0] = u;
A[2 * i, 1] = v;
A[2 * i, 2] = 1;
A[2 * i, 3] = 0;
A[2 * i, 4] = 0;
A[2 * i, 5] = 0;
A[2 * i, 6] = -x * u;
A[2 * i, 7] = -x * v;
b[2 * i, 0] = x;
A[2 * i + 1, 0] = 0;
A[2 * i + 1, 1] = 0;
A[2 * i + 1, 2] = 0;
A[2 * i + 1, 3] = u;
A[2 * i + 1, 4] = v;
A[2 * i + 1, 5] = 1;
A[2 * i + 1, 6] = -y * u;
A[2 * i + 1, 7] = -y * v;
b[2 * i + 1, 0] = y;
}
var h = new Matrix(8, 1);
CvInvoke.Solve(A, b, h, Emgu.CV.CvEnum.DecompMethod.Svd);
_transformMatrix = new Matrix(3, 3);
_transformMatrix[0, 0] = h[0, 0];
_transformMatrix[0, 1] = h[1, 0];
_transformMatrix[0, 2] = h[2, 0];
_transformMatrix[1, 0] = h[3, 0];
_transformMatrix[1, 1] = h[4, 0];
_transformMatrix[1, 2] = h[5, 0];
_transformMatrix[2, 0] = h[6, 0];
_transformMatrix[2, 1] = h[7, 0];
_transformMatrix[2, 2] = 1.0;
return true;
}
///
/// 像素坐标转世界坐标
///
public System.Drawing.PointF PixelToWorld(System.Drawing.PointF pixel)
{
if (_transformMatrix == null)
return pixel;
double u = pixel.X;
double v = pixel.Y;
double w = _transformMatrix[2, 0] * u + _transformMatrix[2, 1] * v + _transformMatrix[2, 2];
double x = (_transformMatrix[0, 0] * u + _transformMatrix[0, 1] * v + _transformMatrix[0, 2]) / w;
double y = (_transformMatrix[1, 0] * u + _transformMatrix[1, 1] * v + _transformMatrix[1, 2]) / w;
return new System.Drawing.PointF((float)x, (float)y);
}
///
/// 保存标定文件
///
public void SaveCalibration(string filePath, List points)
{
var data = new CalibrationData
{
Points = points,
TransformMatrix = new double[9],
CalibrationTime = DateTime.Now
};
if (_transformMatrix != null)
{
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
data.TransformMatrix[i * 3 + j] = _transformMatrix[i, j];
}
var json = JsonSerializer.Serialize(data, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(filePath, json);
}
///
/// 加载标定文件
///
public bool LoadCalibration(string filePath)
{
if (!File.Exists(filePath))
return false;
try
{
var json = File.ReadAllText(filePath);
var data = JsonSerializer.Deserialize(json);
if (data == null || data.TransformMatrix == null || data.TransformMatrix.Length != 9)
return false;
_transformMatrix = new Matrix(3, 3);
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
_transformMatrix[i, j] = data.TransformMatrix[i * 3 + j];
return true;
}
catch
{
return false;
}
}
///
/// 从CSV文件加载标定点
///
public List LoadPointsFromCsv(string filePath)
{
var points = new List();
if (!File.Exists(filePath))
return points;
var lines = File.ReadAllLines(filePath);
for (int i = 0; i < lines.Length; i++)
{
if (i == 0 && (lines[i].Contains("PixelX") || lines[i].Contains("像素"))) continue;
var parts = lines[i].Split(',');
if (parts.Length >= 4)
{
if (double.TryParse(parts[0].Trim(), out double px) &&
double.TryParse(parts[1].Trim(), out double py) &&
double.TryParse(parts[2].Trim(), out double wx) &&
double.TryParse(parts[3].Trim(), out double wy))
{
points.Add(new CalibrationPoint
{
PixelX = px,
PixelY = py,
WorldX = wx,
WorldY = wy
});
}
}
}
return points;
}
}