C# OpenCvSharp摄像头畸形矫正。
·
public class CameraAutoCalibrator
{
#region 可配置参数【根据你的硬件修改,必改项】
public OpenCvSharp.Size ChessboardSize = new OpenCvSharp.Size(9, 6); // 棋盘格内角点行列数 (宽x高)
public float ChessboardSquareSize = 20.0f; // 单个棋盘格物理边长,单位:毫米mm
public bool IsDistortionFreeLens = true; // true=工业无畸变镜头/远心镜头,false=普通畸变镜头
public double Pixel2MmScale { get; private set; } = 0; // 核心转换系数:mm/像素
#endregion
#region 标定核心参数
public Mat CameraMatrix { get; private set; } = new Mat(3, 3, MatType.CV_64FC1);
public Mat DistortionCoeffs { get; private set; } = new Mat();
public bool IsCalibrated { get; private set; } = false;
public int WidthOffsetPixel { get; private set; }= 0;
public int HighOffsetPixel { get; private set; } = 0;
#endregion
#region 你要求的DPI属性【精准换算,无单位误差】
public double ImageDpi
{
get
{
if (Pixel2MmScale <= 0) return 0;
return Math.Round(25.4 / Pixel2MmScale, 2); // DPI=25.4mm ÷ 每像素毫米数
}
}
#endregion
/// <summary>
/// ✅ 全自动标定主函数 - 无任何编译报错
/// </summary>
public double AutoCalibrate(List<string> calibImagePaths)
{
List<Mat> objPointMats = new List<Mat>();
List<Mat> imgPointMats = new List<Mat>();
OpenCvSharp.Size imageSize = new OpenCvSharp.Size();
int cornerCount = ChessboardSize.Width * ChessboardSize.Height;
// 生成棋盘格3D世界坐标Mat (CV_32FC3 XYZ)
Mat singleObjMat = new Mat(cornerCount, 1, MatType.CV_32FC3);
float[] objData = new float[cornerCount * 3];
int dataIdx = 0;
for (int i = 0; i < ChessboardSize.Height; i++)
{
for (int j = 0; j < ChessboardSize.Width; j++)
{
objData[dataIdx++] = j * ChessboardSquareSize;
objData[dataIdx++] = i * ChessboardSquareSize;
objData[dataIdx++] = 0.0f;
}
}
// ✅ 适配你的SetArray<T>(params T[] data) 无行列参数
singleObjMat.SetArray<float>(objData);
// 遍历标定图,角点检测+亚像素优化
foreach (var imgPath in calibImagePaths)
{
if (!File.Exists(imgPath)) continue;
using (Mat grayImg = Cv2.ImRead(imgPath, ImreadModes.Grayscale))
{
if (grayImg.Empty()) continue;
imageSize = grayImg.Size();
bool findCorners = Cv2.FindChessboardCorners(grayImg, ChessboardSize, out Point2f[] corners,
ChessboardFlags.AdaptiveThresh | ChessboardFlags.NormalizeImage | ChessboardFlags.FastCheck);
if (findCorners)
{
// ✅ 你的3参数版亚像素函数 原生调用,无任何修改
Cv2.Find4QuadCornerSubpix(grayImg, corners, new OpenCvSharp.Size(5, 5));
// 生成2D像素坐标Mat (CV_32FC2 XY)
Mat singleImgMat = new Mat(cornerCount, 1, MatType.CV_32FC2);
float[] imgData = new float[cornerCount * 2];
dataIdx = 0;
foreach (var corner in corners)
{
imgData[dataIdx++] = corner.X;
imgData[dataIdx++] = corner.Y;
}
singleImgMat.SetArray<float>(imgData);
objPointMats.Add(singleObjMat.Clone());
imgPointMats.Add(singleImgMat);
}
}
}
if (objPointMats.Count < 3)
throw new Exception("标定失败:有效棋盘格图像不足3张,工业建议10-20张不同位姿");
// ✅ 完美匹配你的CalibrateCamera重载原型
Mat[] rvecs;
Mat[] tvecs;
double reprojError = Cv2.CalibrateCamera(
objectPoints: objPointMats,
imagePoints: imgPointMats,
imageSize: imageSize,
cameraMatrix: CameraMatrix,
distCoeffs: DistortionCoeffs,
rvecs: out rvecs,
tvecs: out tvecs,
flags: CalibrationFlags.None,
criteria: new TermCriteria(CriteriaTypes.Eps | CriteriaTypes.MaxIter, 30, 0.001)
);
// 计算核心转换系数
CalculatePixel2MmScale();
// 无畸变镜头:清空畸变系数,跳过矫正流程,提升效率
if (IsDistortionFreeLens)
DistortionCoeffs = new Mat();
IsCalibrated = true;
return Math.Round(reprojError, 3);
}
/// <summary>
/// ✅ 核心修改:完美适配你的GetOptimalNewCameraMatrix完整7参数原型
/// 畸变矫正函数 - 无畸变镜头直接返回原图,无性能损耗
/// </summary>
public Mat CorrectDistortion(Mat srcImage)
{
// 未标定 或 无畸变镜头,直接返回原图克隆,避免引用冲突
if (!IsCalibrated || IsDistortionFreeLens)
return srcImage.Clone();
Mat dstImage = new Mat();
Rect validPixROI; // 新增:你的原型必填的有效像素区域输出参数
// ✅ 精准匹配你的GetOptimalNewCameraMatrix全部参数,无任何缺失
Mat optimalCamMat = Cv2.GetOptimalNewCameraMatrix(
cameraMatrix: CameraMatrix,
distCoeffs: DistortionCoeffs,
imageSize: srcImage.Size(),
alpha: 0, // alpha=0 裁剪畸变黑边,输出无黑边的有效图像,工业首选
newImgSize: srcImage.Size(), // 矫正后图像尺寸与原图一致
validPixROI: out validPixROI,
centerPrincipalPoint: false
);
// 执行畸变矫正
Cv2.Undistort(srcImage, dstImage, CameraMatrix, DistortionCoeffs, optimalCamMat);
return dstImage;
}
/// <summary>
/// 计算像素→毫米 核心转换系数
/// </summary>
private void CalculatePixel2MmScale()
{
double fx = CameraMatrix.At<double>(0, 0);
double fy = CameraMatrix.At<double>(1, 1);
Pixel2MmScale = ChessboardSquareSize / ((fx + fy) / 2 * 0.001);
Pixel2MmScale = Math.Round(Pixel2MmScale, 6);
}
/// <summary>
/// 像素长度 → 实际物理尺寸 (mm) 工业测量核心方法
/// </summary>
public double PixelToMm(double pixelLength)
{
if (!IsCalibrated) throw new Exception("请先完成相机标定,再进行尺寸测量!");
return Math.Round(pixelLength * Pixel2MmScale, 3);
}
/// <summary>
/// 实际物理尺寸 (mm) → 像素长度
/// </summary>
public double MmToPixel(double mmLength)
{
if (!IsCalibrated) throw new Exception("请先完成相机标定,再进行尺寸转换!");
return Math.Round(mmLength / Pixel2MmScale, 0);
}
/// <summary>
/// 保存标定参数到XML文件,开机直接加载无需重复标定【工业项目必用】
/// </summary>
public void SaveCalibParams(string savePath)
{
if (!IsCalibrated) return;
using (FileStorage fs = new FileStorage(savePath, Modes.Write))
{
fs.Write("CameraMatrix", CameraMatrix);
fs.Write("DistortionCoeffs", DistortionCoeffs);
fs.Write("Pixel2MmScale", Pixel2MmScale);
fs.Write("ImageDpi", ImageDpi);
fs.Write("IsDistortionFreeLens", IsDistortionFreeLens?1:0);
fs.Write("WidthOffsetPixel", WidthOffsetPixel);
fs.Write("HighOffsetPixel", HighOffsetPixel);
}
}
/// <summary>
/// 加载本地标定参数【工业项目必用】
/// </summary>
public void LoadCalibParams(string loadPath)
{
// 校验文件是否存在,不存在直接返回
if (!File.Exists(loadPath)) return;
// ✅ 修正点1:OpenCvSharp4 读取模式的正确写法,替代原FileStorageMode.Read
using (FileStorage fs = new FileStorage(loadPath, Modes.Read))
{
// 读取相机内参矩阵、畸变系数矩阵 写法不变,本身无错误
fs["CameraMatrix"].ReadMat(CameraMatrix);
fs["DistortionCoeffs"].ReadMat(DistortionCoeffs);
// 读取浮点型系数 写法不变,无错误
Pixel2MmScale = fs["Pixel2MmScale"].ReadDouble();
// ✅ 修正点2:读取布尔值的正确替代方案(核心解决ReadBool()报错)
IsDistortionFreeLens = fs["IsDistortionFreeLens"].ReadInt() == 1;
// 标定完成标记
IsCalibrated = true;
WidthOffsetPixel = fs["WidthOffsetPixel"].ReadInt();
HighOffsetPixel = fs["HighOffsetPixel"].ReadInt();
}
}
}
更多推荐
所有评论(0)