添加watershed算法和粒径测量算法

This commit is contained in:
jerryzeng 2025-11-08 09:55:15 +08:00
parent db2ad9337e
commit bafb08b41e
3 changed files with 1525 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,54 @@
#pragma once
#if defined(SG_API_LIBRARY)
# define SG_APISHARED_EXPORT __declspec(dllexport)
#else
# define SG_APISHARED_EXPORT __declspec(dllimport)
#endif
#include "SG_baseDataType.h"
#include <vector>
#include <opencv2/opencv.hpp>
#define OUTPUT_DEBUG 1
typedef struct
{
SWD_sizeParam minSize;
SWD_sizeParam alarmSize;
}SWD_paricleSizeParam;
typedef struct
{
SSG_outlierFilterParam filterParam;
SSG_cornerParam cornerParam;
SSG_treeGrowParam growParam;
}SWD_PSM_algoParam;
typedef struct
{
double EQRadius;
SVzNL3DPoint center_pos;
}SWD_ParticlePosInfo;
//计算一个平面调平参数。
//数据输入中可以有一个地平面和参考调平平面,以最高的平面进行调平
//旋转矩阵为调平参数,即将平面法向调整为垂直向量的参数
SG_APISHARED_EXPORT SSG_planeCalibPara wd_getBaseCalibPara(
std::vector< std::vector<SVzNL3DPosition>>& scanLines);
//相机姿态调平,并去除地面
SG_APISHARED_EXPORT void wd_lineDataR(
std::vector< SVzNL3DPosition>& a_line,
const double* camPoseR,
double groundH);
//粒径检测
SG_APISHARED_EXPORT void wd_particleSizeMeasure(
std::vector< std::vector<SVzNL3DPosition>>& scanLines,
const SWD_paricleSizeParam particleSizeParam,
const SSG_planeCalibPara groundCalibPara,
const SWD_PSM_algoParam algoParam,
std::vector<SWD_ParticlePosInfo>& particles,
int* errCode);

366
sourceCode/WD_watershed.cpp Normal file
View File

