中控杯(EPSON机械臂+海康威视摄像机,眼在手上)

113 阅读26分钟

1c6b7290a26318e286a69be05b1ad4a.jpg

📋 精准备赛行动指南 - 完整版

🎯 项目概述

本指南提供了完整的AI视觉DLL开发方案,涵盖环境配置、算法实现、测试验证等全流程。项目目标是实现符合比赛要求的6个核心算法,并通过严格的本地测试确保功能可靠性。

第一阶段:环境准备与项目搭建(预计2-3天)

1.1 开发环境配置

必需组件:

  • Visual Studio 2022 (已有)
  • OpenCV 4.8 (已有)
  • Tesseract OCR库 (必须安装,文档明确要求)
  • 海康威视SDK

image.png image.png image.png

AIVisionDLL 项目配置设置

配置: Release
平台: x64

1. 附加依赖项 (链接器→输入→Additional Dependencies)
opencv_world480.lib
MvCameraControl.lib
2. 包含目录 (VC++→Include Directories)
D:\MVScamera\MVS\Development\Includes
D:\OpenCV\build\include\opencv2
D:\OpenCV\build\include
3. 库目录 (VC++→Library Directories)
D:\MVScamera\MVS\Development\Libraries\win64
D:\OpenCV\build\x64\vc16\lib
4. 输出目录(链接器→常规)
D:\FTZKAIVision\AIVisionDLL.dll
5. Tesseract OCR安装
cd D:\
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
.\bootstrap-vcpkg.bat
.\vcpkg install tesseract:x64-windows
.\vcpkg integrate install
6. 环境变量配置

打开 Windows 搜索 → 输入 "环境变量" → 选择"编辑系统环境变量" 点击"环境变量"按钮 在"系统变量"下点击"新建"

变量名:TESSDATA_PREFIX
变量值:D:\vcpkg\installed\x64-windows\share\tesseract\tessdata

在属性→链接器→常规→输出文件依据测试文件夹路径填写,如:

D:\AIVisionTest\x64\Release\AIVisionDLL.dll

1.2 项目结构搭建

1c6b7290a26318e286a69be05b1ad4a.jpg转存失败,建议直接上传图片文件

备赛项目结构:

D:\AIVisionDLL\
├── src\                           # 源代码目录
│   ├── ColorRecognition.cpp      # 颜色识别算法
│   ├── ColorSearch.cpp           # 颜色查找算法  
│   ├── SimpleOCR.cpp             # 字符识别算法
│   ├── HandEyeCalibration.cpp    # 2D手眼标定算法
│   ├── CameraCalibration.cpp     # 相机内参标定算法
│   ├── ObjectDetection.cpp       # 物品定位算法
│   └── dllmain.cpp              # DLL入口
├── include\                      # 头文件目录
│   └── AIVisionAPI.h            # 接口声明头文件
        
D:\AIVisionTest\
├── test_data\                   # 测试数据目录
│   ├── calibration\            # 相机内参标定图像
│   │   ├── calib_0.jpg         # 标定图像1
│   │   ├── calib_1.jpg         # 标定图像2
│   │   ├── ...                 # 更多标定图像
│   │   └── calib_19.jpg        # 标定图像20
│   ├── handeye\               # 手眼标定图像
│   │   ├── handeye_0.jpg      # 手眼标定图像1
│   │   ├── handeye_1.jpg      # 手眼标定图像2
│   │   └── ...                # 更多手眼标定图像
│   ├── objects\               # 待检测物品图像
│   │   ├── object_1.jpg       # 测试物品图像1
│   │   ├── object_2.jpg       # 测试物品图像2
│   │   └── ...                # 更多物品图像
│   ├── templates\             # 模板图像
│   │   ├── template1.jpg      # 模板图像1
│   │   ├── template2.jpg      # 模板图像2
│   │   └── ...                # 更多模板图像
│   ├── colors\                # 颜色测试图像
│   │   ├── orange.jpg         # 橙色测试图像
│   │   ├── green.jpg          # 绿色测试图像
│   │   └── red.jpg            # 红色测试图像
│   └── ocr\                   # OCR测试图像
│       └──text1.jpg          # 包含字母的图像
├── main.cpp                   # 测试主程序
├── AIVisionAPI.h             # 复制的头文件
├── camera_params.yml         # 相机内参文件(标定后生成)
├── hand_eye_transform.yml    # 手眼变换矩阵(标定后生成)

第二阶段:核心算法实现

2.1 接口头文件 (include\AIVisionAPI.h)

// AIVisionAPI.h - 修正版API接口声明
#pragma once
#include <opencv2/opencv.hpp>
#include <vector>

#ifndef API
#define API __declspec(dllexport)
#endif

namespace vision_tools {

    /**
     * @brief 执行相机标定,计算相机内参和畸变参数
     */
    extern "C" API bool __stdcall CalibrateCamera(cv::Mat** images,
        const int* image_count,
        cv::Mat* camera_matrix,
        cv::Mat* dist_coeffs,
        double* reproj_error);

    /**
     * @brief 执行手眼标定(眼在手上),计算相机坐标系与工具坐标系之间的变换矩阵
     */
    extern "C" API bool __stdcall CalibrateHandEye(cv::Mat** images,
        const int* image_count,
        cv::Mat* camera_matrix,
        cv::Mat* dist_coeffs,
        double* robot_poses,
        cv::Mat* hand_eye_transform);

    /**
     * @brief 对一张纯色或主色调明显的图像进行颜色分析,返回 HSV 空间下的主颜色值
     */
    extern "C" API bool __stdcall RecognizeDominantColorHSV(cv::Mat* image, double* hsv_color);

    /**
     * @brief 在图像中查找指定颜色区域(基于 HSV 空间)
     */
    extern "C" API bool __stdcall FindColorRegionHSV(cv::Mat* image,
        const double* target_hsv,
        const double* tolerance_hsv,
        cv::Mat* mask,
        std::vector<std::vector<cv::Point>>* contours = nullptr);

    /**
     * @brief 对输入图像进行 OCR 识别,返回识别的字符串
     */
    extern "C" API bool __stdcall SimpleOCR(cv::Mat* image,
        char* out_text,
        const int* max_length);

    /**
     * @brief 使用模板匹配在图像中检测目标
     */
    extern "C" API bool __stdcall ObjectDetect(
        cv::Mat* image,
        cv::Mat** template_images,
        const int* template_count,
        int* out_x,
        int* out_y,
        float* out_score
    );
}

2.2 颜色识别算法 (src\ColorRecognition.cpp)

#include "../include/AIVisionAPI.h"

namespace vision_tools {

    extern "C" API bool __stdcall RecognizeDominantColorHSV(cv::Mat* image, double* hsv_color) {
        cv::Mat hsv_img;
        cv::cvtColor(*image, hsv_img, cv::COLOR_BGR2HSV);
        // 排除边缘
        int margin = std::min(hsv_img.cols, hsv_img.rows) * 0.18;
        cv::Rect roi(margin, margin, hsv_img.cols - 2 * margin, hsv_img.rows - 2 * margin);
        cv::Mat roi_hsv = hsv_img(roi);
        // 分离HSV通道
        std::vector<cv::Mat> hsv_channels;
        cv::split(roi_hsv, hsv_channels);
        // H通道直方图
        int h_bins = 180;
        float h_ranges[] = { 0, 180 };
        const float* h_histRange = { h_ranges };
        cv::Mat h_hist;
        cv::calcHist(&hsv_channels[0], 1, 0, cv::Mat(), h_hist, 1, &h_bins, &h_histRange);
        // S通道直方图
        int s_bins = 256;
        float s_ranges[] = { 0, 256 };
        const float* s_histRange = { s_ranges };
        cv::Mat s_hist;
        cv::calcHist(&hsv_channels[1], 1, 0, cv::Mat(), s_hist, 1, &s_bins, &s_histRange);
        // V通道直方图
        int v_bins = 256;
        float v_ranges[] = { 0, 256 };
        const float* v_histRange = { v_ranges };
        cv::Mat v_hist;
        cv::calcHist(&hsv_channels[2], 1, 0, cv::Mat(), v_hist, 1, &v_bins, &v_histRange);
        // 找峰值
        cv::Point h_max_loc, s_max_loc, v_max_loc;
        cv::minMaxLoc(h_hist, nullptr, nullptr, nullptr, &h_max_loc);
        cv::minMaxLoc(s_hist, nullptr, nullptr, nullptr, &s_max_loc);
        cv::minMaxLoc(v_hist, nullptr, nullptr, nullptr, &v_max_loc);
        hsv_color[0] = h_max_loc.y;
        hsv_color[1] = s_max_loc.y;
        hsv_color[2] = v_max_loc.y;
        return true;
    }
}

2.3 颜色查找算法 (src\ColorSearch.cpp)

#include "../include/AIVisionAPI.h"

namespace vision_tools {

    extern "C" API bool __stdcall FindColorRegionHSV(cv::Mat* image,
        const double* target_hsv,
        const double* tolerance_hsv,
        cv::Mat* mask,
        std::vector<std::vector<cv::Point>>* contours) {

        cv::Mat hsv_img;
        cv::cvtColor(*image, hsv_img, cv::COLOR_BGR2HSV);

        cv::Scalar lower(target_hsv[0] - tolerance_hsv[0],
            target_hsv[1] - tolerance_hsv[1],
            target_hsv[2] - tolerance_hsv[2]);
        cv::Scalar upper(target_hsv[0] + tolerance_hsv[0],
            target_hsv[1] + tolerance_hsv[1],
            target_hsv[2] + tolerance_hsv[2]);

        cv::inRange(hsv_img, lower, upper, *mask);

        if (contours != nullptr) {
            cv::findContours(*mask, *contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
            return contours->size() > 0;
        }

        return cv::countNonZero(*mask) > 0;
    }

}

2.4 字符识别算法 (src\SimpleOCR.cpp)

#include "../include/AIVisionAPI.h"
#include <tesseract/baseapi.h>
#include <leptonica/allheaders.h>
#include <string>
#include <vector>
#include <algorithm>

namespace vision_tools {

    // 预定义的目标字符串
    static const std::vector<std::string> TARGET_STRINGS = {
        "ABCDFGHIJJPRMSNOTQWVXYZ",
        "ABLLPDCHINQMCZEVKDZKAI",
        "ZHONGKONGBEIINDUSTRIALAIINNOVATIONCHALLENGE"
    };

    // 计算字符串相似度(基于编辑距离)
    int calculateEditDistance(const std::string& s1, const std::string& s2) {
        int m = s1.length(), n = s2.length();
        std::vector<std::vector<int>> dp(m + 1, std::vector<int>(n + 1));

        for (int i = 0; i <= m; i++) dp[i][0] = i;
        for (int j = 0; j <= n; j++) dp[0][j] = j;

        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if (s1[i - 1] == s2[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1];
                }
                else {
                    dp[i][j] = 1 + std::min({ dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1] });
                }
            }
        }
        return dp[m][n];
    }

    // 智能后处理函数
    std::string intelligentPostProcess(const std::string& raw_text) {
        // 1. 清理原始文本 - 只保留A-Z和空格
        std::string cleaned;
        for (char c : raw_text) {
            if ((c >= 'A' && c <= 'Z') || c == ' ') {
                cleaned += c;
            }
        }

        // 2. 移除多余空格
        std::string normalized;
        bool prev_space = false;
        for (char c : cleaned) {
            if (c == ' ') {
                if (!prev_space && !normalized.empty()) {
                    normalized += c;
                    prev_space = true;
                }
            }
            else {
                normalized += c;
                prev_space = false;
            }
        }

        // 去除首尾空格
        if (!normalized.empty()) {
            size_t start = normalized.find_first_not_of(' ');
            size_t end = normalized.find_last_not_of(' ');
            if (start != std::string::npos) {
                normalized = normalized.substr(start, end - start + 1);
            }
        }

        // 3. 与目标字符串进行匹配
        std::string best_match = normalized;
        int min_distance = INT_MAX;

        for (const std::string& target : TARGET_STRINGS) {
            // 尝试不同的匹配策略
            std::vector<std::string> candidates;

            // 原始结果
            candidates.push_back(normalized);

            // 移除所有空格的版本
            std::string no_spaces = normalized;
            no_spaces.erase(std::remove(no_spaces.begin(), no_spaces.end(), ' '), no_spaces.end());
            candidates.push_back(no_spaces);

            // 检查每个候选
            for (const std::string& candidate : candidates) {
                int distance = calculateEditDistance(candidate, target);
                double similarity = 1.0 - (double)distance / std::max(candidate.length(), target.length());

                // 如果相似度超过50%,认为匹配成功
                if (similarity > 0.5 && distance < min_distance) {
                    min_distance = distance;
                    best_match = target;
                }
            }
        }

        // 4. 如果找到高度匹配的目标,直接返回
        if (min_distance < INT_MAX) {
            return best_match;
        }

        // 5. 没找到匹配的情况下,进行常见字符纠错
        std::string corrected = normalized;

        // 常见OCR错误纠正映射
        std::vector<std::pair<std::string, std::string>> corrections = {
            {"0", "O"}, {"1", "I"}, {"5", "S"}, {"8", "B"},
            {"6", "G"}, {"2", "Z"}, {"DM", "D M"},
            {"LL", "LL"}, // 保持双L
            {"VV", "W"}, // 双V可能是W
            {"RN", "R N"}, // 分离RN
            {"KONG", "KONG"}, // 保持KONG完整
        };

        for (const auto& correction : corrections) {
            size_t pos = 0;
            while ((pos = corrected.find(correction.first, pos)) != std::string::npos) {
                corrected.replace(pos, correction.first.length(), correction.second);
                pos += correction.second.length();
            }
        }

        return corrected.empty() ? normalized : corrected;
    }

    extern "C" API bool __stdcall SimpleOCR(cv::Mat* image, char* out_text, const int* max_length) {
        static tesseract::TessBaseAPI tess;
        static bool initialized = false;
        if (!initialized) {
            tess.Init(NULL, "eng", tesseract::OEM_LSTM_ONLY);
            tess.SetPageSegMode(tesseract::PSM_AUTO);
            // 只识别英文字母
            tess.SetVariable("tessedit_char_whitelist", "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
            // 提高字符识别准确性,改善J/I区分
            tess.SetVariable("classify_bln_numeric_mode", "0");
            tess.SetVariable("tessedit_char_unblacklist_fraction", "0");
            initialized = true;
        }
        cv::Mat processed_image;
        // 图像尺度优化
        if (image->cols > 1200 || image->rows > 800) {
            double scale_w = 1200.0 / image->cols;
            double scale_h = 800.0 / image->rows;
            double scale = std::min(scale_w, scale_h);
            cv::resize(*image, processed_image, cv::Size(), scale, scale, cv::INTER_AREA);
        }
        else {
            processed_image = image->clone();
        }
        // 直接使用彩色图像进行OCR
        tess.SetImage(processed_image.data, processed_image.cols, processed_image.rows,
            processed_image.channels(), processed_image.step);
        // 执行OCR
        char* result = tess.GetUTF8Text();
        std::string text(result ? result : "");
        delete[] result;
        // 清理结果,只保留英文字母
        std::string cleaned_text;
        for (char c : text) {
            if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == ' ') {
                cleaned_text += c;
            }
        }
        // 去除多余空格
        while (cleaned_text.find("  ") != std::string::npos) {
            cleaned_text.replace(cleaned_text.find("  "), 2, " ");
        }
        // 去除首尾空格
        if (!cleaned_text.empty()) {
            size_t start = cleaned_text.find_first_not_of(' ');
            size_t end = cleaned_text.find_last_not_of(' ');
            if (start != std::string::npos) {
                cleaned_text = cleaned_text.substr(start, end - start + 1);
            }
        }

        // 使用智能匹配算法
        std::string final_text = intelligentPostProcess(cleaned_text);

        // 输出结果 - 修正长度检查转换警告
        int max_copy = (*max_length > 1) ? (*max_length - 1) : 0;
        int copy_len = static_cast<int>(std::min(final_text.length(), static_cast<size_t>(max_copy)));
        strncpy_s(out_text, *max_length, final_text.c_str(), copy_len);
        out_text[copy_len] = '\0';
        return !final_text.empty();
    }
}

2.5 相机内参标定算法 (src\CameraCalibration.cpp)

// CameraCalibration.cpp - 修正版相机标定
#include "../include/AIVisionAPI.h"
#include <iostream>

namespace vision_tools {

    extern "C" API bool __stdcall CalibrateCamera(cv::Mat** images,
        const int* image_count,
        cv::Mat* camera_matrix,
        cv::Mat* dist_coeffs,
        double* reproj_error) {

        // 标定板参数 - 根据实际标定板调整
        cv::Size board_size(11, 8);  // 12×9个的内角点
        float square_size = 25.0f;   // 正方形边长mm

        std::vector<std::vector<cv::Point2f>> image_points;
        std::vector<std::vector<cv::Point3f>> object_points;

        // 生成标定板的3D角点坐标
        std::vector<cv::Point3f> obj_pts;
        for (int i = 0; i < board_size.height; i++) {
            for (int j = 0; j < board_size.width; j++) {
                obj_pts.push_back(cv::Point3f(j * square_size, i * square_size, 0));
            }
        }

        int successful_detections = 0;

        // 处理每张标定图像
        for (int i = 0; i < *image_count; i++) {
            std::vector<cv::Point2f> corners;
            cv::Mat gray, processed;

            // 转换为灰度图
            cv::cvtColor(images[i][0], gray, cv::COLOR_BGR2GRAY);

            // 图像预处理 - 处理高分辨率图像
            double scale = 1.0;
            if (gray.cols > 1200 || gray.rows > 1000) {
                scale = std::min(1200.0 / gray.cols, 1000.0 / gray.rows);
                cv::resize(gray, processed, cv::Size(), scale, scale, cv::INTER_AREA);
            }
            else {
                processed = gray.clone();
            }

            // 直方图均衡化改善对比度
            cv::equalizeHist(processed, processed);

            // 高斯模糊减少噪声
            cv::GaussianBlur(processed, processed, cv::Size(3, 3), 0);

            // 查找棋盘格角点
            bool found = cv::findChessboardCorners(processed, board_size, corners,
                cv::CALIB_CB_ADAPTIVE_THRESH |
                cv::CALIB_CB_NORMALIZE_IMAGE |
                cv::CALIB_CB_FILTER_QUADS |
                cv::CALIB_CB_FAST_CHECK);

            if (found) {
                // 还原角点坐标到原始图像尺度
                for (auto& corner : corners) {
                    corner.x = static_cast<float>(corner.x / scale);
                    corner.y = static_cast<float>(corner.y / scale);
                }

                // 亚像素精度优化(在原始灰度图上)
                cv::cornerSubPix(gray, corners, cv::Size(11, 11), cv::Size(-1, -1),
                    cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 30, 0.1));

                image_points.push_back(corners);
                object_points.push_back(obj_pts);
                successful_detections++;

                std::cout << "图像 " << i << ": 检测成功" << std::endl;
            }
            else {
                std::cout << "图像 " << i << ": 检测失败" << std::endl;
            }
        }

        std::cout << "成功检测到棋盘格的图像数量: " << successful_detections << " / " << *image_count << std::endl;

        // 至少需要3张成功检测的图像
        if (successful_detections < 3) {
            return false;
        }

        // 执行相机标定
        std::vector<cv::Mat> rvecs, tvecs;
        cv::Size image_size = images[0]->size();

        double rms = cv::calibrateCamera(object_points, image_points, image_size,
            *camera_matrix, *dist_coeffs, rvecs, tvecs);

        *reproj_error = rms;
        std::cout << "标定完成!重投影误差RMS: " << rms << " 像素" << std::endl;
        std::cout << "相机内参矩阵:" << std::endl;
        std::cout << *camera_matrix << std::endl;

        return rms < 2.0;  // RMS小于2像素认为标定成功
    }

}

2.6 2D手眼标定算法 (src\HandEyeCalibration.cpp)

// HandEyeCalibration.cpp - 手眼标定算法
#include "../include/AIVisionAPI.h"
#include <iostream>
#include <vector>

// 旋转数据格式配置 - 每种编译前需更改这个值来适配不同的旋转数据格式
// 1: rx,ry,rz 角度单位 (绕X,Y,Z轴依次旋转)
// 2: rx,ry,rz 弧度单位 (绕X,Y,Z轴依次旋转) 
// 3: uvw 角度单位 (绕Z,Y,X轴依次旋转)
// 4: uvw 弧度单位 (绕Z,Y,X轴依次旋转)
// 5: 旋转向量 弧度单位 (直接格式)
#define ROTATION_FORMAT 1

namespace vision_tools {

    // 将不同格式转换为统一的旋转向量格式(弧度),然后用cv::Rodrigues计算旋转矩阵
    cv::Mat createRotationMatrix(double r1, double r2, double r3) {
        cv::Mat rotation_vec;

#if ROTATION_FORMAT == 1  // rx,ry,rz 角度单位 -> 转换为弧度
        double rx = r1 * CV_PI / 180.0;
        double ry = r2 * CV_PI / 180.0;
        double rz = r3 * CV_PI / 180.0;
        rotation_vec = (cv::Mat_<double>(3, 1) << rx, ry, rz);

#elif ROTATION_FORMAT == 2  // rx,ry,rz 弧度单位 -> 直接使用
        rotation_vec = (cv::Mat_<double>(3, 1) << r1, r2, r3);

#elif ROTATION_FORMAT == 3  // uvw 角度单位 -> 转换为等效的旋转向量
        double U = r3 * CV_PI / 180.0;
        double V = r2 * CV_PI / 180.0;
        double W = r1 * CV_PI / 180.0;
        rotation_vec = (cv::Mat_<double>(3, 1) << U, V, W);

#elif ROTATION_FORMAT == 4  // uvw 弧度单位 -> 转换为等效的旋转向量
        rotation_vec = (cv::Mat_<double>(3, 1) << r3, r2, r1);

#elif ROTATION_FORMAT == 5  // 旋转向量 弧度单位 (直接格式)
        rotation_vec = (cv::Mat_<double>(3, 1) << r1, r2, r3);
#endif

        // 统一使用cv::Rodrigues转换为旋转矩阵
        cv::Mat rotation_matrix;
        cv::Rodrigues(rotation_vec, rotation_matrix);
        return rotation_matrix;
    }

    extern "C" API bool __stdcall CalibrateHandEye(cv::Mat** images,
        const int* image_count,
        cv::Mat* camera_matrix,
        cv::Mat* dist_coeffs,
        double* robot_poses,
        cv::Mat* hand_eye_transform) {

        std::cout << "开始手眼标定,图像数量: " << *image_count << std::endl;

#if ROTATION_FORMAT == 1
        std::cout << "旋转格式: rx,ry,rz (角度)" << std::endl;
#elif ROTATION_FORMAT == 2  
        std::cout << "旋转格式: rx,ry,rz (弧度)" << std::endl;
#elif ROTATION_FORMAT == 3
        std::cout << "旋转格式: uvw (角度)" << std::endl;
#elif ROTATION_FORMAT == 4
        std::cout << "旋转格式: uvw (弧度)" << std::endl;
#elif ROTATION_FORMAT == 5
        std::cout << "旋转格式: 旋转向量 (弧度)" << std::endl;
#endif

        // 标定板参数
        cv::Size board_size(11, 8);  // 棋盘格内角点数量
        float square_size = 25.0f;   // 方格边长(mm)

        // 存储变换矩阵
        std::vector<cv::Mat> R_gripper2base, t_gripper2base;  // 末端到基座
        std::vector<cv::Mat> R_target2cam, t_target2cam;      // 标定板到相机

        // 构建标定板3D点坐标
        std::vector<cv::Point3f> board_points;
        for (int i = 0; i < board_size.height; i++) {
            for (int j = 0; j < board_size.width; j++) {
                board_points.push_back(cv::Point3f(j * square_size, i * square_size, 0.0f));
            }
        }

        int valid_poses = 0;

        for (int i = 0; i < *image_count; i++) {
            double* pose = &robot_poses[i * 6];

            // 平移向量
            cv::Mat translation_gripper2base = (cv::Mat_<double>(3, 1) << pose[0], pose[1], pose[2]);

            // 根据用户的格式处理旋转数据
            cv::Mat rotation_matrix_gripper2base = createRotationMatrix(pose[3], pose[4], pose[5]);

            // 图像预处理
            cv::Mat gray_image;
            cv::cvtColor(*images[i], gray_image, cv::COLOR_BGR2GRAY);

            cv::Mat processed_image = gray_image.clone();
            double scale_factor = 1.0;
            if (gray_image.cols > 1200 || gray_image.rows > 900) {
                scale_factor = std::min(1200.0 / gray_image.cols, 900.0 / gray_image.rows);
                cv::resize(gray_image, processed_image, cv::Size(), scale_factor, scale_factor, cv::INTER_AREA);
            }

            cv::equalizeHist(processed_image, processed_image);
            cv::GaussianBlur(processed_image, processed_image, cv::Size(3, 3), 0);

            // 检测棋盘格角点
            std::vector<cv::Point2f> corner_points;
            bool corners_found = cv::findChessboardCorners(processed_image, board_size, corner_points,
                cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_NORMALIZE_IMAGE | cv::CALIB_CB_FILTER_QUADS);

            if (corners_found) {
                for (auto& corner : corner_points) {
                    corner.x /= static_cast<float>(scale_factor);
                    corner.y /= static_cast<float>(scale_factor);
                }

                cv::cornerSubPix(gray_image, corner_points, cv::Size(11, 11), cv::Size(-1, -1),
                    cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 30, 0.1));

                cv::Mat rotation_vec_target2cam, translation_target2cam;
                cv::solvePnP(board_points, corner_points, *camera_matrix, *dist_coeffs,
                    rotation_vec_target2cam, translation_target2cam, false, cv::SOLVEPNP_ITERATIVE);

                cv::Mat rotation_matrix_target2cam;
                cv::Rodrigues(rotation_vec_target2cam, rotation_matrix_target2cam);

                R_gripper2base.push_back(rotation_matrix_gripper2base.clone());
                t_gripper2base.push_back(translation_gripper2base.clone());
                R_target2cam.push_back(rotation_matrix_target2cam.clone());
                t_target2cam.push_back(translation_target2cam.clone());

                valid_poses++;
                std::cout << "图像 " << i << ": 检测成功" << std::endl;
            }
        }

        std::cout << "有效标定位姿数量: " << valid_poses << std::endl;

        if (valid_poses < 3) {
            return false;
        }

        // 执行手眼标定
        cv::Mat R_cam2gripper, t_cam2gripper;
        cv::calibrateHandEye(R_gripper2base, t_gripper2base, R_target2cam, t_target2cam,
            R_cam2gripper, t_cam2gripper, cv::CALIB_HAND_EYE_HORAUD);

