-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Advanced Edge Refinement results #4
Comments
Updated code to compute statistics for 0019.png image
#include <ceres/ceres.h>
#include <Eigen/Dense>
#include <apriltags/TagFamily.h>
#include <apriltags/Tag36h11.h>
#include <apriltags/TagDetector.h>
namespace
{
void read_ground_truth(const std::string& filepath, std::map<std::string, std::map<int, std::vector<cv::Point2d>>>& ground_truth)
{
std::ifstream file(filepath);
if (!file.is_open()) {
throw std::invalid_argument("Cannot open ground truth file tags.csv.");
}
std::string line;
file >> line;
while (file >> line) {
std::istringstream ss(line);
std::string token;
std::getline(ss, token, ',');
std::string img = token;
std::getline(ss, token, ',');
int tag_id = std::atoi(token.c_str());
std::getline(ss, token, ',');
std::getline(ss, token, ',');
double ground_truth_x = std::atof(token.c_str());
std::getline(ss, token, ',');
double ground_truth_y = std::atof(token.c_str());
ground_truth[img][tag_id].push_back(cv::Point2d(ground_truth_x, ground_truth_y));
}
}
double point_error(double u1, double v1, double u2, double v2)
{
double u_err = u1 - u2, v_err = v1 - v2;
return sqrt(u_err*u_err + v_err*v_err);
}
class EdgeCostFunctor
{
public:
EdgeCostFunctor(const cv::Mat& rawImg, const cv::Mat& maskImg)
: raw_img(rawImg), mask_img(maskImg)
{
template_img.create(1, 2, CV_8U);
template_img.data[0] = 0;
template_img.data[1] = 255;
}
template<typename T>
T getSubPixel(const cv::Mat& img, cv::Point_<T> pt) const
{
const T x = floor(pt.x);
const T y = floor(pt.y);
const int x0 = cv::borderInterpolate(x, img.cols, cv::BORDER_REPLICATE);
const int x1 = cv::borderInterpolate(x + 1, img.cols, cv::BORDER_REPLICATE);
const int y0 = cv::borderInterpolate(y, img.rows, cv::BORDER_REPLICATE);
const int y1 = cv::borderInterpolate(y + 1, img.rows, cv::BORDER_REPLICATE);
const T a = pt.x - T(x);
const T c = pt.y - T(y);
return (T(img.at<uint8_t>(y0, x0)) * (T(1.0) - a) + T(img.at<uint8_t>(y0, x1)) * a) * (T(1.0) - c) +
(T(img.at<uint8_t>(y1, x0)) * (T(1.0) - a) + T(img.at<uint8_t>(y1, x1)) * a) * c;
}
template<typename T>
bool operator()(const T* const edge_normal_ptr, const T* const edge_offset_ptr, T* residual) const
{
const Eigen::Map<const Eigen::Matrix<T, 2, 1>> edge_normal(edge_normal_ptr);
const double& edge_offset = *edge_offset_ptr;
const Eigen::Hyperplane<T, 2> line(edge_normal, edge_offset);
int residual_index = 0;
for (int row = 0; row < raw_img.rows; row++)
{
const auto raw_img_row_ptr = raw_img.ptr<uint8_t>(row);
const auto mask_row_ptr = mask_img.ptr<uint8_t>(row);
for (int col = 0; col < raw_img.cols; col++)
{
const auto eval = mask_row_ptr[col];
if (eval)
{
const Eigen::Vector2d raw_img_pos(col, row);
const double dist_to_line = line.signedDistance(raw_img_pos);
const ::cv::Point2d template_pt(dist_to_line + 0.5, 0);
const double pred_pixel_value = getSubPixel<double>(template_img, template_pt);
const uint8_t current_pixel_values = raw_img_row_ptr[col];
residual[residual_index] = pred_pixel_value - current_pixel_values;
residual_index++;
}
}
}
return true;
}
private:
cv::Mat raw_img;
cv::Mat mask_img;
cv::Mat template_img;
};
void removeBadTags(std::vector<AprilTags::TagDetection>& tag_detections) noexcept
{
tag_detections.erase(remove_if(begin(tag_detections),
end(tag_detections),
[](AprilTags::TagDetection const& tag) { return tag.good == false; }),
end(tag_detections));
}
void refineCornerPointsByDirectEdgeOptimization(
cv::Mat& img, std::vector<AprilTags::TagDetection>& tag_detections) noexcept
{
for (AprilTags::TagDetection& tag : tag_detections)
{
std::pair<float, float> x_range(std::numeric_limits<float>::max(), std::numeric_limits<float>::min());
std::pair<float, float> y_range(std::numeric_limits<float>::max(), std::numeric_limits<float>::min());
for (std::pair<float, float>& corner_point : tag.p)
{
x_range.first = std::min(x_range.first, corner_point.first);
x_range.second = std::max(x_range.second, corner_point.first);
y_range.first = std::min(y_range.first, corner_point.second);
y_range.second = std::max(y_range.second, corner_point.second);
}
x_range.first = std::max(x_range.first - 10, static_cast<float>(0));
x_range.second = std::min(x_range.second + 10, static_cast<float>(img.cols - 1));
y_range.first = std::max(y_range.first - 10, static_cast<float>(0));
y_range.second = std::min(y_range.second + 10, static_cast<float>(img.rows - 1));
try
{
cv::Rect roi(cv::Point(x_range.first, y_range.first), cv::Point(x_range.second, y_range.second));
cv::Mat cropped_img = img(roi);
std::array<Eigen::Vector2d, 4> estimated_edge_normals;
std::array<double, 4> estimated_edge_offsets;
std::array<cv::Mat, 4> mask_images;
int line_thickness = 5;
auto nextCornerIndex = [](const int i) {
if (i <= 2)
{
return i + 1;
}
else
{
return 0;
}
};
for (int i = 0; i < 4; i++)
{
const int next_corner_index = nextCornerIndex(i);
const Eigen::Vector2d roi_offset_vector(roi.x, roi.y);
const Eigen::Vector2d corner(tag.p[i].first, tag.p[i].second);
const Eigen::Vector2d next_corner(tag.p[next_corner_index].first, tag.p[next_corner_index].second);
const Eigen::Hyperplane<double, 2> edge_line =
Eigen::Hyperplane<double, 2>::Through(corner - roi_offset_vector, next_corner - roi_offset_vector);
estimated_edge_normals[i] = edge_line.normal();
estimated_edge_offsets[i] = edge_line.offset();
mask_images[i].create(cropped_img.rows, cropped_img.cols, CV_8U);
mask_images[i].setTo(0);
const cv::Point2i current_corner_point(std::round(tag.p[i].first - roi.x),
std::round(tag.p[i].second - roi.y));
const cv::Point2i next_corner_point(std::round(tag.p[next_corner_index].first - roi.x),
std::round(tag.p[next_corner_index].second - roi.y));
cv::line(mask_images[i], current_corner_point, next_corner_point, cv::Scalar(255), line_thickness);
cv::rectangle(mask_images[i], current_corner_point, current_corner_point, cv::Scalar(0), 10);
cv::rectangle(mask_images[i], next_corner_point, next_corner_point, cv::Scalar(0), 10);
}
ceres::Problem optimization_problem;
// Set up the only cost function (also known as residual). This uses
// auto-differentiation to obtain the derivative (jacobian).
ceres::NumericDiffOptions numeric_diff_options;
auto addEdgeResidualBlocks = [&optimization_problem,
&mask_images,
&cropped_img,
&estimated_edge_normals,
&estimated_edge_offsets,
&numeric_diff_options,
&nextCornerIndex](const int i) {
const int pixel_count = cv::countNonZero(mask_images[i]);
ceres::CostFunction* cost_function =
new ceres::NumericDiffCostFunction<EdgeCostFunctor, ceres::CENTRAL, ceres::DYNAMIC, 2, 1>(
new EdgeCostFunctor(cropped_img, mask_images[i]),
ceres::TAKE_OWNERSHIP,
pixel_count,
numeric_diff_options);
optimization_problem.AddResidualBlock(
cost_function, nullptr, estimated_edge_normals[i].data(), &estimated_edge_offsets[i]);
optimization_problem.SetParameterization(estimated_edge_normals[i].data(),
new ceres::HomogeneousVectorParameterization(2));
};
addEdgeResidualBlocks(0);
addEdgeResidualBlocks(1);
addEdgeResidualBlocks(2);
addEdgeResidualBlocks(3);
ceres::Solver::Options solve_options;
solve_options.linear_solver_type = ceres::DENSE_QR;
solve_options.max_num_iterations = 100;
ceres::Solver::Summary summary;
ceres::Solve(solve_options, &optimization_problem, &summary);
for (int edge_index = 0; edge_index < 4; edge_index++)
{
const int next_edge_index = nextCornerIndex(edge_index);
const int corner_index = next_edge_index;
const Eigen::Hyperplane<double, 2> edge_A(estimated_edge_normals[edge_index],
estimated_edge_offsets[edge_index]);
const Eigen::Hyperplane<double, 2> edge_B(estimated_edge_normals[next_edge_index],
estimated_edge_offsets[next_edge_index]);
const Eigen::Vector2d estimated_corner_pos_roi = edge_A.intersection(edge_B);
tag.p[corner_index].first = estimated_corner_pos_roi.x() + roi.x;
tag.p[corner_index].second = estimated_corner_pos_roi.y() + roi.y;
}
}
catch (const std::exception& /*e*/)
{
tag.good = false;
}
}
// removeBadTags(tag_detections);
}
struct less_than_key
{
inline bool operator() (const AprilTags::TagDetection& det1, const AprilTags::TagDetection& det2)
{
return (det1.id < det2.id);
}
};
}
int main(int argc, char** argv)
{
AprilTags::TagDetector apriltag_cpp_detector(AprilTags::TagCodes(AprilTags::tagCodes36h11));
cv::Mat img_gray = cv::imread("0019.png", cv::IMREAD_GRAYSCALE);
std::vector<AprilTags::TagDetection> tag_detections = apriltag_cpp_detector.extractTags(img_gray);
std::sort(tag_detections.begin(), tag_detections.end(), less_than_key());
std::cout << "tag_detections: " << tag_detections.size() << std::endl;
std::vector<AprilTags::TagDetection> tag_detections_refined = tag_detections;
refineCornerPointsByDirectEdgeOptimization(img_gray, tag_detections_refined);
std::map<std::string, std::map<int, std::vector<cv::Point2d>>> ground_truth;
read_ground_truth("tags.csv", ground_truth);
const std::string imageFilename = "0019.png";
std::cout << "ground_truth: " << (ground_truth.find(imageFilename) != ground_truth.end())
<< " ; ground_truth: " << ground_truth.size() << std::endl;
cv::Mat img = cv::imread("0019.png");
double error_detection_all = 0.0, error_detection_refined_all = 0.0;
int nb_corners = 0;
for (size_t i = 0; i < tag_detections.size(); i++) {
if (tag_detections[i].good) {
std::cout << "\nTag " << tag_detections[i].id << ":" << std::endl;
for (size_t j = 0; j < 4; j++, nb_corners++) {
cv::line(img, cv::Point(tag_detections[i].p[j].first, tag_detections[i].p[j].second),
cv::Point(tag_detections[i].p[(j+1) % 4].first, tag_detections[i].p[(j+1) % 4].second),
cv::Scalar(0,0,255));
cv::line(img, cv::Point(tag_detections_refined[i].p[j].first, tag_detections_refined[i].p[j].second),
cv::Point(tag_detections_refined[i].p[(j+1) % 4].first, tag_detections_refined[i].p[(j+1) % 4].second),
cv::Scalar(0,255,0));
cv::Point2d corner = ground_truth[imageFilename][tag_detections[i].id][j];
double error_detection = point_error(tag_detections[i].p[j].first, tag_detections[i].p[j].second, corner.x, corner.y);
error_detection_all += error_detection;
std::cout << "detection: " << tag_detections[i].p[j].first << ", " << tag_detections[i].p[j].second << " ; error="
<< error_detection << std::endl;
double error_detection_refined = point_error(tag_detections_refined[i].p[j].first, tag_detections_refined[i].p[j].second, corner.x, corner.y);
error_detection_refined_all += error_detection_refined;
std::cout << "detection refined: " << tag_detections_refined[i].p[j].first << ", " << tag_detections_refined[i].p[j].second << " ; error="
<< error_detection_refined << std::endl;
std::cout << "ground truth: " << corner.x << ", " << corner.y << std::endl;
}
}
}
std::cout << "\nMean error detection: " << (error_detection_all / nb_corners) << std::endl;
std::cout << "Mean error detection refined: " << (error_detection_refined_all / nb_corners) << std::endl;
cv::imshow("Detections", img);
cv::waitKey();
return 0;
} It gives:
With my custom image, I am also not getting good results: code
#include <ceres/ceres.h>
#include <Eigen/Dense>
#include <apriltags/TagFamily.h>
#include <apriltags/Tag36h11.h>
#include <apriltags/TagDetector.h>
namespace
{
class EdgeCostFunctor
{
public:
EdgeCostFunctor(const cv::Mat& rawImg, const cv::Mat& maskImg)
: raw_img(rawImg), mask_img(maskImg)
{
template_img.create(1, 2, CV_8U);
template_img.data[0] = 0;
template_img.data[1] = 255;
}
template<typename T>
T getSubPixel(const cv::Mat& img, cv::Point_<T> pt) const
{
const T x = floor(pt.x);
const T y = floor(pt.y);
const int x0 = cv::borderInterpolate(x, img.cols, cv::BORDER_REPLICATE);
const int x1 = cv::borderInterpolate(x + 1, img.cols, cv::BORDER_REPLICATE);
const int y0 = cv::borderInterpolate(y, img.rows, cv::BORDER_REPLICATE);
const int y1 = cv::borderInterpolate(y + 1, img.rows, cv::BORDER_REPLICATE);
const T a = pt.x - T(x);
const T c = pt.y - T(y);
return (T(img.at<uint8_t>(y0, x0)) * (T(1.0) - a) + T(img.at<uint8_t>(y0, x1)) * a) * (T(1.0) - c) +
(T(img.at<uint8_t>(y1, x0)) * (T(1.0) - a) + T(img.at<uint8_t>(y1, x1)) * a) * c;
}
template<typename T>
bool operator()(const T* const edge_normal_ptr, const T* const edge_offset_ptr, T* residual) const
{
const Eigen::Map<const Eigen::Matrix<T, 2, 1>> edge_normal(edge_normal_ptr);
const double& edge_offset = *edge_offset_ptr;
const Eigen::Hyperplane<T, 2> line(edge_normal, edge_offset);
int residual_index = 0;
for (int row = 0; row < raw_img.rows; row++)
{
const auto raw_img_row_ptr = raw_img.ptr<uint8_t>(row);
const auto mask_row_ptr = mask_img.ptr<uint8_t>(row);
for (int col = 0; col < raw_img.cols; col++)
{
const auto eval = mask_row_ptr[col];
if (eval)
{
const Eigen::Vector2d raw_img_pos(col, row);
const double dist_to_line = line.signedDistance(raw_img_pos);
const ::cv::Point2d template_pt(dist_to_line + 0.5, 0);
const double pred_pixel_value = getSubPixel<double>(template_img, template_pt);
const uint8_t current_pixel_values = raw_img_row_ptr[col];
residual[residual_index] = pred_pixel_value - current_pixel_values;
residual_index++;
}
}
}
return true;
}
private:
cv::Mat raw_img;
cv::Mat mask_img;
cv::Mat template_img;
};
void removeBadTags(std::vector<AprilTags::TagDetection>& tag_detections) noexcept
{
tag_detections.erase(remove_if(begin(tag_detections),
end(tag_detections),
[](AprilTags::TagDetection const& tag) { return tag.good == false; }),
end(tag_detections));
}
void refineCornerPointsByDirectEdgeOptimization(
cv::Mat& img, std::vector<AprilTags::TagDetection>& tag_detections) noexcept
{
for (AprilTags::TagDetection& tag : tag_detections)
{
std::pair<float, float> x_range(std::numeric_limits<float>::max(), std::numeric_limits<float>::min());
std::pair<float, float> y_range(std::numeric_limits<float>::max(), std::numeric_limits<float>::min());
for (std::pair<float, float>& corner_point : tag.p)
{
x_range.first = std::min(x_range.first, corner_point.first);
x_range.second = std::max(x_range.second, corner_point.first);
y_range.first = std::min(y_range.first, corner_point.second);
y_range.second = std::max(y_range.second, corner_point.second);
}
x_range.first = std::max(x_range.first - 10, static_cast<float>(0));
x_range.second = std::min(x_range.second + 10, static_cast<float>(img.cols - 1));
y_range.first = std::max(y_range.first - 10, static_cast<float>(0));
y_range.second = std::min(y_range.second + 10, static_cast<float>(img.rows - 1));
try
{
cv::Rect roi(cv::Point(x_range.first, y_range.first), cv::Point(x_range.second, y_range.second));
cv::Mat cropped_img = img(roi);
std::array<Eigen::Vector2d, 4> estimated_edge_normals;
std::array<double, 4> estimated_edge_offsets;
std::array<cv::Mat, 4> mask_images;
int line_thickness = 5;
auto nextCornerIndex = [](const int i) {
if (i <= 2)
{
return i + 1;
}
else
{
return 0;
}
};
for (int i = 0; i < 4; i++)
{
const int next_corner_index = nextCornerIndex(i);
const Eigen::Vector2d roi_offset_vector(roi.x, roi.y);
const Eigen::Vector2d corner(tag.p[i].first, tag.p[i].second);
const Eigen::Vector2d next_corner(tag.p[next_corner_index].first, tag.p[next_corner_index].second);
const Eigen::Hyperplane<double, 2> edge_line =
Eigen::Hyperplane<double, 2>::Through(corner - roi_offset_vector, next_corner - roi_offset_vector);
estimated_edge_normals[i] = edge_line.normal();
estimated_edge_offsets[i] = edge_line.offset();
mask_images[i].create(cropped_img.rows, cropped_img.cols, CV_8U);
mask_images[i].setTo(0);
const cv::Point2i current_corner_point(std::round(tag.p[i].first - roi.x),
std::round(tag.p[i].second - roi.y));
const cv::Point2i next_corner_point(std::round(tag.p[next_corner_index].first - roi.x),
std::round(tag.p[next_corner_index].second - roi.y));
cv::line(mask_images[i], current_corner_point, next_corner_point, cv::Scalar(255), line_thickness);
cv::rectangle(mask_images[i], current_corner_point, current_corner_point, cv::Scalar(0), 10);
cv::rectangle(mask_images[i], next_corner_point, next_corner_point, cv::Scalar(0), 10);
}
ceres::Problem optimization_problem;
// Set up the only cost function (also known as residual). This uses
// auto-differentiation to obtain the derivative (jacobian).
ceres::NumericDiffOptions numeric_diff_options;
auto addEdgeResidualBlocks = [&optimization_problem,
&mask_images,
&cropped_img,
&estimated_edge_normals,
&estimated_edge_offsets,
&numeric_diff_options,
&nextCornerIndex](const int i) {
const int pixel_count = cv::countNonZero(mask_images[i]);
ceres::CostFunction* cost_function =
new ceres::NumericDiffCostFunction<EdgeCostFunctor, ceres::CENTRAL, ceres::DYNAMIC, 2, 1>(
new EdgeCostFunctor(cropped_img, mask_images[i]),
ceres::TAKE_OWNERSHIP,
pixel_count,
numeric_diff_options);
optimization_problem.AddResidualBlock(
cost_function, nullptr, estimated_edge_normals[i].data(), &estimated_edge_offsets[i]);
optimization_problem.SetParameterization(estimated_edge_normals[i].data(),
new ceres::HomogeneousVectorParameterization(2));
};
addEdgeResidualBlocks(0);
addEdgeResidualBlocks(1);
addEdgeResidualBlocks(2);
addEdgeResidualBlocks(3);
ceres::Solver::Options solve_options;
solve_options.linear_solver_type = ceres::DENSE_QR;
solve_options.max_num_iterations = 100;
ceres::Solver::Summary summary;
ceres::Solve(solve_options, &optimization_problem, &summary);
for (int edge_index = 0; edge_index < 4; edge_index++)
{
const int next_edge_index = nextCornerIndex(edge_index);
const int corner_index = next_edge_index;
const Eigen::Hyperplane<double, 2> edge_A(estimated_edge_normals[edge_index],
estimated_edge_offsets[edge_index]);
const Eigen::Hyperplane<double, 2> edge_B(estimated_edge_normals[next_edge_index],
estimated_edge_offsets[next_edge_index]);
const Eigen::Vector2d estimated_corner_pos_roi = edge_A.intersection(edge_B);
tag.p[corner_index].first = estimated_corner_pos_roi.x() + roi.x;
tag.p[corner_index].second = estimated_corner_pos_roi.y() + roi.y;
}
}
catch (const std::exception& e)
{
std::cerr << "Exception: " << e.what() << std::endl;
tag.good = false;
}
}
// removeBadTags(tag_detections);
}
struct less_than_key
{
inline bool operator() (const AprilTags::TagDetection& det1, const AprilTags::TagDetection& det2)
{
return (det1.id < det2.id);
}
};
void loadGroundTruth(const std::string& path, int idx, cv::Matx31d& rvec, cv::Matx31d& tvec)
{
char buffer[256];
sprintf(buffer, path.c_str(), idx);
std::string filename = buffer;
std::ifstream file(filename);
cv::Matx44d T;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
file >> T(i,j);
}
}
cv::Matx33d R;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
R(i,j) = T(i,j);
}
tvec(i) = T(i,3);
}
cv::Rodrigues(R, rvec);
}
std::vector<cv::Point3d> buildTagPoints(double size)
{
std::vector<cv::Point3d> pts;
double tag_size_2 = (size * 8/10.0) / 2.0;
pts.push_back(cv::Point3d(-tag_size_2, -tag_size_2, 0));
pts.push_back(cv::Point3d( tag_size_2, -tag_size_2, 0));
pts.push_back(cv::Point3d( tag_size_2, tag_size_2, 0));
pts.push_back(cv::Point3d(-tag_size_2, tag_size_2, 0));
return pts;
}
double pointError(double u1, double v1, double u2, double v2)
{
double u_err = u1 - u2, v_err = v1 - v2;
return sqrt(u_err*u_err + v_err*v_err);
}
}
int main(int argc, char** argv)
{
AprilTags::TagDetector apriltag_cpp_detector(AprilTags::TagCodes(AprilTags::tagCodes36h11));
std::string imageFilepath = "scene_36h11.png";
cv::Mat img = cv::imread(imageFilepath);
cv::Mat img_gray;
cv::cvtColor(img, img_gray, cv::COLOR_BGR2GRAY);
std::vector<AprilTags::TagDetection> tag_detections = apriltag_cpp_detector.extractTags(img_gray);
std::sort(tag_detections.begin(), tag_detections.end(), less_than_key());
std::cout << "tag_detections: " << tag_detections.size() << std::endl;
cv::Matx33d cameraMatrix = cv::Matx33d::eye();
cameraMatrix(0,0) = 2100;
cameraMatrix(1,1) = 2100;
cameraMatrix(0,2) = 960;
cameraMatrix(1,2) = 540;
std::vector<std::vector<cv::Point3d>> tagsPoints;
const double scale = 8.0/10.0;
double tagSize[5] = {0.25, 0.25, 0.25, 0.125, 0.125};
tagsPoints.push_back(buildTagPoints(0.25));
tagsPoints.push_back(buildTagPoints(0.25));
tagsPoints.push_back(buildTagPoints(0.25));
tagsPoints.push_back(buildTagPoints(0.125));
tagsPoints.push_back(buildTagPoints(0.125));
std::vector<cv::Matx31d> rvecs, tvecs;
std::vector<std::vector<cv::Point2d>> tagsImagePoints;
for (size_t i = 0; i < 5; i++) {
cv::Matx31d rvec, tvec;
loadGroundTruth("cMo%d.txt", i+1, rvec, tvec);
rvecs.push_back(rvec);
tvecs.push_back(tvec);
std::vector<cv::Point2d> imagePoints;
cv::projectPoints(tagsPoints[i], rvec, tvec, cameraMatrix, cv::noArray(), imagePoints);
tagsImagePoints.push_back(imagePoints);
}
// for (size_t i = 0; i < tag_detections.size(); i++) {
// for (size_t j = 0; j < 4; j++) {
// cv::line(img, cv::Point(tag_detections[i].p[j].first, tag_detections[i].p[j].second),
// cv::Point(tag_detections[i].p[(j+1) % 4].first, tag_detections[i].p[(j+1) % 4].second),
// cv::Scalar(0, 0, 255));
// }
// }
std::vector<AprilTags::TagDetection> tag_detections_refined = tag_detections;
refineCornerPointsByDirectEdgeOptimization(img_gray, tag_detections_refined);
for (size_t i = 0; i < tag_detections_refined.size(); i++) {
std::cout << "\nDetection: " << tag_detections[i].id << std::endl;
for (size_t j = 0; j < 4; j++) {
std::cout << j << ") " << "pt: " << tag_detections[i].p[j].first << " " << tag_detections[i].p[j].second << std::endl;
std::cout << j << ") " << "pt refined: " << tag_detections_refined[i].p[j].first << " "
<< tag_detections_refined[i].p[j].second << std::endl;
std::cout << j << ") " << "true pt: " << tagsImagePoints[i][j].x << " " << tagsImagePoints[i][j].y << std::endl;
std::cout << j << ") " << "error: " << pointError(tag_detections[i].p[j].first, tag_detections[i].p[j].second,
tagsImagePoints[i][j].x, tagsImagePoints[i][j].y) << std::endl;
std::cout << j << ") " << "error refined: " << pointError(tag_detections_refined[i].p[j].first, tag_detections_refined[i].p[j].second,
tagsImagePoints[i][j].x, tagsImagePoints[i][j].y) << std::endl;
cv::line(img, cv::Point(tag_detections_refined[i].p[j].first, tag_detections_refined[i].p[j].second),
cv::Point(tag_detections_refined[i].p[(j+1) % 4].first, tag_detections_refined[i].p[(j+1) % 4].second),
cv::Scalar(0, 255, 0));
}
}
cv::imshow("Image", img);
cv::waitKey();
cv::imwrite("detections_refine_direct_edge.png", img);
return 0;
} Ground truth poses: Results:
It is even worse with badly illuminated scene where it can diverge (look at the top most tag): |
Any idea why I could not reproduce the paper results? I have used the provided code without trying to fine tune the parameters. Ceres-solver version is |
Hi! Thank you for your detailed description of the problem. Unfortunately I am extremely busy this week and have no time to look at the topic. I will try to find some time and have a closer look at it next week. |
Any chance to confirm or disprove my findings? |
Hi, sorry for the delay, I'll jump in here. The inconsistencies you experience result from different pixel definitions as described in section III.A of our paper. Unfortunately, the ground truth and the detection algorithm do follow a different convention here. You need to subtract 0.5 from the x and y positions of the ground truth (e.g., in line 32 and 35 of your updated code) to evaluate the error correctly. As a result, your first example will give:
So the refined tag poses are significantly more accurate than the ones returned by AprilTags C++. |
Thanks for your answer. I will try to test it on my data when I will found some free time. |
Thanks, subtracting But what about my Blender test case? I am using Blender to generate the images. I am retrieving the ground truth camera pose and the camera intrinsic parameters. How to compare tag coordinates from the AprilTag algorithm + your refined strategy with the ground truth corners coordinates computed from perspective projection? What about the pose computation step? Do we have to add an offset on pixel coordinates before doing pose computation(e.g. |
From your implementation, it seems that you are assuming a black-white image for the refinement method to work well. Your refinement method may fail completely for dark images. Also line assumption is not valid for images with large distortion. |
Hi,
I am interested by the "Advanced Edge Refinement" function since the paper states a median error of
0.017 px
.But I am not able to get good results using the
refineCornerPointsByDirectEdgeOptimization
function.For instance, I am testing the refine code with
0019.png
from therandom.zip
dataset.Please see the code I am using if there is any obvious error I would have made:
0019.png
:C++ code (collapsed block, click on the arrow)
This is mostly copy-paste code but comparing the corners coordinates between refined and ground truth data shows not good results. For instance:
What did I miss since it should be mostly copy-paste code? Any issue or subtility with the ground truth data?
The text was updated successfully, but these errors were encountered: