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++
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;
|
|
}
|
|
};
|