Autonomy Software C++ 24.5.1
Welcome to the Autonomy Software repository of the Mars Rover Design Team (MRDT) at Missouri University of Science and Technology (Missouri S&T)! API reference contains the source code and other resources for the development of the autonomy software for our Mars rover. The Autonomy Software project aims to compete in the University Rover Challenge (URC) by demonstrating advanced autonomous capabilities and robust navigation algorithms.
Loading...
Searching...
No Matches
ArucoDetection.hpp
1
12#ifndef ARUCO_DETECTION_HPP
13#define ARUCO_DETECTION_HPP
14
15#include "../../AutonomyLogging.h"
16#include "../../util/vision/ImageOperations.hpp"
17
19#include <opencv2/imgproc.hpp>
21#include <opencv2/opencv.hpp>
22#include <vector>
23
25
26
33namespace arucotag
34{
35
43 struct ArucoTag
44 {
45 public:
46 // Declare public struct member attributes.
47 cv::Point2f CornerTL; // The top left corner of the bounding box.
48 cv::Point2f CornerTR; // The top right corner of the bounding box.
49 cv::Point2f CornerBL; // The bottom left corner of the bounding box.
50 cv::Point2f CornerBR; // The bottom right corner of bounding box.
51 int nID; // ID of the tag.
52 int nHits; // Total number of detections for tag id.
53 int nFramesSinceLastHit; // The total number of frames since a tag with this ID was last detected.
54 double dStraightLineDistance; // Distance between the tag and the camera.
55 double dYawAngle; // This is the yaw angle so roll and pitch are ignored.
56 };
57
58
67 inline cv::Point2f FindTagCenter(const ArucoTag& stTag)
68 {
69 // Average of the four corners
70 cv::Point2f cvCenter(0, 0);
71
72 // Add each tag x, y to the center x, y.
73 cvCenter.x += stTag.CornerBL.x + stTag.CornerBR.x + stTag.CornerTL.x + stTag.CornerTR.x;
74 cvCenter.y += stTag.CornerBL.y + stTag.CornerBR.y + stTag.CornerTL.y + stTag.CornerTR.y;
75 // Divide by number of corners.
76 cvCenter.x /= 4;
77 cvCenter.y /= 4;
78
79 // Return a copy of the center point of the tag.
80 return cvCenter;
81 }
82
83
96 inline void PreprocessFrame(const cv::Mat& cvInputFrame, cv::Mat& cvOutputFrame)
97 {
98 // Check if the input frame is in BGR format.
99 if (cvInputFrame.channels() != 3)
100 {
101 // Submit logger message.
102 LOG_ERROR(logging::g_qSharedLogger, "PreprocessFrame() requires a BGR image.");
103 return;
104 }
105
106 // Grayscale.
107 cv::cvtColor(cvInputFrame, cvOutputFrame, cv::COLOR_BGR2GRAY);
108 cv::filter2D(cvOutputFrame, cvOutputFrame, -1, constants::ARUCO_SHARPEN_KERNEL_EXTRA);
109 // Reduce number of colors/gradients in the image.
110 imgops::ColorReduce(cvOutputFrame);
111 // Denoise (Looks like bilateral filter is req. for ArUco, check speed since docs say it's slow)
112 // cv::bilateralFilter(cvInputFrame, cvInputFrame, /*diameter =*/5, /*sigmaColor =*/0.2, /*sigmaSpace =*/3);
113 // imgops::CustomBilateralFilter(cvInputFrame, 3, 0.1, 3);
114 // Deblur? (Would require determining point spread function that caused the blur)
115
116 // Threshold mask (could use OTSU or TRIANGLE, just a const threshold for now)
117 // cv::threshold(cvInputFrame, cvInputFrame, constants::ARUCO_PIXEL_THRESHOLD, constants::ARUCO_PIXEL_THRESHOLD_MAX_VALUE, cv::THRESH_BINARY);
118
119 // Super-Resolution
120 // std::string szModelPath = "ESPCN_x3.pb";
121 // std::string szModelName = "espcn";
122 // dnn_superres::DnnSuperResImpl dnSuperResModel;
123 // dnSuperResModel.readModel(/*path*/);
124 // dnSuperResModel.setModel(/*path, scale*/);
125 // dnSuperResModel.upsample(cvInputFrame, cvOutputFrame);
126 }
127
128
140 inline std::vector<ArucoTag> Detect(const cv::Mat& cvFrame, const cv::aruco::ArucoDetector& cvArucoDetector)
141 {
143 std::vector<int> vIDs;
144 std::vector<std::vector<cv::Point2f>> cvMarkerCorners, cvRejectedCandidates;
145
146 // Run Aruco detection algorithm.
147 cvArucoDetector.detectMarkers(cvFrame, cvMarkerCorners, vIDs, cvRejectedCandidates);
148
149 // Store all of the detected tags as ArucoTag.
150 std::vector<ArucoTag> vDetectedTags;
151 vDetectedTags.reserve(vIDs.size());
152
153 // Loop through each detection and build tag for it.
154 for (long unsigned int unIter = 0; unIter < vIDs.size(); unIter++)
155 {
156 // Create and initialize new tag.
157 ArucoTag stDetectedTag;
158 stDetectedTag.nID = vIDs[unIter];
159 // Copy corners.
160 stDetectedTag.CornerTL = cvMarkerCorners[unIter][0];
161 stDetectedTag.CornerTR = cvMarkerCorners[unIter][1];
162 stDetectedTag.CornerBR = cvMarkerCorners[unIter][2];
163 stDetectedTag.CornerBL = cvMarkerCorners[unIter][3];
164
165 // Add new tag to detected tags vector.
166 vDetectedTags.push_back(stDetectedTag);
167 }
168
169 // Return the detected tags.
170 return vDetectedTags;
171 }
172
173
185 inline void DrawDetections(cv::Mat& cvDetectionsFrame, const std::vector<ArucoTag>& vDetectedTags)
186 {
187 // Create instance variables.
188 std::vector<int> vIDs;
189 std::vector<std::vector<cv::Point2f>> cvMarkers;
190
191 // Loop through each of the given AR tags and repackage them so that the draw function can read them.
192 for (long unsigned int nIter = 0; nIter < vDetectedTags.size(); ++nIter)
193 {
194 // Append tag ID.
195 vIDs.emplace_back(vDetectedTags[nIter].nID);
196
197 // Assemble vector of marker corners.
198 std::vector<cv::Point2f> cvMarkerCorners;
199 cvMarkerCorners.emplace_back(vDetectedTags[nIter].CornerTL);
200 cvMarkerCorners.emplace_back(vDetectedTags[nIter].CornerTR);
201 cvMarkerCorners.emplace_back(vDetectedTags[nIter].CornerBR);
202 cvMarkerCorners.emplace_back(vDetectedTags[nIter].CornerBL);
203 // Append vector of marker corners.
204 cvMarkers.emplace_back(cvMarkerCorners);
205 }
206
207 // Check if the given frame is a 1 or 3 channel image. (not BGRA)
208 if (!cvDetectionsFrame.empty() && (cvDetectionsFrame.channels() == 1 || cvDetectionsFrame.channels() == 3))
209 {
210 // Draw markers onto normal given image.
211 cv::aruco::drawDetectedMarkers(cvDetectionsFrame, cvMarkers, vIDs, cv::Scalar(0, 0, 0));
212 }
213 else
214 {
215 // Submit logger message.
216 LOG_ERROR(logging::g_qSharedLogger,
217 "ArucoDetect: Unable to draw markers on image because it is empty or because it has {} channels. (Should be 1 or 3)",
218 cvDetectionsFrame.channels());
219 }
220 }
221
222
234 inline void EstimatePoseFromPointCloud(const cv::Mat& cvPointCloud, ArucoTag& stTag)
235 {
236 // Confirm correct coordinate system.
237 if (constants::ZED_COORD_SYSTEM != sl::COORDINATE_SYSTEM::LEFT_HANDED_Y_UP)
238 {
239 // Submit logger message.
240 LOG_CRITICAL(logging::g_qSharedLogger, "ArucoDetection: Calculations won't work for anything other than ZED coordinate system == LEFT_HANDED_Y_UP");
241 }
242
243 // Find the center point of the given tag.
244 cv::Point2f cvCenter = FindTagCenter(stTag);
245
246 // Ensure the detected center is inside the domain of the point cloud.
247 if (cvCenter.y > cvPointCloud.rows || cvCenter.x > cvPointCloud.cols || cvCenter.y < 0 || cvCenter.x < 0)
248 {
249 LOG_ERROR(logging::g_qSharedLogger,
250 "Detected tag center ({}, {}) out of point cloud's domain ({},{})",
251 cvCenter.y,
252 cvCenter.x,
253 cvPointCloud.rows,
254 cvPointCloud.cols);
255 return;
256 }
257
258 // Get tag center point location relative to the camera. Point cloud location stores float x, y, z, BGRA.
259 cv::Vec4f cvCoordinate = cvPointCloud.at<cv::Vec4f>(cvCenter.y, cvCenter.x);
260 float fForward = cvCoordinate[2]; // Z
261 float fRight = cvCoordinate[0]; // X
262 float fUp = cvCoordinate[1]; // Y
263
264 // Calculate euclidean distance from ZED camera left eye to the point of interest
265 stTag.dStraightLineDistance = sqrt(pow(fForward, 2) + pow(fRight, 2) + pow(fUp, 2));
266
267 // Calculate the angle on plane horizontal to the viewpoint
268 stTag.dYawAngle = atan2(fRight, fForward);
269 }
270
271
281 inline void EstimatePoseFromPNP(cv::Mat& cvCameraMatrix, cv::Mat& cvDistCoeffs, ArucoTag& stTag)
282 {
283 // rotVec is how the tag is orientated with respect to the camera. It's 3 numbers defining an axis of rotation around which we rotate the angle which is the
284 // euclidean distance of the vector. transVec is the XYZ translation of the tag from the camera if you image the convergence of light as a pinhole sitting at
285 // (0,0,0) in space.
286 cv::Vec3d cvRotVec, cvTransVec;
287
288 // Set expected object coordinate system shape.
289 cv::Mat cvObjPoints(4, 1, CV_32FC3);
290 cvObjPoints.at<cv::Vec3f>(0) = cv::Vec3f{0, 0, 0}; // Top-left corner.
291 cvObjPoints.at<cv::Vec3f>(1) = cv::Vec3f{constants::ARUCO_TAG_SIDE_LENGTH, 0, 0}; // Bottom-left corner.
292 cvObjPoints.at<cv::Vec3f>(2) = cv::Vec3f{0, constants::ARUCO_TAG_SIDE_LENGTH, 0}; // Top-right corner.
293 cvObjPoints.at<cv::Vec3f>(3) = cv::Vec3f{constants::ARUCO_TAG_SIDE_LENGTH, constants::ARUCO_TAG_SIDE_LENGTH, 0}; // Bottom-right corner.
294
295 // Repackage tag image points into a mat.
296 cv::Mat cvImgPoints(4, 1, CV_32FC3);
297 cvImgPoints.at<cv::Vec3f>(0) = cv::Vec3f{stTag.CornerTL.x, stTag.CornerTL.y, 0}; // Top-left corner.
298 cvImgPoints.at<cv::Vec3f>(1) = cv::Vec3f{stTag.CornerBL.x, stTag.CornerBL.y, 0}; // Bottom-left corner.
299 cvImgPoints.at<cv::Vec3f>(2) = cv::Vec3f{stTag.CornerTR.x, stTag.CornerTR.y, 0}; // Top-right corner.
300 cvImgPoints.at<cv::Vec3f>(3) = cv::Vec3f{stTag.CornerBR.x, stTag.CornerBR.y, 0}; // Bottom-right corner.
301
302 // Use solve perspective n' point algorithm to estimate pose of the tag.
303 cv::solvePnP(cvObjPoints, cvImgPoints, cvCameraMatrix, cvDistCoeffs, cvRotVec, cvTransVec);
304
305 // Grab (x,y,z) coordinates from where the tag was detected
306 double dForward = cvTransVec[2];
307 double dRight = cvTransVec[0];
308 double dUp = cvTransVec[1];
309
310 // Calculate euclidean distance from ZED camera left eye to the point of interest
311 stTag.dStraightLineDistance = std::sqrt(std::pow(dForward, 2) + std::pow(dRight, 2) + std::pow(dUp, 2));
312
313 // Calculate the angle on plane horizontal to the viewpoint
314 stTag.dYawAngle = std::atan2(dRight, dForward);
315 }
316} // namespace arucotag
317
318#endif
int channels() const
_Tp & at(int i0=0)
bool empty() const
void detectMarkers(InputArray image, OutputArrayOfArrays corners, OutputArray ids, OutputArrayOfArrays rejectedImgPoints=noArray()) const
bool solvePnP(InputArray objectPoints, InputArray imagePoints, InputArray cameraMatrix, InputArray distCoeffs, OutputArray rvec, OutputArray tvec, bool useExtrinsicGuess=false, int flags=SOLVEPNP_ITERATIVE)
void sqrt(InputArray src, OutputArray dst)
void pow(InputArray src, double power, OutputArray dst)
#define CV_32FC3
__device__ __forceinline__ float1 atan2(const uchar1 &a, const uchar1 &b)
void cvtColor(InputArray src, OutputArray dst, int code, int dstCn=0)
COLOR_BGR2GRAY
void filter2D(InputArray src, OutputArray dst, int ddepth, InputArray kernel, Point anchor=Point(-1,-1), double delta=0, int borderType=BORDER_DEFAULT)
void drawDetectedMarkers(InputOutputArray image, InputArrayOfArrays corners, InputArray ids=noArray(), Scalar borderColor=Scalar(0, 255, 0))
Namespace containing functions related to ArUco operations on images.
Definition ArucoDetection.hpp:34
void EstimatePoseFromPointCloud(const cv::Mat &cvPointCloud, ArucoTag &stTag)
Estimate the pose of a position with respect to the observer using a point cloud.
Definition ArucoDetection.hpp:234
cv::Point2f FindTagCenter(const ArucoTag &stTag)
Given an ArucoTag struct find the center point of the corners.
Definition ArucoDetection.hpp:67
std::vector< ArucoTag > Detect(const cv::Mat &cvFrame, const cv::aruco::ArucoDetector &cvArucoDetector)
Detect ArUco tags in the provided image.
Definition ArucoDetection.hpp:140
void DrawDetections(cv::Mat &cvDetectionsFrame, const std::vector< ArucoTag > &vDetectedTags)
Given a vector of ArucoTag structs draw each tag corner and ID onto the given image.
Definition ArucoDetection.hpp:185
void PreprocessFrame(const cv::Mat &cvInputFrame, cv::Mat &cvOutputFrame)
Preprocess images for specifically for the Aruco Detect() method. This method creates a copy of the c...
Definition ArucoDetection.hpp:96
void EstimatePoseFromPNP(cv::Mat &cvCameraMatrix, cv::Mat &cvDistCoeffs, ArucoTag &stTag)
Estimate the pose of a position with respect to the observer using an image.
Definition ArucoDetection.hpp:281
void ColorReduce(cv::Mat &cvFrame, int nDiv=64)
Given an image and a divisor, divide the precision of the elements.
Definition ImageOperations.hpp:295
Represents a single ArUco tag. Stores all information about a specific tag detection.
Definition ArucoDetection.hpp:44