|
本节主要解决的问题是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 &#34;C&#34; 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(&#34;My-AR-Dll&#34;, EntryPoint = &#34;DetectMarkerPoses&#34;)]
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显示在本节获取的位姿上。 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|