新版opencv4.7.0开始在主库中就有了aruco码的生成和识别库
要再详细的识别位姿什么的,还是需要到opencv_contrib中

以下代码为自己的测试过程,包括有选定aruco码的字典、生成aruco码、图像仿射投影变换、识别aruco码的参数设置、所有用到的参数保存yaml等

#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int main() {
    // 分别创建两个标记图像
    Mat markerImage1, markerImage2;
    //创建aruco码的字典,必须要识别的类型保持一致
    aruco::Dictionary dictionary = aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);
    //Mat combinedImage = imread("C:\\Users\\Administrator\\Pictures\\combined_markers-1.png",0);
    int size = 40;
    ////Mat bytelist = dictionary.getBitsFromByteList(dictionary.bytesList, dictionary.markerSize);
    ////Mat byte = aruco::Dictionary::getByteListFromBits(dictionary.bytesList);
    // 生成两个不同的标记
    dictionary.generateImageMarker(0, size, markerImage1, 1);    // ID 0, 尺寸200
    dictionary.generateImageMarker(1, size, markerImage2, 1);    // ID 1, 尺寸200

    // 创建一个大的画布来放置两个标记
    Mat combinedImage = Mat(500, 600, CV_8UC1,Scalar(255));

    // 将两个标记放在画布的不同位置
    Rect roi1(50, 150, size, size);    // 第一个标记位置
    Rect roi2(350, 150, size, size);   // 第二个标记位置

    markerImage1.copyTo(combinedImage(roi1));
    markerImage2.copyTo(combinedImage(roi2));

#pragma region 将图片进行仿射变换
    //// 获取图像中心点
    //Point2f center(combinedImage.cols / 2.0f, combinedImage.rows / 2.0f);

    //// 定义旋转角度(45度)和缩放因子
    //double angle = 45.0;  // 旋转角度
    //double scale_x = 1.5; // X轴缩放因子(拉伸)
    //double scale_y = 0.8; // Y轴缩放因子(压缩)
    //// 方法1:使用getRotationMatrix2D构建旋转矩阵,然后添加缩放
    //Mat rotation_matrix = getRotationMatrix2D(center, angle, 1.5);
    //rotation_matrix.at<double>(0, 0) *= scale_x; // a * scale_x
    //rotation_matrix.at<double>(0, 1) *= scale_x; // b * scale_x  
    //rotation_matrix.at<double>(1, 0) *= scale_y; // c * scale_y
    //rotation_matrix.at<double>(1, 1) *= scale_y; // d * scale_y
    //warpAffine(combinedImage, combinedImage, rotation_matrix, Size(700, 600));

    // 定义原始图像的四个角点(顺时针顺序)
    vector<Point2f> src_points = {
        Point2f(0, 0),                     // 左上角
        Point2f(combinedImage.cols - 1, 0),          // 右上角  
        Point2f(combinedImage.cols - 1, combinedImage.rows - 1), // 右下角
        Point2f(0, combinedImage.rows - 1)           // 左下角
    };

    // 定义变换后的四个点(创建透视效果)
    vector<Point2f> dst_points = {
        Point2f(50, 50),                   // 左上角向内移动
        Point2f(combinedImage.cols - 100, 80),       // 右上角向内移动
        Point2f(combinedImage.cols - 50, combinedImage.rows - 50),  // 右下角向内移动
        Point2f(100, combinedImage.rows - 80)        // 左下角向内移动
    };

    // 计算投影变换矩阵
    Mat perspective_matrix = getPerspectiveTransform(src_points, dst_points);
    warpPerspective(combinedImage, combinedImage, perspective_matrix, combinedImage.size(),
        INTER_LINEAR, BORDER_CONSTANT, Scalar(0));

    // 保存生成的图像
    imwrite("combined_markers.png", combinedImage);
