找回密码
 立即注册
查看: 195|回复: 0

[简易教程] 【Unity:从零开始搞AR|05】OpenCV识别Aruco位姿(下 ...

[复制链接]
发表于 2022-4-19 09:17 | 显示全部楼层 |阅读模式
本节主要解决的问题是Marker位姿估计及在Unity脚本中接收从dll返回的位姿结果。同时,本节也可看做是C#调用Dll的P/Invoke教程。
Marker位姿估计

我们可以从opencv提供的接口中看到需要的参数:
void cv::aruco::estimatePoseSingleMarkers   (   InputArrayOfArrays  corners,
                                                float   markerLength,
                                                InputArray  cameraMatrix,
                                                InputArray  distCoeffs,
                                                OutputArray     rvecs,
                                                OutputArray     tvecs,
                                                OutputArray     _objPoints = noArray()
)
可见,cameraMatrix及 distCoeffs属于相机内参,也就是说相机需要提前标定。请参考我的标定系列 【相机标定00】目录 ,标定代码请看最后一篇。
markerLength 是marker的实际尺寸。这几个参数是需要做成配置或者通过C#脚本传入(不过传参会比较麻烦)。
位姿返回值的映射

首先是c++中计算位姿的代码:
int DetectMarkerPoses(Color32** rawImage, int width, int height, PoseContainer &container){
    std::vector<int> ids;
    std::vector<std::vector<cv::Point2f>> corners;
    // create an opencv object sharing the same data space
    cv::Mat image(height, width, CV_8UC4, *rawImage);
    cv::Mat imageCopy;
    image.copyTo(imageCopy);
    flip(imageCopy, imageCopy, 0);
    cv::cvtColor(imageCopy, imageCopy, cv::COLOR_RGBA2BGR);
    cv::aruco::detectMarkers(imageCopy, dictionary, corners, ids);

    // if at least one marker detected
    if (ids.size() > 0) {
        std::vector<cv::Vec3d> rvecs, tvecs;
        cv::aruco::estimatePoseSingleMarkers(corners, 0.05, cameraMatrix, distCoeffs, rvecs, tvecs);
        // draw axis for each marker
        for(int i=0; i<ids.size(); i++){
            // 位姿赋值
        }
    }
    return ids.size();
}
estimatePoseSingleMarkers输出的 rvecs(旋转) 和 tvecs(平移)是vector类型,其中每一个 Vec3d 可以看作是3个double的数组。我们增加一个struct,用以存储同一个marker的revc和tvec:
struct Pose
{
    int id;
    double x;
    double y;
    double z;
    double rx;
    double ry;
    double rz;
};
同时再用一个struct来保存Pose的数组:
#define C_SHARP_MAX_OBJECTS 8
struct PoseContainer
{
    Pose poses[C_SHARP_MAX_OBJECTS];
};
赋值:
for (int i = 0; i < ids.size(); i++) {
            if (i < C_SHARP_MAX_OBJECTS)
            {
                auto pose = &container.poses;
                pose->id = ids;
                pose->x = tvecs[0];
                pose->y = tvecs[1];
                pose->z = tvecs[2];
                pose->rx = rvecs[0];
                pose->ry = rvecs[1];
                pose->rz = rvecs[2];
            }
        }
接下来定义计算marker位姿的接口,
extern "C" MY_AR_API int DetectMarkerPoses(Color32 * *rawImage, int width, int height, PoseContainer &poses);
可能读者会有个疑问,为什么用PoseContainer套在一个数组之上,而不是直接返回数组呢?
因为在C#中通过P/Invoke的方式调用非托管代码时,数组本身传递的是内存地址,也就是一个指针,可以通过IntPtr传递,但是操作稍显复杂(其实也还好,就是Pin一块内存),读者可以尝试这样做。
相应地,Unity中的C#脚本也要做对应的映射:
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
    public struct Pose
    {

        /// int
        public int id;

        /// double
        public double x;

        /// double
        public double y;

        /// double
        public double z;

        /// double
        public double rx;

        /// double
        public double ry;

        /// double
        public double rz;
    }

    [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
    public struct PoseContainer
    {

        /// int
        public const int size = 8;

        /// Pose[8]
        [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst = size, ArraySubType = System.Runtime.InteropServices.UnmanagedType.Struct)]
        public Pose[] poses;
    }
方法映射:
[System.Runtime.InteropServices.DllImportAttribute("My-AR-Dll", EntryPoint = "DetectMarkerPoses")]
    public static extern int DetectMarkerPoses(ref Color32[] rawImage, int width, int height, ref PoseContainer poses);
使用时:
PoseContainer container = new PoseContainer();
var count = DetectMarkerPoses(ref image, webCamTexture.width, webCamTexture.height, ref container);
其它辅助内容说明

以上就是获取位姿的核心代码,但仍需要读者做一点辅助工作,比如:

  • 获取相机内参
  • 获取marker的参数
  • marker长度
  • marker的PredefinedDictionaryName
这些不重要但是是必须的,请读者自己完成。
运行结果如下(为了展示暂且把坐标轴画出来了,不然就是单纯的图像,其实是不需要的,示例代码中也没有):



读者可自行检查PoseContainer中的数据,这里就不再展示了。
小结

获取marker位姿的方法为:

  • 相机标定
  • 数据映射
另外,随着代码量的增长,需适时做一点代码重构,可以将代码封装在一个class中,来管理数量众多的参数,比如内参等。
最后,本系列对OpenCV的使用到此就告一段落了(如果之后有时间,我可能会将这个应用部署到Android上。),接下来将是Unity时间,介绍如何将Unity中的3D Object显示在本节获取的位姿上。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-5-8 02:15 , Processed in 0.272478 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表