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
Geolocate.hpp
Go to the documentation of this file.
1
12#ifndef GEOLOCATE_HPP
13#define GEOLOCATE_HPP
14
15#include "../../AutonomyLogging.h"
16#include "../GeospatialOperations.hpp"
17#include "../NumberOperations.hpp"
18
20#include <algorithm>
21#include <cmath>
22#include <numeric>
23#include <opencv2/opencv.hpp>
24#include <vector>
25
27
28
35namespace geoloc
36{
37
57 inline geoops::Waypoint GeolocateBox(const cv::Mat& cvPointcloud,
58 const geoops::RoverPose& stCameraPose,
59 const cv::Point& cvPixel,
60 int nNeighborhoodSize = 5,
61 float fDepthPercentileTarget = 0.20f,
62 float fDepthToleranceMeters = 0.5f)
63 {
64 // Ensure neighborhood size is at least 1.
65 if (nNeighborhoodSize < 1)
66 {
67 LOG_WARNING(logging::g_qSharedLogger, "GeolocateBox: Invalid neighborhood size {}, defaulting to 5x5", nNeighborhoodSize);
68 nNeighborhoodSize = 5; // Default to 5x5 neighborhood if invalid.
69 }
70
71 // Get the camera's current UTM position.
72 const geoops::UTMCoordinate& stCameraUTM = stCameraPose.GetUTMCoordinate();
73
74 // Get the half-width of neighborhood for window calculations.
75 int nHalfSize = nNeighborhoodSize / 2;
76
77 // Create containers for raw valid points in the neighborhood.
78 std::vector<cv::Vec4f> vRawPoints;
79 std::vector<float> vRawZ;
80
81 // Loop through the neighborhood window to extract initial valid points.
82 for (int nOffsetY = -nHalfSize; nOffsetY <= nHalfSize; ++nOffsetY)
83 {
84 for (int nOffsetX = -nHalfSize; nOffsetX <= nHalfSize; ++nOffsetX)
85 {
86 // Calculate the current pixel coordinates in the neighborhood.
87 int nCurrY = cvPixel.y + nOffsetY;
88 int nCurrX = cvPixel.x + nOffsetX;
89
90 // Skip out-of-bounds pixels.
91 if (nCurrY < 0 || nCurrY >= cvPointcloud.rows || nCurrX < 0 || nCurrX >= cvPointcloud.cols)
92 {
93 continue;
94 }
95
96 // Access the 3D point at this pixel.
97 cv::Vec4f cvPoint = cvPointcloud.at<cv::Vec4f>(nCurrY, nCurrX);
98
99 // Check if the point is valid. (not zero or NaN)
100 if (cvPoint[2] > 0 && std::isfinite(cvPoint[0]) && std::isfinite(cvPoint[1]) && std::isfinite(cvPoint[2]))
101 {
102 vRawPoints.push_back(cvPoint);
103 vRawZ.push_back(cvPoint[2]);
104 }
105 }
106 }
107
108 // Containers for the average point calculation.
109 float fAvgX = 0.0f;
110 float fAvgY = 0.0f;
111 float fAvgZ = 0.0f;
112
113 // Final container for the filtered target points (used for radius calculation later)
114 std::vector<float> vX, vY, vZ, vDistances;
115
116 // Handle edge case: No valid points found (Stereovision failure)
117 if (vRawZ.empty())
118 {
119 LOG_DEBUG(logging::g_qSharedLogger, "GeolocateBox: Stereovision failed for pixel ({}, {}). Attempting Monocular Fallback.", cvPixel.x, cvPixel.y);
120
121 // MONOCULAR GROUND PLANE FALLBACK.
122 // Calculate the geometric intersection of the pixel with the ground plane.
123 int nBottomY = std::min(cvPointcloud.rows - 1, cvPixel.y + nHalfSize);
124
125 // Assume typical pinhole focal properties based on image dimensions
126 float fFy = cvPointcloud.rows / 2.0f;
127 float fCy = cvPointcloud.rows / 2.0f;
128
129 // Extract camera height off the ground from the current UTM altitude offset
130 float fCameraHeight = static_cast<float>(stCameraUTM.dAltitude);
131
132 float fRayAngleY = atan2(nBottomY - fCy, fFy);
133 float fTotalAngle = fRayAngleY; // Assuming level physical pitch since it's accounted for in the pose.
134
135 // If the total angle is too small, the point is likely on or above the horizon, which is unreliable for geolocation.
136 if (fTotalAngle <= 0.01f)
137 {
138 LOG_DEBUG(logging::g_qSharedLogger, "GeolocateBox: Fallback failed. Object is on or above the horizon line.");
139 return geoops::Waypoint(); // Total failure.
140 }
141
142 fAvgZ = fCameraHeight / tan(fTotalAngle);
143 fAvgX = ((cvPixel.x - (cvPointcloud.cols / 2.0f)) / fFy) * fAvgZ;
144 fAvgY = -fCameraHeight; // ZED Y is UP, ground is negative height.
145 }
146 else
147 {
148 // ROBUST STATISTICAL DEPTH EXTRACTION.
149 // Sort Z values to find the requested percentile to isolate the target object.
150 std::sort(vRawZ.begin(), vRawZ.end());
151 size_t unPercentileIndex = static_cast<size_t>(vRawZ.size() * fDepthPercentileTarget);
152 float fTargetZ = vRawZ[unPercentileIndex];
153
154 // Calculate the centroid using ONLY points around the target Z depth.
155 for (const auto& cvPoint : vRawPoints)
156 {
157 if (std::abs(cvPoint[2] - fTargetZ) <= fDepthToleranceMeters)
158 {
159 vX.push_back(cvPoint[0]);
160 vY.push_back(cvPoint[1]);
161 vZ.push_back(cvPoint[2]);
162 }
163 }
164
165 if (!vX.empty())
166 {
167 fAvgX = std::accumulate(vX.begin(), vX.end(), 0.0f) / vX.size();
168 fAvgY = std::accumulate(vY.begin(), vY.end(), 0.0f) / vY.size();
169 fAvgZ = std::accumulate(vZ.begin(), vZ.end(), 0.0f) / vZ.size();
170 }
171 else
172 {
173 // Edge case catch if tolerance strictly filtered everything (rare).
174 fAvgX = vRawPoints[unPercentileIndex][0];
175 fAvgY = vRawPoints[unPercentileIndex][1];
176 fAvgZ = vRawPoints[unPercentileIndex][2];
177 }
178 }
179
180 // Adjust camera degree heading to match unit circle 0 position.
181 double dAdjustedHeading = numops::InputAngleModulus((stCameraPose.GetCompassHeading() * -1.0) + 90.0, 0.0, 360.0);
182 // Convert camera heading to radians. (0 = North, CW positive)
183 double dHeadingRad = dAdjustedHeading * M_PI / 180.0;
184
185 // Transform camera coordinates to global UTM coordinates.
186 // NOTE: ZED uses left-handed Y-up coordinate system, so:
187 // Camera +X = Right = East when camera faces North
188 // Camera +Y = Up = not used for horizontal positioning
189 // Camera +Z = Forward = North when camera faces North
190
191 // Rotate by camera's heading and translate by camera's position.
192 double dEasting = stCameraUTM.dEasting + (fAvgZ * cos(dHeadingRad) + fAvgX * sin(dHeadingRad));
193 double dNorthing = stCameraUTM.dNorthing + (fAvgZ * sin(dHeadingRad) - fAvgX * cos(dHeadingRad));
194
195 // Calculate object altitude based on the camera's altitude and the object's relative Y coordinate.
196 double dAltitude = stCameraUTM.dAltitude + fAvgY; // Y is up in camera frame.
197
198 // Create a UTM coordinate for the object.
199 geoops::UTMCoordinate stObjectUTM(dEasting, dNorthing, stCameraUTM.nZone, stCameraUTM.bWithinNorthernHemisphere, dAltitude);
200
201 // Estimate the object's radius from the spread of target points.
202 double dRadius = 0.0;
203 if (vX.size() > 1)
204 {
205 // Calculate Euclidean distance from each point to the centroid.
206 for (size_t siI = 0; siI < vX.size(); ++siI)
207 {
208 double dX = vX[siI] - fAvgX;
209 double dY = vY[siI] - fAvgY;
210 double dZ = vZ[siI] - fAvgZ;
211 vDistances.push_back(sqrt(dX * dX + dY * dY + dZ * dZ));
212 }
213
214 // Use median distance as radius estimate to be robust against outliers.
215 std::sort(vDistances.begin(), vDistances.end());
216 if (vDistances.size() % 2 == 0)
217 {
218 dRadius = (vDistances[vDistances.size() / 2 - 1] + vDistances[vDistances.size() / 2]) / 2.0;
219 }
220 else
221 {
222 dRadius = vDistances[vDistances.size() / 2];
223 }
224
225 // Apply a scaling factor to better approximate the object's boundary.
226 dRadius *= 1.5; // Empirical scale factor.
227 }
228
229 // Create and return the waypoint with the object's UTM coordinates and radius.
230 return geoops::Waypoint(stObjectUTM, geoops::WaypointType::eUNKNOWN, dRadius);
231 }
232} // namespace geoloc
233
234#endif // GEOLOCATE_HPP
_Tp & at(int i0=0)
void sqrt(InputArray src, OutputArray dst)
__device__ __forceinline__ float1 atan2(const uchar1 &a, const uchar1 &b)
__device__ __forceinline__ float1 cos(const uchar1 &a)
__device__ __forceinline__ float4 sin(const uchar4 &a)
__device__ __forceinline__ float3 tan(const uchar3 &a)
Namespace containing functions related to geolocation of objects within camera frames and conversion ...
Definition Geolocate.hpp:36
geoops::Waypoint GeolocateBox(const cv::Mat &cvPointcloud, const geoops::RoverPose &stCameraPose, const cv::Point &cvPixel, int nNeighborhoodSize=5, float fDepthPercentileTarget=0.20f, float fDepthToleranceMeters=0.5f)
Converts pixel coordinates in a ZED2i pointcloud into global UTM coordinates, given the camera's curr...
Definition Geolocate.hpp:57
constexpr T InputAngleModulus(T tValue, T tMinValue, T tMaxValue)
Calculates the modulus of an input angle.
Definition NumberOperations.hpp:162
This struct is used by the WaypointHandler to provide an easy way to store all pose data about the ro...
Definition GeospatialOperations.hpp:708
double GetCompassHeading() const
Accessor for the Compass Heading private member.
Definition GeospatialOperations.hpp:787
const geoops::UTMCoordinate & GetUTMCoordinate() const
Accessor for the geoops::UTMCoordinate member variable.
Definition GeospatialOperations.hpp:767
This struct stores/contains information about a UTM coordinate.
Definition GeospatialOperations.hpp:211
This struct is used by the WaypointHandler class to store location, size, and type information about ...
Definition GeospatialOperations.hpp:423