You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

278 lines
6.4 KiB
C++

#pragma once
#include "GSurface.h"
#include "clipper2\clipper.h"
using Contour = std::vector<cv::Point2f>;
using Contours = std::vector<Contour>;
class ContourUtils
{
public:
//五点三次平滑
static void SmoothContour(Contours& contours, const Contour& border, std::vector<cv::Vec4i> 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<cv::Point2f> 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;
}
};