#include "contours_extractor_impl.hpp" #include #include #include "opencv2/opencv.hpp" #include "opencv2/imgproc/imgproc_c.h" #include "blob/blob.h" #include "blob/BlobDetector.h" #include "blob/BlobGroup.h" #include "blob/BlobContour.h" #include "opencv2/imgproc.hpp" #include "bspline.hpp" #define MIN_VALUE 1e-8 #define IS_DOUBLE_ZERO(d) (abs(d) < MIN_VALUE) namespace cvpr { ContoursExtractorImpl::ContoursExtractorImpl() { } void ContoursExtractorImpl::init() { } void ContoursExtractorImpl::destroy() { } void ContoursExtractorImpl::setAreaThreshold(double threshold) { m_areaThreshold = threshold; } double ContoursExtractorImpl::areaThreshold() { return m_areaThreshold; } void ContoursExtractorImpl::setContourOffset(int32_t offset) { m_contourOffset = offset; } int32_t ContoursExtractorImpl::contourOffset() { return m_contourOffset; } void ContoursExtractorImpl::setApproxPolyEpsilon(double epsilon) { m_approxPolyEpsilon = epsilon; } double ContoursExtractorImpl::approxPolyEpsilon() { return m_approxPolyEpsilon; } void ContoursExtractorImpl::setSmoothMethod(SMOOTH_METHOD method) { m_smoothMethod = method; } SMOOTH_METHOD ContoursExtractorImpl::smoothMethod() { return m_smoothMethod; } void ContoursExtractorImpl::setAngelThreshold(double threshold) { m_angelThreshold = threshold; } double ContoursExtractorImpl::angelThreshold() { return m_angelThreshold; } void ContoursExtractorImpl::setDefectThreshold(double threshold) { m_defectThreshold = threshold; } double ContoursExtractorImpl::defectThreshold() { return m_defectThreshold; } void ContoursExtractorImpl::setSmoothStep(double step) { m_smoothStep = step; } double ContoursExtractorImpl::smoothStep() { return m_smoothStep; } void ContoursExtractorImpl::extract(const cv::Mat& alpha, std::vector>& outputContours) { assert(!alpha.empty()); if (alpha.empty()) { return; } cv::Mat edges; cv::threshold(alpha, edges, 20, 255, cv::THRESH_BINARY); cv::Mat kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(3, 3)); cv::erode(edges, edges, kernel); if (m_contourOffset == 0) { cv::findContours(edges, outputContours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE); return; } #if DEBUG_CONTOURS_EXTRACTOR cv::imshow("edges", edges); cv::waitKey(0); #endif BlobDetector detector(edges); BlobGroup group = detector.getBlobGroup(); group.filter(group, FilterAction::FLT_EXCLUDE, BlobGetArea(), FilterCondition::FLT_LESS, m_areaThreshold); cv::Point center(edges.cols / 2, edges.rows / 2); Blob* centerBlob = group.getBlobNearestTo(center); cv::Point pt = centerBlob->getCentroid(true); for (int i = 0; i < group.getNumBlobs(); i++) { auto* blob = group.getBlob(i); if (blob != centerBlob) { auto center = blob->getCenter(); cv::line(edges, center, pt, cv::Scalar(255, 255, 255), m_contourOffset, 1); } } std::vector> contours; cv::findContours(edges, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE); cv::Mat tmp = cv::Mat::zeros(edges.rows, edges.cols, CV_8UC3); #if DEBUG_CONTOURS_EXTRACTOR cv::drawContours(tmp, contours, 0, cv::Scalar(0, 0, 255), 1); #endif for (int i = 0; i < contours.size(); ++i) { for (int j = 0; j < contours[i].size(); ++j) { cv::circle(tmp, contours[i][j], m_contourOffset, cv::Scalar(255, 255, 255), -1); } } #if DEBUG_CONTOURS_EXTRACTOR cv::imshow("tmp", tmp); cv::waitKey(0); #endif cv::Mat bin; cv::cvtColor(tmp, bin, CV_BGR2GRAY); std::vector> output; cv::findContours(bin, output, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE); #if DEBUG_CONTOURS_EXTRACTOR cv::drawContours(tmp, output, 0, cv::Scalar(0, 0, 255), 1); cv::imshow("tmp", tmp); cv::waitKey(0); #endif if (m_smoothMethod == SMOOTH_METHOD::BSPLINE) { std::vector approxCurve; cv::approxPolyDP(output[0], approxCurve, m_approxPolyEpsilon, false); //output[0].emplace_back(output[0][0]); #if DEBUG_CONTOURS_EXTRACTOR cv::Mat color = cv::imread("C:/Users/ouxia/source/repos/Contour/data/origin-03.png", cv::ImreadModes::IMREAD_UNCHANGED); std::vector> cs; cs.emplace_back(approxCurve); cv::drawContours(color, cs, 0, cv::Scalar(255, 255, 255), 1); std::vector hullPoints; cv::convexHull(approxCurve, hullPoints, true, true); std::vector> hullContours; hullContours.emplace_back(hullPoints); cv::drawContours(color, hullContours, 0, cv::Scalar(0, 255, 0), 1); #endif //std::vector> indices; //getSubCurves(approxCurve, 150.0, indices); //for (const auto& indice : indices) { // if (indice.size() < 3) { // continue; // } // std::vector> psArr; // for (int32_t idx = 0; idx < indice.size(); ++idx) { // std::vector pt; // pt.emplace_back(approxCurve[indice[idx]].x); // pt.emplace_back(approxCurve[indice[idx]].y); // psArr.emplace_back(pt); // } // BSpline spline(psArr, 2); // for (double t = 0.0; t < 1.0; t += 0.05) { // std::vector point = spline.eval(t); // if (point.size() > 1) { // cv::Point pt; // pt.x = point[0]; // pt.y = point[1]; // cv::circle(color, pt, 1, cv::Scalar(0, 0, 255), -1); // } // } //} auto curve = approxCurve; if (!IS_DOUBLE_ZERO(m_defectThreshold)) { std::vector hull; cv::convexHull(approxCurve, hull, true, false); std::vector defects; cv::convexityDefects(approxCurve, hull, defects); for (const auto& defect : defects) { cv::Point startPoint = approxCurve[defect[0]]; cv::Point endPoint = approxCurve[defect[1]]; #if DEBUG_CONTOURS_EXTRACTOR cv::Point farthestPoint = cs[0][defect[2]]; float depth = defect[3] / 256.0; cv::circle(color, startPoint, 5, cv::Scalar(255, 0, 0), -1); cv::circle(color, endPoint, 5, cv::Scalar(255, 127, 0), -1); cv::circle(color, farthestPoint, 5, cv::Scalar(0, 255, 255), -1); std::string depthText = "Depth: " + std::to_string(depth); cv::putText(color, depthText, farthestPoint + cv::Point(0, -10), cv::FONT_HERSHEY_SIMPLEX, 0.4, cv::Scalar(255, 255, 255)); cv::imshow("Convexity Defects", color); #endif int start = -1; int end = -1; for (int i = 0; i < curve.size(); ++i) { if (startPoint == curve[i]) { start = i; } if (endPoint == curve[i]) { end = i; } } if (start != -1 && end != -1 && start != end) { if (euclideanDistance(startPoint, endPoint) < m_defectThreshold) { cv::Point connected = (startPoint + endPoint) / 2; std::vector sub{ connected }; if (start > end) { std::swap(start, end); } replaceSubsequence(curve, start, end, sub); } } } } curve.emplace_back(curve[0]); std::vector> psArr; for (int i = 0; i < curve.size(); ++i) { std::vector pt; pt.emplace_back(curve[i].x); pt.emplace_back(curve[i].y); psArr.emplace_back(pt); } BSpline spline(psArr, 2); if (spline.isValid()) { std::vector weights; std::vector points; for (double t = 0.0; t < 1.0; t += m_smoothStep) { std::vector point = spline.eval(t); if (point.size() > 1) { cv::Point pt; pt.x = point[0]; pt.y = point[1]; points.emplace_back(pt); } } outputContours.emplace_back(points); } else { outputContours = output; } } else { outputContours = output; } #if DEBUG_CONTOURS_EXTRACTOR cv::drawContours(alpha, outputContours, 0, cv::Scalar(255, 255, 255), 1); cv::imshow("alpha", alpha); cv::waitKey(0); #endif } double ContoursExtractorImpl::calculateAngle(const cv::Point& pt1, const cv::Point& pt2, const cv::Point& pt3) { cv::Point vec1 = pt1 - pt2; cv::Point vec2 = pt3 - pt2; double dotProduct = vec1.x * vec2.x + vec1.y * vec2.y; double magnitude1 = std::sqrt(vec1.x * vec1.x + vec1.y * vec1.y); double magnitude2 = std::sqrt(vec2.x * vec2.x + vec2.y * vec2.y); double cosTheta = dotProduct / (magnitude1 * magnitude2); cosTheta = std::clamp(cosTheta, -1.0, 1.0); return std::acos(cosTheta) * 180.0 / CV_PI; } double ContoursExtractorImpl::euclideanDistance(const cv::Point& p1, const cv::Point& p2) { return cv::norm(p1 - p2); } double ContoursExtractorImpl::curveDistance(const std::vector& contour, int startIdx, int endIdx) { double curveDistance = 0.0; int n = contour.size(); for (int i = startIdx; i != endIdx; i = (i + 1) % n) { curveDistance += euclideanDistance(contour[i], contour[(i + 1) % n]); } return curveDistance; } void ContoursExtractorImpl::getSubCurves(const std::vector& contour, double thresholdAngle, std::vector>& indices) { std::set currentGroup; for (size_t i = 0; i < contour.size(); ++i) { const cv::Point& pt1 = contour[(i + contour.size() - 1) % contour.size()]; const cv::Point& pt2 = contour[i]; const cv::Point& pt3 = contour[(i + 1) % contour.size()]; double angle = calculateAngle(pt1, pt2, pt3); if (angle < thresholdAngle) { currentGroup.insert((i + contour.size() - 1) % contour.size()); currentGroup.insert(i); currentGroup.insert((i + 1) % contour.size()); } else if (!currentGroup.empty()) { std::vector vec; for (auto it = currentGroup.begin(); it != currentGroup.end(); ++it) { vec.emplace_back(*it); } indices.push_back(vec); currentGroup.clear(); } } if (!currentGroup.empty()) { std::vector vec; for (auto it = currentGroup.begin(); it != currentGroup.end(); ++it) { vec.emplace_back(*it); } indices.push_back(vec); } } void ContoursExtractorImpl::getOptimalPoints(const std::vector& contour, int& bestIdx1, int& bestIdx2, double& bestCurveDist, double& bestLineDist) { bestCurveDist = 0.0; bestLineDist = std::numeric_limits::max(); int n = contour.size(); for (int i = 0; i < n; ++i) { for (int j = i + 1; j < n; ++j) { double curveDist = curveDistance(contour, i, j); double lineDist = euclideanDistance(contour[i], contour[j]); if (curveDist > bestCurveDist || (curveDist == bestCurveDist && lineDist < bestLineDist)) { bestCurveDist = curveDist; bestLineDist = lineDist; bestIdx1 = i; bestIdx2 = j; } } } } void ContoursExtractorImpl::replaceSubsequence(std::vector& contour, int startIdx, int endIdx, const std::vector& newSubsequence) { int n = contour.size(); if (startIdx < 0 || endIdx < 0 || startIdx >= n || endIdx >= n) { return; } if (startIdx > endIdx) { return; } contour.erase(contour.begin() + startIdx, contour.begin() + endIdx + 1); contour.insert(contour.begin() + startIdx, newSubsequence.begin(), newSubsequence.end()); } cv::Point ContoursExtractorImpl::getPointOnLine(const cv::Point& pt1, const cv::Point& pt2, int32_t x) { double dx = pt2.x - pt1.x; double dy = pt2.y - pt1.y; cv::Point pt; pt.x = pt1.x + x * dx; pt.y = pt1.y + x * dy; return pt; } }