@ -0,0 +1,366 @@
#if 0
#include <iostream>
#include <vector>
#include <queue>
#include <cmath>
#include <cstring>
#include <fstream>
using namespace std;
// 图像尺寸
const int WIDTH = 256;
const int HEIGHT = 256;
// 8邻域方向
const int dx[] = { -1, -1, -1, 0, 0, 1, 1, 1 };
const int dy[] = { -1, 0, 1, -1, 1, -1, 0, 1 };
// 像素结构
struct Pixel {
int x, y; // 坐标
int value; // 像素值(灰度)
Pixel(int x_, int y_, int v_) : x(x_), y(y_), value(v_) {}
// 优先队列需要的比较函数(小顶堆)
bool operator<(const Pixel& other) const {
return value > other.value; // 注意:优先队列默认是大顶堆,这里反转比较
}
};
// 读取PGM格式图像
bool readPGM(const string& filename, int image[HEIGHT][WIDTH]) {
ifstream file(filename, ios::binary);
if (!file) return false;
string magic;
int w, h, max_val;
file >> magic >> w >> h >> max_val;
if (magic != "P5" || w != WIDTH || h != HEIGHT) return false;
// 跳过换行符
file.ignore(1);
// 读取像素值
unsigned char pixel;
for (int i = 0; i < HEIGHT; ++i) {
for (int j = 0; j < WIDTH; ++j) {
file.read((char*)&pixel, 1);
image[i][j] = (int)pixel;
}
}
return true;
}
// 保存分割结果为PGM图像
void writePGM(const string& filename, int labels[HEIGHT][WIDTH], int num_labels) {
ofstream file(filename, ios::binary);
file << "P5\n" << WIDTH << " " << HEIGHT << "\n255\n";
// 将标签映射到0-255范围
int step = 255 / (num_labels + 1);
for (int i = 0; i < HEIGHT; ++i) {
for (int j = 0; j < WIDTH; ++j) {
unsigned char val = (labels[i][j] + 1) * step;
if (labels[i][j] == -1) val = 0; // 分水岭边界
file.write((char*)&val, 1);
}
}
}
// 分水岭算法实现
int watershed(int image[HEIGHT][WIDTH], int labels[HEIGHT][WIDTH]) {
// 初始化标签:-1表示未标记0表示边界
memset(labels, -1, sizeof(int) * HEIGHT * WIDTH);
// 优先队列(按像素值从小到大处理)
priority_queue<Pixel> pq;
// 标记所有局部最小值作为初始盆地
int current_label = 0;
bool is_min;
// 第一步:找到所有局部最小值并标记
for (int i = 0; i < HEIGHT; ++i) {
for (int j = 0; j < WIDTH; ++j) {
is_min = true;
for (int d = 0; d < 8; ++d) {
int ni = i + dx[d];
int nj = j + dy[d];
if (ni >= 0 && ni < HEIGHT && nj >= 0 && nj < WIDTH) {
if (image[ni][nj] < image[i][j]) {
is_min = false;
break;
}
}
}
if (is_min) {
labels[i][j] = current_label++;
pq.push(Pixel(i, j, image[i][j]));
}
}
}
// 第二步:模拟淹没过程
while (!pq.empty()) {
Pixel p = pq.top();
pq.pop();
int x = p.x;
int y = p.y;
for (int d = 0; d < 8; ++d) {
int nx = x + dx[d];
int ny = y + dy[d];
if (nx >= 0 && nx < HEIGHT && ny >= 0 && ny < WIDTH) {
// 如果邻域像素未标记
if (labels[nx][ny] == -1) {
// 标记为当前区域
labels[nx][ny] = labels[x][y];
pq.push(Pixel(nx, ny, image[nx][ny]));
}
// 如果邻域像素已标记且属于不同区域,则标记为边界
else if (labels[nx][ny] != labels[x][y]) {
labels[x][y] = -1; // 当前像素设为边界
}
}
}
}
return current_label;
}
#if 0
"积水盆地"
使
使
256x256 PGM input.pgm
g++ watershed.cpp - o watershed
. / watershed
output.pgm
4 8
WIDTH和HEIGHT常量
int main() {
int image[HEIGHT][WIDTH];
int labels[HEIGHT][WIDTH];
// 读取输入图像
if (!readPGM("input.pgm", image)) {
cerr << "无法读取图像文件!" << endl;
return -1;
}
// 执行分水岭算法
int num_labels = watershed(image, labels);
cout << "分割完成,共得到 " << num_labels << " 个区域" << endl;
// 保存结果
writePGM("output.pgm", labels, num_labels);
cout << "结果已保存为 output.pgm" << endl;
return 0;
}
#endif
#else
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
#include <fstream>
#include <cmath>
#include "SG_baseDataType.h"
#include "SG_baseAlgo_Export.h"
using namespace std;
#if 0
// 读取PPM格式图像简化版
bool readPPM(const string& filename, Image& img) {
ifstream file(filename, ios::binary);
if (!file) return false;
string magic;
int maxVal;
file >> magic >> img.width >> img.height >> maxVal;
if (magic != "P6") return false; // 仅支持二进制PPM
file.ignore(); // 忽略换行符
vector<unsigned char> data(img.width * img.height * 3);
file.read((char*)data.data(), data.size());
// 转为灰度图Y = 0.299*R + 0.587*G + 0.114*B
img.gray.resize(img.height, vector<int>(img.width));
img.markers.resize(img.height, vector<int>(img.width, 0)); // 初始化标记图为0
for (int i = 0; i < img.height; ++i) {
for (int j = 0; j < img.width; ++j) {
int idx = (i * img.width + j) * 3;
int r = data[idx];
int g = data[idx + 1];
int b = data[idx + 2];
img.gray[i][j] = (int)(0.299 * r + 0.587 * g + 0.114 * b);
}
}
return true;
}
// 保存分割结果为PPM格式
void saveResult(const string& filename, const Image& img) {
ofstream file(filename, ios::binary);
file << "P6\n" << img.width << " " << img.height << "\n255\n";
for (int i = 0; i < img.height; ++i) {
for (int j = 0; j < img.width; ++j) {
if (img.markers[i][j] == -1) { // 分水岭边界(红色)
file.put(255); file.put(0); file.put(0);
}
else { // 区域(根据标记值生成不同颜色)
int color = (img.markers[i][j] * 50) % 256;
file.put(color);
file.put((color + 85) % 256);
file.put((color + 170) % 256);
}
}
}
}
#endif
// 8邻域坐标偏移
const int dx[] = { -1, -1, -1, 0, 0, 1, 1, 1 };
const int dy[] = { -1, 0, 1, -1, 1, -1, 0, 1 };
// 计算局部极小值作为初始种子点
void findMinima(SWD_waterShedImage& img, int& markerCount) {
markerCount = 1; //背景对应的ID
// 遍历每个像素判断是否为局部极小值8邻域内最小
for (int i = 1; i < img.height - 1; ++i) {
for (int j = 1; j < img.width - 1; ++j) {
if (1 == img.markers[i][j]) //背景
continue;
bool isMin = true;
#if 0
for (int d = 0; d < 8; ++d) {
int y = i + dy[d];
int x = j + dx[d];
if (img.gray[y][x] < img.gray[i][j]) {
isMin = false;
break;
}
}
#else
for (int dy = -7; dy < 7; dy++)
{
for (int dx = -7; dx < 7; dx++)
{
int y = i + dy;
int x = j + dx;
if ((x >= 0) && (x < img.width) && (y >= 0) && (y < img.height))
{
if (img.gray[y][x] < img.gray[i][j]) {
isMin = false;
break;
}
}
}
}
#endif
if (isMin) {
// 避免重复标记同一区域的极小值
bool hasMarker = false;
for (int d = 0; d < 8; ++d) {
int y = i + dy[d];
int x = j + dx[d];
if (img.markers[y][x] > 1) {
hasMarker = true;
img.markers[y][x] = img.markers[i][j];
break;
}
}
if (!hasMarker) {
img.markers[i][j] = ++markerCount; // 新种子点
}
}
}
}
}
// 分水岭算法主函数
void watershed(SWD_waterShedImage& img)
{
int markerCount;
findMinima(img, markerCount); // 自动标记种子点
// 按灰度值排序所有像素(模拟水位上升)
vector<pair<int, pair<int, int>>> pixels; // (灰度值, (x,y))
for (int i = 0; i < img.height; ++i) {
for (int j = 0; j < img.width; ++j) {
pixels.emplace_back(img.gray[i][j], make_pair(i, j));
}
}
sort(pixels.begin(), pixels.end());
// 遍历排序后的像素,执行注水过程
for (const auto& p : pixels) {
int x = p.second.first;
int y = p.second.second;
if (img.markers[x][y] > 0) continue; // 已标记的种子点
// 收集邻域已标记的区域
vector<int> neighborMarkers;
for (int d = 0; d < 8; ++d) {
int nx = x + dx[d];
int ny = y + dy[d];
if (nx >= 0 && nx < img.height && ny >= 0 && ny < img.width) {
int m = img.markers[nx][ny];
if (m > 0) {
neighborMarkers.push_back(m);
}
}
}
if (neighborMarkers.empty()) {
continue; // 无邻域标记,暂不处理
}
else if (neighborMarkers.size() == 1) {
img.markers[x][y] = neighborMarkers[0]; // 属于同一区域
}
else {
// 多区域交汇,标记为分水岭
img.markers[x][y] = -1;
}
}
}
#if 0
int main() {
Image img;
if (!readPPM("input.ppm", img)) { // 输入PPM图像
cerr << "无法读取图像!" << endl;
return -1;
}
watershed(img); // 执行分水岭分割
saveResult("watershed_result.ppm", img); // 保存结果
cout << "分割完成,结果已保存为 watershed_result.ppm" << endl;
return 0;
}
#endif
#endif