VisionMaster标定板像素标定,测量尺寸以及opencv/C++实现

1.VM的像素标定流程很简单、如下:

2.拿到标定结果对将图像尺寸转换成物理尺寸

使用了两种方法进行转换,实际证明单位转换更加准确。

使用opencv/c++进行标定,我的圆标定板的直径是7.5mm,圆心之间的距离是15.0mm

简单的像素标定:

cpp 复制代码
void xsCeli::calculatePixelEquivalent(const cv::Mat& src, float real_distance_mm)
{
    // 加载图像
    //cv::Mat src = cv::imread("circle_pattern.jpg");
    if (src.empty()) {
        std::cerr << "Could not load image!" << std::endl;
        return;
    }

    cv::Mat gray;
    cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);  // 转灰度图

    // 霍夫变换检测圆
    std::vector<cv::Vec3f> circles;
    cv::HoughCircles(gray, circles, cv::HOUGH_GRADIENT,
        1,             // dp 分辨率比例
        50,            // minDist 圆心最小距离
        100, 30,       // Canny 边缘检测阈值
        10, 100);      // 半径范围

    // 只处理前两个圆(假设是已知间距的两个圆)
    if (circles.size() < 2) {
        std::cerr << "Not enough circles detected!" << std::endl;
        return;
    }

    // 提取前两个圆的圆心坐标
    cv::Point2f p1(circles[0][0], circles[0][1]);
    cv::Point2f p2(circles[1][0], circles[1][1]);

    // 计算像素距离
    float pixel_distance = cv::norm(p1 - p2);

    // 已知这两个圆的实际物理距离(单位:mm)
    //float real_distance_mm = 15.0;  // 举例:20mm

    // 像素当量:mm per pixel
    float mm_per_pixel = real_distance_mm / pixel_distance;

    std::cout << "Pixel distance: " << pixel_distance << " pixels" << std::endl;
    std::cout << "Real distance: " << real_distance_mm << " mm" << std::endl;
    std::cout << "Scale: " << mm_per_pixel << " mm/pixel" << std::endl;

    // 可视化结果
    for (size_t i = 0; i < circles.size(); ++i) {
        cv::Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
        int radius = cvRound(circles[i][2]);
        cv::circle(src, center, radius, cv::Scalar(0, 255, 0), 2);
        cv::circle(src, center, 2, cv::Scalar(0, 0, 255), 3);  // 圆心
    }

    cv::imshow("Detected Circles", src);
    cv::waitKey(0);

    return;
}

我使用的银行卡进行的测试、

银行卡的长度通常是固定的,遵循国际标准ISO/IEC 7810 ID-1,这是针对识别卡物理特性的标准。根据这一标准,银行卡(包括信用卡和借记卡)的尺寸应为:

  • 长度:85.60毫米(约3.37英寸)
  • 宽度:53.98毫米(约2.13英寸)
  • 厚度:0.76毫米(约0.03英寸)
cpp 复制代码
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>

// 全局变量:标定系数(像素/毫米)
double pixelToMm = 24.066;

// 函数声明
cv::Mat preprocessImage(const cv::Mat& input);
std::vector<cv::Point> findTargetContour(const cv::Mat& processedImg);
cv::RotatedRect getBoundingRect(const std::vector<cv::Point>& contour);
double calculateWidth(const cv::RotatedRect& rect);
void calibrate(const cv::Mat& calibImg, double knownWidth);