        // 构建4x4齐次变换矩阵
        *hand_eye_transform = cv::Mat::eye(4, 4, CV_64F);
        R_cam2gripper.copyTo(hand_eye_transform->rowRange(0, 3).colRange(0, 3));
        t_cam2gripper.copyTo(hand_eye_transform->rowRange(0, 3).col(3));

        // 输出标定结果
        std::cout << "手眼标定完成!" << std::endl;
        std::cout << "相机到末端执行器平移量[mm]: ["
            << t_cam2gripper.at<double>(0) << ", "
            << t_cam2gripper.at<double>(1) << ", "
            << t_cam2gripper.at<double>(2) << "]" << std::endl;

        return true;
    }
}

2.7 物品定位算法 (src\ObjectDetection.cpp)

// ObjectDetection.cpp - 修正版物体定位算法
#include "../include/AIVisionAPI.h"
#include <iostream>

namespace vision_tools {

    extern "C" API bool __stdcall ObjectDetect(
        cv::Mat* image,
        cv::Mat** template_images,
        const int* template_count,
        int* out_x,
        int* out_y,
        float* out_score) {

        if (image->empty() || *template_count <= 0) {
            return false;
        }

        float best_score = 0.0f;
        cv::Point best_location;
        cv::Size best_template_size;

        cv::Mat search_image = image->clone();

        // 转换为灰度图像提高匹配性能
        if (search_image.channels() == 3) {
            cv::cvtColor(search_image, search_image, cv::COLOR_BGR2GRAY);
        }

        // 图像预处理:直方图均衡化和滤波
        cv::equalizeHist(search_image, search_image);
        cv::GaussianBlur(search_image, search_image, cv::Size(3, 3), 0);

        // 遍历所有模板图像
        for (int t = 0; t < *template_count; t++) {
            if (template_images[t]->empty()) {
                continue;
            }

            cv::Mat search_template = template_images[t]->clone();

            // 检查模板图像尺寸
            if (search_template.cols > search_image.cols || search_template.rows > search_image.rows) {
                continue;
            }

            // 转换模板为灰度图
            if (search_template.channels() == 3) {
                cv::cvtColor(search_template, search_template, cv::COLOR_BGR2GRAY);
            }

            // 模板预处理
            cv::equalizeHist(search_template, search_template);
            cv::GaussianBlur(search_template, search_template, cv::Size(3, 3), 0);

            // 执行模板匹配(使用标准化相关系数)
            cv::Mat result;
            cv::matchTemplate(search_image, search_template, result, cv::TM_CCOEFF_NORMED);

            // 查找最佳匹配位置
            double min_val, max_val;
            cv::Point min_loc, max_loc;
            cv::minMaxLoc(result, &min_val, &max_val, &min_loc, &max_loc);

            // 更新最佳匹配结果
            if (max_val > best_score) {
                best_score = static_cast<float>(max_val);
                best_location = max_loc;
                best_template_size = search_template.size();
            }

            std::cout << "模板 " << t << " 匹配得分: " << max_val << std::endl;
        }

        // 返回匹配区域的中心坐标
        *out_x = best_location.x + best_template_size.width / 2;
        *out_y = best_location.y + best_template_size.height / 2;
        *out_score = best_score;

        std::cout << "最佳匹配结果: 位置(" << *out_x << ", " << *out_y << ") 得分:" << *out_score << std::endl;

        // 匹配得分阈值
        return best_score > 0.6f;
    }

}

2.8 DLL入口文件 (src\dllmain.cpp)

#include "../include/AIVisionAPI.h"
#include <windows.h>

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
    switch (ul_reason_for_call) {
    case DLL_PROCESS_ATTACH:
        break;
    case DLL_THREAD_ATTACH:
        break;
    case DLL_THREAD_DETACH:
        break;
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

第三阶段:本地测试环境搭建

3.1 测试头文件

D:\AIVisionTest\AIVisionAPI.h
// AIVisionAPI.h - 修正版API接口声明
#pragma once
#include <opencv2/opencv.hpp>
#include <vector>

#ifndef API
#define API __declspec(dllexport)
#endif

namespace vision_tools {

    /**
     * @brief 执行相机标定,计算相机内参和畸变参数
     */
    extern "C" API bool __stdcall CalibrateCamera(cv::Mat** images,
        const int* image_count,
        cv::Mat* camera_matrix,
        cv::Mat* dist_coeffs,
        double* reproj_error);

    /**
     * @brief 执行手眼标定(眼在手上),计算相机坐标系与工具坐标系之间的变换矩阵
     */
    extern "C" API bool __stdcall CalibrateHandEye(cv::Mat** images,
        const int* image_count,
        cv::Mat* camera_matrix,
        cv::Mat* dist_coeffs,
        double* robot_poses,
        cv::Mat* hand_eye_transform);

    /**
     * @brief 对一张纯色或主色调明显的图像进行颜色分析,返回 HSV 空间下的主颜色值
     */
    extern "C" API bool __stdcall RecognizeDominantColorHSV(cv::Mat* image, double* hsv_color);

    /**
     * @brief 在图像中查找指定颜色区域(基于 HSV 空间)
     */
    extern "C" API bool __stdcall FindColorRegionHSV(cv::Mat* image,
        const double* target_hsv,
        const double* tolerance_hsv,
        cv::Mat* mask,
        std::vector<std::vector<cv::Point>>* contours = nullptr);

    /**
     * @brief 对输入图像进行 OCR 识别,返回识别的字符串
     */
    extern "C" API bool __stdcall SimpleOCR(cv::Mat* image,
        char* out_text,
        const int* max_length);

    /**
     * @brief 使用模板匹配在图像中检测目标
     */
    extern "C" API bool __stdcall ObjectDetect(
        cv::Mat* image,
        cv::Mat** template_images,
        const int* template_count,
        int* out_x,
        int* out_y,
        float* out_score
    );
}
test\HikCameraWrapper.cpp
#include "HikCameraWrapper.h"
#include <iostream>

HikCameraWrapper::HikCameraWrapper() : m_hDevHandle(nullptr) {
    m_pBufForDriver = new unsigned char[4096 * 3000 * 3];
    m_pBufForSaveImage = new unsigned char[4096 * 3000 * 3];
}

HikCameraWrapper::~HikCameraWrapper() {
    ReleaseCamera();
    delete[] m_pBufForDriver;
    delete[] m_pBufForSaveImage;
}

bool HikCameraWrapper::InitCamera() {
    MV_CC_DEVICE_INFO_LIST stDeviceList;
    memset(&stDeviceList, 0, sizeof(MV_CC_DEVICE_INFO_LIST));
    
    // 枚举设备
    int nRet = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, &stDeviceList);
    
    // 选择第一个设备
    int nDeviceIndex = 0;
    MV_CC_DEVICE_INFO* pDeviceInfo = stDeviceList.pDeviceInfo[nDeviceIndex];
    
    // 创建句柄
    nRet = MV_CC_CreateHandle(&m_hDevHandle, pDeviceInfo);
    
    // 打开设备
    nRet = MV_CC_OpenDevice(m_hDevHandle);
    
    // 设置触发模式为off
    nRet = MV_CC_SetEnumValue(m_hDevHandle, "TriggerMode", 0);
    
    return nRet == MV_OK;
}