#pragma endregion

  

    // 准备检测
    Mat displayImage;
    cvtColor(combinedImage, displayImage, COLOR_GRAY2BGR);

    //Mat binary;
    //adaptiveThreshold(combinedImage, binary, 255, AdaptiveThresholdTypes::ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 3, 0);


    aruco::DetectorParameters detectorParams;

    // 优化参数设置
    detectorParams.adaptiveThreshWinSizeMin = 3; //参数代表选择的自适应阈值窗口大小(以像素为单位)间隔
    detectorParams.adaptiveThreshWinSizeMax = 23;
    detectorParams.adaptiveThreshWinSizeStep = 2;
    detectorParams.adaptiveThreshConstant = 7; // 这一参数表达了阈值状态下的常量
    detectorParams.minMarkerPerimeterRate = 0.03;  // 检测轮廓,这些参数决定了marker的最小值和最大值,具体来说,是最大最小marker的周长
    detectorParams.maxMarkerPerimeterRate = 4.0;   // 例如,大小为640x480,最大相对marker周长为0.05的图像,将会产生一个最大周长640x4 = 2560(像素)的marker,因为640是图像的最大尺寸
    detectorParams.cornerRefinementMethod = aruco::CORNER_REFINE_SUBPIX;
    
    detectorParams.cornerRefinementWinSize = 3;    //  这一参数决定了亚像素级细化过程的窗口大小。
    detectorParams.polygonalApproxAccuracyRate = 0.01;//我们对所有的候选进行多边形近似,只有近似结果为方形的形状才能通过测试
    detectorParams.minCornerDistanceRate = 0.05;//同一张marker中每一对角的最小距离。这是相对于marker周长的值。像素的最小距离为Perimeter * minCornerDistanceRate
    detectorParams.minDistanceToBorder = 3;//  marker角到图像边缘最小距离。
    detectorParams.markerBorderBits = 1;// 这一参数指定了marker边界的宽度。
    //detectorParams.useAruco3Detection = true;
    detectorParams.errorCorrectionRate = 3;
    detectorParams.minOtsuStdDev = 5;
    detectorParams.perspectiveRemovePixelPerCell = 10;
    detectorParams.maxErroneousBitsInBorderRate = 0.35;
    detectorParams.perspectiveRemoveIgnoredMarginPerCell = 0.13;

    aruco::RefineParameters refineParams;
    refineParams.minRepDistance = 20;
    aruco::ArucoDetector detector(dictionary, detectorParams, refineParams);

#pragma region 保存参数
    FileStorage fs("111.txt", cv::FileStorage::WRITE);
    //fs.write("H", detectorParams.adaptiveThreshConstant);
    dictionary.writeDictionary(fs);
    detectorParams.writeDetectorParameters(fs);
    refineParams.writeRefineParameters(fs);
    fs.release();

    FileStorage fs2("111.txt", cv::FileStorage::READ);
    FileNode fn2 = fs2.root();
    //std::cout << fn2["nmarkers"].real() << std::endl;;
    aruco::RefineParameters refineParams2;  //refineParams2.errorCorrectionRate的值会取到跟dictionary中的值一样
    refineParams2.readRefineParameters(fn2);
    aruco::Dictionary dictionary2;
    dictionary2.readDictionary(fn2);
    aruco::DetectorParameters detectorParams2;
    detectorParams2.readDetectorParameters(fn2);
    fs2.release();
#pragma endregion

   

    // 检测标记
    vector<vector<Point2f>> markerCorners, rejectedCandidates;
    vector<int> markerIds;

    detector.detectMarkers(combinedImage, markerCorners, markerIds, rejectedCandidates);

    if (markerIds.size() > 0) {
        aruco::drawDetectedMarkers(displayImage, markerCorners, markerIds);
        
    }

    // 绘制检测结果
    cout << "检测到的标记数量: " << markerIds.size() << endl;
    for (size_t i = 0; i < markerIds.size(); i++) {
        cout << "标记ID: " << markerIds[i] << endl;

        // 绘制角点
        for (size_t j = 0; j < markerCorners[i].size(); j++) {
            circle(displayImage, markerCorners[i][j], 3, Scalar(0, 0, 255), -1);
        }

        // 绘制标记边框
        for (size_t j = 0; j < markerCorners[i].size(); j++) {
            line(displayImage, markerCorners[i][j],
                markerCorners[i][(j + 1) % 4], Scalar(0, 255, 0), 2);
        }
    }

    // 绘制被拒绝的候选标记
    for (size_t i = 0; i < rejectedCandidates.size(); i++) {
        for (size_t j = 0; j < rejectedCandidates[i].size(); j++) {
            circle(displayImage, rejectedCandidates[i][j], 2, Scalar(255, 0, 0), -1);
        }
    }

    // 显示结果
    //imwrite("detection_result.png", displayImage);
    cout << "结果已保存为 detection_result.png" << endl;

    return 0;
}

aruco码的周围得是白色,这样才可以找轮廓是找到aruco码,所有手动生成的码外面需要有一个白色轮廓
参考:https://www.cnblogs.com/yilangUAV/p/14436171.html

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