int main() {

    // 加载图像
	//cv::Mat src = cv::imread("circle_pattern.jpg");
	//if (src.empty()) {
	//	std::cerr << "Could not load image!" << std::endl;
	//	return -1;
	//}
	//xsCeli celi;
	//celi.calculatePixelEquivalent(src, 15.0f); // 假设实际距离为15mm
	//return 0;

    // 1. 读取图像(替换为实际相机获取或文件读取)
    cv::Mat image = cv::imread("23.jpg");
    if (image.empty()) {
        std::cerr << "无法读取图像!" << std::endl;
        return -1;
    }
    	// 设置最大显示宽度和高度
	int max_width = 800;
	int max_height = 600;

	cv::Mat resized;
	double scale = std::min(max_width / (double)image.cols, max_height / (double)image.rows);
	if (scale < 1.0) { // 只有当图像大于 max_width/max_height 时才缩放
		cv::resize(image, resized, cv::Size(), scale, scale, cv::INTER_AREA);
	}
	else {
		resized = image; // 否则直接显示原图
	}
	image = resized; // 使用缩放后的图像

    // 2. 图像预处理
    cv::Mat processedImg = preprocessImage(image);

    // 3. 提取目标轮廓(假设已完成标定)
    std::vector<cv::Point> targetContour = findTargetContour(processedImg);
    if (targetContour.empty()) {
        std::cerr << "未找到目标轮廓!" << std::endl;
        return -1;
    }

    // 4. 获取最小包围矩形
    cv::RotatedRect boundingRect = getBoundingRect(targetContour);


    // 5. 计算宽度(基于标定系数)
    double widthMm = calculateWidth(boundingRect);

	 widthMm = widthMm / scale;
    std::cout << "目标产品宽度:" << widthMm << " 毫米" << std::endl;

    // 6. 可视化结果
    cv::Mat resultImg = image.clone();

    // 1. 检查resultImg是否有效
    if (resultImg.empty()) {
        std::cerr << "Error: resultImg is empty!" << std::endl;
        return -1;
    }

    // 2. 检查targetContour是否有效
    if (targetContour.empty()) {
        std::cerr << "Error: targetContour is empty!" << std::endl;
        return -1;
    }

    // 3. 输出轮廓信息(可选)
    std::cout << "Drawing contour with " << targetContour.size() << " points" << std::endl;

    cv::drawContours(resultImg, std::vector<std::vector<cv::Point>>{ targetContour }, 0, cv::Scalar(0, 255, 0), 2);

    cv::ellipse(resultImg, boundingRect, cv::Scalar(0, 0, 255), 2);

    // 标注宽度
    cv::Point textPos(boundingRect.center.x - 50, boundingRect.center.y - 50);
    std::string widthText = "Width: " + std::to_string(widthMm) + " mm";
    cv::putText(resultImg, widthText, textPos, cv::FONT_HERSHEY_SIMPLEX, 0.7,
        cv::Scalar(255, 0, 0), 2);


    // 1. 画轮廓
    std::vector<std::vector<cv::Point>> contours{ targetContour };
    cv::drawContours(resultImg, contours, 0, cv::Scalar(0, 255, 0), 2);  // 绿色轮廓

    // 2. 获取并画旋转矩形
    cv::Point2f vertices[4];
    boundingRect.points(vertices);
    for (int i = 0; i < 4; ++i) {
        cv::line(resultImg, vertices[i], vertices[(i + 1) % 4], cv::Scalar(0, 0, 255), 2);  // 红色矩形
    }


    cv::imshow("测量结果", resultImg);
    cv::waitKey(0);
    return 0;
}

// 图像预处理:灰度转换、滤波、二值化
cv::Mat preprocessImage(const cv::Mat& input) {
    cv::Mat gray, blurred, binary;

    // 1. 灰度转换
    cv::cvtColor(input, gray, cv::COLOR_BGR2GRAY);

    // 2. 高斯滤波去噪(根据实际噪声调整参数)
    cv::GaussianBlur(gray, blurred, cv::Size(5, 5), 0);

    // 3. 自适应阈值二值化(适应不均匀光照)
    cv::adaptiveThreshold(blurred, binary, 255, cv::ADAPTIVE_THRESH_GAUSSIAN_C,
        cv::THRESH_BINARY_INV, 11, 2);

    // 4. 形态学操作:闭运算填充小孔,开运算去除噪点
    cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));
    cv::morphologyEx(binary, binary, cv::MORPH_CLOSE, kernel);
    cv::morphologyEx(binary, binary, cv::MORPH_OPEN, kernel);
	//cv::imshow("Processed Image", binary);
   // cv::waitKey(0);
    return binary;
}

// 提取目标轮廓(筛选带倒圆角的矩形)
std::vector<cv::Point> findTargetContour(const cv::Mat& processedImg) {
    std::vector<std::vector<cv::Point>> contours;
    std::vector<cv::Vec4i> hierarchy;

    // 1. 查找轮廓
    cv::findContours(processedImg, contours, hierarchy, cv::RETR_EXTERNAL,
        cv::CHAIN_APPROX_SIMPLE);

    if (contours.empty()) return {};

    // 2. 筛选目标轮廓(根据面积、形状因子筛选)
    std::vector<cv::Point> targetContour;
    double maxArea = 0;
    std::cout << "目标轮廓个数:" << contours.size() << std::endl;
    std::sort(contours.begin(), contours.end(),
        [](const auto& a, const auto& b) {
            return cv::contourArea(a) > cv::contourArea(b);
        });
    //for (const auto& contour : contours) {
    //    double area = cv::contourArea(contour);
    //    if (area < 1000) continue; // 过滤小面积噪声

    //    // 计算轮廓近似(关键:调整epsilon适应倒圆角)
    //    std::vector<cv::Point> approx;
    //    double epsilon = 0.02 * cv::arcLength(contour, true); // 2%的轮廓周长
    //    cv::approxPolyDP(contour, approx, epsilon, true);

    //    // 带倒圆角的矩形近似后应为8个顶点(4个边+4个圆角弧段)
    //    if (approx.size() > 6 && approx.size() < 12) {
    //        double areaRatio = area / (cv::boundingRect(contour).width *
    //            cv::boundingRect(contour).height);
    //        if (areaRatio > 0.8) { // 面积比接近矩形
    //            targetContour = contour;
    //            maxArea = area;
    //        }
    //    }
    //}

    targetContour = contours[0];
    return targetContour;
}

// 获取最小包围旋转矩形
cv::RotatedRect getBoundingRect(const std::vector<cv::Point>& contour) {
    return cv::minAreaRect(contour);
}

// 计算宽度(基于标定系数)
double calculateWidth(const cv::RotatedRect& rect) {
    // 旋转矩形的宽高可能需要根据角度判断哪条是宽度
    double width = rect.size.width;
    double height = rect.size.height;

    // 银行卡宽度通常小于长度,假设宽度是较短边
    return (width < height ? width : height) / pixelToMm;
}

// 标定函数(使用已知宽度的标准物)
void calibrate(const cv::Mat& calibImg, double knownWidth) {
    cv::Mat processed = preprocessImage(calibImg);
    std::vector<cv::Point> calibContour = findTargetContour(processed);

    if (calibContour.empty()) {
        std::cerr << "标定图像轮廓提取失败!" << std::endl;
        return;
    }

    cv::RotatedRect calibRect = cv::minAreaRect(calibContour);
    double calibPixelWidth = (calibRect.size.width < calibRect.size.height
        ? calibRect.size.width : calibRect.size.height);

    // 计算像素/毫米系数
    pixelToMm = calibPixelWidth / knownWidth;
    std::cout << "标定完成,像素/毫米系数:" << pixelToMm << std::endl;
}

得到以下结果:

测量结果有一定的误差、可以验证这样写是没有问题的。

这是标定图片与银行卡的图片连接,如有需要自取。

链接:https://pan.quark.cn/s/57d9f8a7508f

相关推荐
企销客CRM1 分钟前
企微CRM系统中的任务分配与效率提升技巧
大数据·数据库·人工智能·数据分析·企业微信
Tony沈哲16 分钟前
基于 MODNet 和 Face Parsing 实现高质量人像分割与换发色
深度学习·opencv·算法
Baihai_IDP38 分钟前
为什么说大家低估了 AI 的实际使用规模?实际情况如何?
人工智能·llm·aigc
三花AI42 分钟前
HeyGen AI 三步创建产品广告视频
人工智能
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ1 小时前
LoRA、QLoRA是什么
人工智能·深度学习·机器学习
Binary_ey1 小时前
AR/VR显示为何视场受限?OAS对标波导案例来解决
人工智能·软件需求·光学软件
白熊1882 小时前
【深度学习】生成对抗网络(GANs)深度解析:从理论到实践的革命性生成模型
人工智能·深度学习·生成对抗网络
老周聊大模型2 小时前
并行性能提升300%!LangGraph如何重塑大模型任务编排
人工智能·程序员
CoovallyAIHub2 小时前
YOLOv8/v10/v11自动驾驶实测对比:揭秘v11遮挡车辆检测精度提升关键
深度学习·算法·计算机视觉
DeepSeek忠实粉丝2 小时前
微调篇--Transformers多模态数据预处理
人工智能·程序员·llm