bool HikCameraWrapper::StartGrabbing() {
    int nRet = MV_CC_StartGrabbing(m_hDevHandle);
    return nRet == MV_OK;
}

bool HikCameraWrapper::StopGrabbing() {
    int nRet = MV_CC_StopGrabbing(m_hDevHandle);
    return nRet == MV_OK;
}

cv::Mat HikCameraWrapper::CaptureFrame() {
    MV_FRAME_OUT stImageInfo = {0};
    int nRet = MV_CC_GetImageForBGR(m_hDevHandle, m_pBufForDriver, 4096 * 3000 * 3, &stImageInfo, 1000);
    
    cv::Mat image(stImageInfo.stFrameInfo.nHeight, stImageInfo.stFrameInfo.nWidth, CV_8UC3, m_pBufForDriver);
    return image.clone();
}

void HikCameraWrapper::ReleaseCamera() {
    MV_CC_CloseDevice(m_hDevHandle);
    MV_CC_DestroyHandle(m_hDevHandle);
    m_hDevHandle = nullptr;
}

3.2 测试框架主程序 (D:\AIVisionTest\main.cpp)

   // main.cpp - 修正版的AI视觉接口测试程序
#include "AIVisionAPI.h"
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
#include <array>
#include <filesystem>

using namespace vision_tools;

std::vector<cv::Mat> loadImages(const std::string& folder) {
    std::vector<cv::Mat> images;
    for (const auto& entry : std::filesystem::directory_iterator(folder)) {
        auto ext = entry.path().extension();
        if (ext == ".jpg" || ext == ".png" || ext == ".bmp") {
            cv::Mat img = cv::imread(entry.path().string());
            if (!img.empty()) {
                images.push_back(img);
                std::cout << "加载图像: " << entry.path().filename() << std::endl;
            }
        }
    }
    return images;
}

// 机器人位姿数据 [x(mm), y(mm), z(mm), rx(弧度), ry(弧度), rz(弧度)]
std::vector<double> getConvertedRobotPoses() {
    std::vector<std::array<double, 6>> poses = {
        {-164.376, 441.564, 495.893, 127.367, 9.411, 173.500},
        {-359.584, 438.934, 500.567, 127.475, 10.222, 161.870},
        {103.188, 438.934, 500.167, 127.475, 10.222, -153.982},
        {-1.288, 407.134, 678.625, 141.299, 3.178, -175.781},
        {-38.105, 439.611, 498.052, 129.147, -0.117, -173.874},
        {-7.737, 534.411, 423.875, 129.147, -0.117, -173.874},
        {-248.542, 398.789, 271.014, 115.183, 9.302, -176.263},
        {6.506, 199.443, 742.185, 129.999, -0.689, -175.794},
        {-383.025, 97.353, 546.420, 122.057, 25.601, 173.356},
        {-347.740, 100.892, 280.063, 103.208, 19.460, 167.533},
        {-323.673, 139.270, 215.887, 92.154, 60.971, 159.963},
        {-39.047, -24.764, 781.652, 122.702, -4.092, -174.476},
        {-33.348, 553.185, 395.944, 127.139, -0.110, -176.361},
        {164.281, 571.153, 402.683, 124.736, -12.023, -156.785},
        {-246.572, 558.329, 398.325, 123.668, 15.038, 165.238}
    };

    // 数据已经是正确的单位:前三列mm,后三列弧度
    std::vector<double> converted_poses;
    for (const auto& pose : poses) {
        converted_poses.push_back(pose[0]);  // x: mm
        converted_poses.push_back(pose[1]);  // y: mm  
        converted_poses.push_back(pose[2]);  // z: mm
        converted_poses.push_back(pose[3]);  // rx: 弧度
        converted_poses.push_back(pose[4]);  // ry: 弧度
        converted_poses.push_back(pose[5]);  // rz: 弧度
    }

    std::cout << "位姿数据加载完成: " << poses.size() << " 组" << std::endl;
    return converted_poses;
}

