#pragma once #include "GSurface.h" #include "clipper2\clipper.h" using Contour = std::vector; using Contours = std::vector; class ContourUtils { public: //五点三次平滑 static void SmoothContour(Contours& contours, const Contour& border, std::vector m_hierarchy, int nTimes) { int N = contours.size(); for (int i = 0; i < N; i++) { //一级轮廓线 if (-1 == m_hierarchy[i][3]) SmoothRootContour(contours[i], border, nTimes); else SmoothContour(contours[i], nTimes); } } //平滑跟轮廓线,可能部分在边界上 static void SmoothRootContour(Contour& inputContour, const Contour& border, int nTimes) { if (nTimes < 1 || inputContour.size() < 4) return; TrimContour(inputContour, nTimes); int N = inputContour.size(); //查找inputContour在边界上的部分 int istart = 0; int iend = 0; int i = 0; std::vector subline; while (i < N) { if (!isPtOnBorder(inputContour[i], border)) //找到不在边界上的首点 { istart = i; for (i = istart + 1; i < N; i++) { if (N - 1 == i) { iend = N - 1; break; } else if (isPtOnBorder(inputContour[i], border)) { iend = i - 1; break; } } if (istart > 0) istart--; if (iend < N - 1) iend++; if (iend - istart > 3) //至少5个点 { //平滑不在边界线上的局部曲线[istart,iend] subline.clear(); subline.reserve(iend - istart + 1); for (int k = istart; k <= iend; k++) subline.push_back(inputContour[k]); SmoothContour(subline, nTimes); int t = 0; int nMax = subline.size(); for (int k = istart; k <= iend && t < nMax; k++) { inputContour[k] = subline[t]; t++; } } } i++; } } // 去除冗余点 static void TrimContour(Contour& inputContour,int times) { using namespace Clipper2Lib; double dScale = 100000; Path64 sourcePath; for (cv::Point2f item : inputContour) { sourcePath.push_back(Point64((int64_t)(item.x*dScale), (int64_t)(item.y*dScale))); } int nSmoothScale = (int)(times*0.333); if (nSmoothScale < 1) { nSmoothScale = 1; } Path64 destPath = TrimCollinear(sourcePath, nSmoothScale); inputContour.clear(); // 尝试释放容量 inputContour.reserve(destPath.size()); // 提前分配内存以提高效率 for (Point64 pt : destPath) { inputContour.push_back(cv::Point2f(pt.x / dScale, pt.y / dScale)); } } //点是否在边界线上 static bool isPtOnBorder(const cv::Point &pt, const Contour& border) { for (auto& p : border) { if (fabs(pt.x - p.x) < 0.5 && fabs(pt.y - p.y) < 0.5) return true; } return false; } //平滑轮廓线 static void SmoothContour(Contour& inputContour, int nTimes) { TrimContour(inputContour, nTimes); if (nTimes < 1 || inputContour.size() < 4) return; int N = inputContour.size(); double* x = new double[N]; double* y = new double[N]; double tx, ty; for (int i = 0; i < N; i++) { tx = inputContour[i].x; ty = inputContour[i].y; x[i] = tx; y[i] = ty; } if (N > 4 && nTimes > 0) { Smooth53(x, N, nTimes); Smooth53(y, N, nTimes); } for (int i = 0; i < N; i++) { inputContour[i].x = x[i]; inputContour[i].y = y[i]; } delete[]x; delete[]y; } //五点三次平滑 n 必须大于4 static bool Smooth53(double* val, int n, int stimes) { if (n < 5 || stimes < 1) return false; int i, k; //k为平滑次数 double *xx = new double[n]; for (k = 0; k < stimes; k++) { xx[0] = 69 * val[0] + 4 * val[1] - 6 * val[2] + 4 * val[3] - val[4]; xx[0] = xx[0] / 70; xx[1] = 2 * val[0] + 27 * val[1] + 12 * val[2] - 8 * val[3]; xx[1] = (xx[1] + 2 * val[4]) / 35; for (i = 2; i <= n - 3; i++) { xx[i] = -3 * val[i - 2] + 12 * val[i - 1] + 17 * val[i]; xx[i] = (xx[i] + 12 * val[i + 1] - 3 * val[i + 2]) / 35; } xx[n - 2] = 2 * val[n - 5] - 8 * val[n - 4] + 12 * val[n - 3]; xx[n - 2] = (xx[n - 2] + 27 * val[n - 2] + 2 * val[n - 1]) / 35; xx[n - 1] = -val[n - 5] + 4 * val[n - 4] - 6 * val[n - 3]; xx[n - 1] = (xx[n - 1] + 4 * val[n - 2] + 69 * val[n - 1]) / 70; for (int i = 0; i < n; i++) val[i] = xx[i]; } delete[]xx; return true; } //输出轮廓线 static bool WriteContours(const Contours& contours, const char* path) { FILE* fw = fopen(path, "w"); if (nullptr == fw) return false; float x, y; for (int j = 0; j < contours.size(); j++) { if (contours[j].size() < 3) continue; fprintf(fw, "Pline.%d\n", j); for (auto& p : contours[j]) { x = p.x; y = p.y; fprintf(fw, "%.12g,%.12g\n", x, y); } auto p = contours[j][0]; x = p.x; y = p.y; fprintf(fw, "%.12g,%.12g\n", x, y); fprintf(fw, "\n"); } fclose(fw); return true; } static char* WriteContours(const Contours& contours) { // 初始化一个较小的缓冲区大小 size_t initial_capacity = 1024; // 初始可以存8个点的数据 char* buffer = new char[initial_capacity](); size_t capacity = initial_capacity-1; // 当前缓冲区大小 size_t data_size = 0; const char* newline = "\r\n"; // 插入额外换行符 size_t newline_size = std::strlen(newline); // 换行符的字节大小 // 动态将每个Point数据写入缓冲区 //float x, y; for (int i = 0; i < contours.size(); i++) { // 生成"pline <序号>\n"字符串 std::ostringstream oss; oss << "Pline." << (i + 1) << "\r\n"; // 生成序号 std::string pline_with_number = oss.str(); writeString2Buffer(pline_with_number.c_str(), capacity, data_size, buffer); // 写入所有点 for (auto& p : contours[i]) { // 将当前点的数据(x, y)写入缓冲区 std::ostringstream oss; oss << std::fixed << std::setprecision(6); // 设置保留2位小数 oss << p.x << "," << p.y << "\r\n"; // 转换x和y为字符串 std::string point_str = oss.str(); writeString2Buffer(point_str.c_str(), capacity, data_size, buffer); } // 写入换行符 writeString2Buffer(newline, capacity, data_size, buffer); } return buffer; // 返回最终的序列化数据 } static void writeString2Buffer(const char* src, size_t &capacity, size_t& dataSize, char * &buffer) { size_t sizeSrc = std::strlen(src); size_t required_size = dataSize + sizeSrc; if (required_size >= capacity) { size_t new_capacity = capacity * 2; char* new_buffer = new char[new_capacity + 1](); // 将旧数据复制到新缓冲区 std::memcpy(new_buffer, buffer, dataSize); // 释放旧缓冲区 delete[] buffer; buffer = new_buffer; capacity = new_capacity; } std::memcpy(buffer+ dataSize, src, sizeSrc); dataSize += sizeSrc; } };