VisionMaster标定板像素标定,测量尺寸以及opencv/C++实现
1.VM的像素标定流程很简单、如下:
2.拿到标定结果对将图像尺寸转换成物理尺寸
使用了两种方法进行转换,实际证明单位转换更加准确。
使用opencv/c++进行标定,我的圆标定板的直径是7.5mm,圆心之间的距离是15.0mm
简单的像素标定:
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 pixelfloat 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英寸)
#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