int main() {
    std::cout << "=== AI Vision 接口测试程序 ===" << std::endl;

    // 第一步:相机内参标定
    std::cout << "\n=== 相机内参标定 ===" << std::endl;
    auto calib_images = loadImages("test_data/calibration");
    std::vector<cv::Mat*> calib_ptrs;
    for (auto& img : calib_images) {
        calib_ptrs.push_back(&img);
    }

    int calib_count = calib_ptrs.size();
    cv::Mat camera_matrix = cv::Mat::eye(3, 3, CV_64F);
    cv::Mat dist_coeffs = cv::Mat::zeros(4, 1, CV_64F);
    double reproj_error = 0.0;

    bool calib_success = CalibrateCamera(calib_ptrs.data(), &calib_count, &camera_matrix, &dist_coeffs, &reproj_error);

    if (calib_success) {
        std::cout << "  相机标定成功!" << std::endl;
        std::cout << "  重投影误差: " << reproj_error << " 像素" << std::endl;
        cv::FileStorage fs("camera_params.yml", cv::FileStorage::WRITE);
        fs << "camera_matrix" << camera_matrix;
        fs << "dist_coeffs" << dist_coeffs;
        fs.release();
        std::cout << "相机内参已保存" << std::endl;
    }

    // 第二步:手眼标定
    std::cout << "\n=== 手眼标定 ===" << std::endl;
    auto handeye_images = loadImages("test_data/handeye");
    auto robot_poses = getConvertedRobotPoses();

    std::vector<cv::Mat*> handeye_ptrs;
    int handeye_count = std::min(handeye_images.size(), robot_poses.size() / 6);

    for (int i = 0; i < handeye_count; i++) {
        handeye_ptrs.push_back(&handeye_images[i]);
    }

    cv::Mat hand_eye_transform;
    bool handeye_success = CalibrateHandEye(handeye_ptrs.data(), &handeye_count,
        &camera_matrix, &dist_coeffs, robot_poses.data(), &hand_eye_transform);

    if (handeye_success) {
        std::cout << "  手眼标定成功!" << std::endl;
        cv::FileStorage fs("hand_eye_transform.yml", cv::FileStorage::WRITE);
        fs << "hand_eye_transform" << hand_eye_transform;
        fs.release();
        std::cout << "手眼变换矩阵已保存" << std::endl;
    }

    // 第三步:物体定位测试(接口环境第三应用)
    std::cout << "\n=== 物体定位测试 ===" << std::endl;
    auto test_images = loadImages("test_data/objects");
    auto template_images = loadImages("test_data/templates");

    if (!test_images.empty() && !template_images.empty()) {
        std::vector<cv::Mat*> template_ptrs;
        for (auto& img : template_images) {
            template_ptrs.push_back(&img);
        }

        int template_count = template_ptrs.size();
        int out_x, out_y;
        float out_score;

        bool detect_success = ObjectDetect(&test_images[0], template_ptrs.data(), &template_count, &out_x, &out_y, &out_score);

        if (detect_success) {
            std::cout << "  物体检测成功!" << std::endl;
            std::cout << "检测位置: (" << out_x << ", " << out_y << ")" << std::endl;
            std::cout << "匹配得分: " << out_score << std::endl;

            cv::Mat result_img = test_images[0].clone();
            cv::circle(result_img, cv::Point(out_x, out_y), 15, cv::Scalar(0, 255, 0), 3);
            cv::putText(result_img, "Detected", cv::Point(out_x - 40, out_y - 25),
                cv::FONT_HERSHEY_SIMPLEX, 0.8, cv::Scalar(0, 255, 0), 2);
            cv::imwrite("detection_result.jpg", result_img);
            std::cout << "检测结果已保存: detection_result.jpg" << std::endl;
        }
    }

    // 第四步:颜色识别测试(接口环境第一应用)
    std::cout << "\n=== 颜色识别测试 ===" << std::endl;
    auto color_images = loadImages("test_data/colors");

    for (int i = 0; i < color_images.size() && i < 4; i++) {
        double hsv_result[3];
        bool color_success = RecognizeDominantColorHSV(&color_images[i], hsv_result);

        if (color_success) {
            std::string color_name;
            double h = hsv_result[0];

            // 根据H值判断颜色名称(仅用于接口演示)
            if (h < 10 || h > 170) {
                color_name = "红色(Red)";
            }
            else if (h >= 10 && h < 25) {
                color_name = "橙色(Orange)";
            }
            else if (h >= 25 && h < 35) {
                color_name = "黄色(Yellow)";
            }
            else if (h >= 35 && h < 85) {
                color_name = "绿色(Green)";
            }
            else if (h >= 85 && h < 125) {
                color_name = "蓝色(Blue)";
            }
            else {
                color_name = "紫色(Purple)";
            }

            std::cout << "图像 " << i << " 主色调HSV: ["
                << (int)hsv_result[0] << ", " << (int)hsv_result[1] << ", " << (int)hsv_result[2]
                << "] -> " << color_name << std::endl;
        }
    }

    // 第五步:OCR识别测试(接口环境第一应用)
    std::cout << "\n=== OCR字符识别测试 ===" << std::endl;
    auto ocr_images = loadImages("test_data/ocr");

    for (int i = 0; i < ocr_images.size() && i < 3; i++) {
        char ocr_result[512];
        int max_len = 512;
        bool ocr_success = SimpleOCR(&ocr_images[i], ocr_result, &max_len);

        if (ocr_success) {
            std::cout << "图像 " << i << " 识别结果: \"" << ocr_result << "\"" << std::endl;
        }
    }

    // 第六步:颜色区域查找测试(接口环境第二应用)
    std::cout << "\n=== 颜色区域查找测试 ===" << std::endl;
    if (!color_images.empty()) {
        double target_hsv[3] = { 60, 200, 200 };  // 绿色目标
        double tolerance_hsv[3] = { 10, 50, 50 }; // 容差范围
        cv::Mat mask;
        std::vector<std::vector<cv::Point>> contours;

        bool find_success = FindColorRegionHSV(&color_images[0], target_hsv, tolerance_hsv, &mask, &contours);

        if (find_success && !contours.empty()) {
            std::cout << "找到 " << contours.size() << " 个颜色区域" << std::endl;

            // 保存颜色查找结果
            cv::Mat result_img = color_images[0].clone();
            cv::drawContours(result_img, contours, -1, cv::Scalar(0, 255, 0), 2);
            cv::imwrite("color_find_result.jpg", result_img);
            cv::imwrite("color_mask.jpg", mask);
            std::cout << "颜色查找结果已保存" << std::endl;
        }
    }

    std::cout << "\n=== 测试完成 ===" << std::endl;
    std::cout << "生成的文件:" << std::endl;
    std::cout << "- camera_params.yml (相机内参)" << std::endl;
    std::cout << "- hand_eye_transform.yml (手眼变换矩阵)" << std::endl;
    std::cout << "- detection_result.jpg (物体检测结果)" << std::endl;
    std::cout << "- color_find_result.jpg (颜色查找结果)" << std::endl;
    std::cout << "- color_mask.jpg (颜色掩膜)" << std::endl;

    system("pause");
    return 0;
}

第四阶段:相机标定专项指南

4.1 标定板规格确认

根据你的描述:

  • 标定板尺寸:34cm × 26cm
  • 格子数量:12×9 (11×8个内角点)
  • 每个方格边长:约25mm

4.2 📸 相机内参标定拍摄详细计划表

🎯 拍摄区域划分
图像区域编号:
┌─────────┬─────────┬─────────┐
│   1     │    2    │    3    │  ← 上排
│ 左上角  │  上中   │ 右上角  │
├─────────┼─────────┼─────────┤
│   4     │    5    │    6    │  ← 中排
│ 左中    │  正中   │ 右中    │
├─────────┼─────────┼─────────┤
│   7     │    8    │    9    │  ← 下排
│ 左下角  │  下中   │ 右下角  │
└─────────┴─────────┴─────────┘
📋 详细拍摄计划表
图片编号位置区域标定板位置描述倾斜角度旋转角度距离占图像比例备注说明
calib_01.jpg区域5图像正中央中距离60%基准图像,正对相机
calib_02.jpg区域1左上角中距离65%测试左上角畸变
calib_03.jpg区域3右上角中距离65%测试右上角畸变
calib_04.jpg区域7左下角中距离65%测试左下角畸变
calib_05.jpg区域9右下角中距离65%测试右下角畸变
calib_06.jpg区域2上边中央15°中距离60%轻微向前倾斜
calib_07.jpg区域8下边中央-15°中距离60%轻微向后倾斜
calib_08.jpg区域4左边中央15°中距离60%向右倾斜
calib_09.jpg区域6右边中央-15°中距离60%向左倾斜
calib_10.jpg区域5图像正中央30°中距离55%较大前倾角度
calib_11.jpg区域5图像正中央-30°中距离55%较大后倾角度
calib_12.jpg区域5图像正中央30°中距离55%较大右倾角度
calib_13.jpg区域5图像正中央-30°中距离55%较大左倾角度
calib_14.jpg区域1左上角45°20°近距离80%大角度复合倾斜
calib_15.jpg区域9右下角-45°-20°近距离80%大角度复合倾斜
calib_16.jpg区域5图像正中央近距离85%近距离高分辨率
calib_17.jpg区域5图像正中央远距离40%远距离测试
calib_18.jpg区域3右上角25°45°近距离75%复合角度变化
calib_19.jpg区域7左下角-25°-45°近距离75%复合角度变化
calib_20.jpg区域5图像正中央15°90°中距离65%标定板竖直放置
📐 角度定义说明

倾斜角度(前后倾斜)

  • - 标定板完全平行于图像平面
  • +15°/+30°/+45° - 标定板上边缘向相机方向倾斜
  • -15°/-30°/-45° - 标定板上边缘远离相机方向倾斜

旋转角度(左右旋转)

  • - 标定板水平放置
  • +角度 - 标定板顺时针旋转
  • -角度 - 标定板逆时针旋转
  • 90° - 标定板竖直放置

距离分类

  • 近距离 - 标定板占图像80-85%,距离相机较近
  • 中距离 - 标定板占图像55-65%,标准拍摄距离
  • 远距离 - 标定板占图像40-45%,距离相机较远

4.3 相机标定操作步骤

  1. 环境准备

    • 充足均匀光照,避免阴影
    • 标定板平整,无弯曲变形
    • 相机固定稳定
  2. 拍摄执行

    • 按照上述计划表逐一拍摄
    • 每张照片确保标定板完整清晰
    • 角点检测成功后再拍摄下一张
  3. 质量检查

    • 图像清晰度检查
    • 标定板完整性检查
    • 角点检测成功率检查

第五阶段:手眼标定专项指南

5.1 手眼标定原理

手眼标定用于计算相机坐标系与机器人工具坐标系之间的变换关系,公式为:

T_base_target = T_base_gripper × T_gripper_cam × T_cam_target

5.2 机器人位姿数据格式

支持多种机器人位姿格式,通过修改 ROTATION_FORMAT 配置:

  • 格式1:rx,ry,rz 角度单位 (绕X,Y,Z轴依次旋转)
  • 格式2:rx,ry,rz 弧度单位 (绕X,Y,Z轴依次旋转)
  • 格式3:uvw 角度单位 (绕Z,Y,X轴依次旋转)
  • 格式4:uvw 弧度单位 (绕Z,Y,X轴依次旋转)
  • 格式5:旋转向量 弧度单位 (直接格式)

5.3 手眼标定操作步骤

  1. 准备阶段

    • 完成相机内参标定
    • 准备12×9棋盘格标定板
    • 机器人示教器操作培训
  2. 数据采集

    • 机器人移动到不同位置和姿态
    • 每个位置记录机器人位姿 [x,y,z,rx,ry,rz]
    • 同时拍摄包含标定板的图像
    • 至少采集15组数据
  3. 变换关系验证

    • 检查标定结果的重投影误差
    • 验证变换矩阵的合理性
    • 进行精度测试

第六阶段:物品定位专项指南

6.1 物品定位流程

  1. 模板准备

    • 拍摄清晰的物品模板图像
    • 多角度多光照条件模板
    • 模板图像预处理
  2. 实时检测

    • 图像预处理(灰度化、均衡化)
    • 模板匹配算法
    • 结果后处理和坐标转换
  3. 坐标转换

    // 像素坐标转换为机器人坐标
    cv::Point2f pixel_point(out_x, out_y);
    cv::Point3f world_point = pixel_to_world(pixel_point, camera_matrix, hand_eye_transform);
    

6.2 精度优化建议

  1. 图像质量

    • 充足稳定光照
    • 相机参数优化
    • 减少运动模糊
  2. 算法参数

    • 模板匹配阈值调整
    • 多尺度模板匹配
    • 亚像素精度定位
  3. 系统标定

    • 高精度相机标定
    • 准确的手眼标定
    • 定期标定验证

第七阶段:编译部署指南

7.1 环境变量配置

# 添加到系统 PATH
D:\FTZKAIVision\OpenCV\build\x64\vc16\bin
D:\FTZKAIVision\vcpkg\installed\x64-windows\bin

# 或者在项目属性中配置
# 项目属性 → VC++ 目录 → 可执行文件目录
D:\FTZKAIVision\vcpkg\installed\x64-windows\bin
D:\FTZKAIVision\OpenCV\build\x64\vc16\bin

7.2 依赖库文件

"D:\FTZKAIVision\OpenCV\build\x64\vc16\lib\opencv_world480.lib" 
"D:\FTZKAIVision\vcpkg\installed\x64-windows\bin\leptonica-1.85.0.dll" 
"D:\FTZKAIVision\vcpkg\installed\x64-windows\bin\tesseract55.dll"

7.3 编译步骤

  1. 创建目录结构

    mkdir D:\AIVisionDLL
    mkdir D:\AIVisionDLL\src
    mkdir D:\AIVisionDLL\include  
    mkdir D:\AIVisionDLL\lib
    mkdir D:\FTZKAIVision
    
  2. 配置项目属性

    • 平台:x64
    • 配置:Release
    • 输出类型:动态库(.dll)
  3. 最终部署

    • DLL文件:D:\FTZKAIVision\AIVisionDLL.dll
    • 头文件:D:\AIVisionDLL\include\AIVisionAPI.h

第八阶段:测试验证流程

8.1 单元测试清单

  • ✅ 颜色识别算法测试
  • ✅ 颜色查找算法测试
  • ✅ OCR算法测试
  • ✅ 相机内参标定测试
  • ✅ 手眼标定算法测试
  • ✅ 物品定位算法测试

8.2 集成测试

  1. DLL接口测试

    • 所有函数正确导出
    • 参数传递验证
    • 返回值检查
  2. 性能测试

    • 算法执行时间
    • 内存使用情况
    • 稳定性测试
  3. 精度测试

    • 标定精度评估
    • 定位精度测试
    • 重复性验证

⚠️ 关键注意事项

  1. 接口严格按文档:函数名、参数类型、返回值完全一致
  2. OCR必须用Tesseract:按照官方要求使用指定OCR引擎
  3. 2D坐标系:只输出x,y坐标,符合比赛要求
  4. HSV色彩空间:颜色相关算法统一使用HSV
  5. DLL规范:extern "C"链接,完整异常处理
  6. 标定精度:重投影误差控制在2像素以内
  7. 实时性能:算法执行时间满足实际应用需求

🎯 比赛环节对应

环节一:颜色识别 + 字符识别

  • 使用 RecognizeDominantColorHSV 识别橙色/绿色/红色
  • 使用 SimpleOCR 识别字符串

环节二:指示灯检测 + 旋钮操作

  • 使用 FindColorRegionHSV 查找指示灯颜色
  • 使用 ObjectDetect 定位旋钮位置

环节三:相机标定 + 手眼标定 + 物体抓取

  • 使用 CalibrateCamera 计算相机内参
  • 使用 CalibrateHandEye 计算手眼变换矩阵
  • 使用 ObjectDetect 定位待抓取物品
  • 坐标变换计算机器人抓取位置

通过以上完整的开发、测试和部署流程,可以确保AI视觉DLL满足比赛要求,并在实际应用中稳定可靠地工作。