添加watershed算法和粒径测量算法
This commit is contained in:
parent
db2ad9337e
commit
bafb08b41e
1105
sourceCode/WD_particleSizeMeasure.cpp
Normal file
1105
sourceCode/WD_particleSizeMeasure.cpp
Normal file
File diff suppressed because it is too large
Load Diff
54
sourceCode/WD_particleSizeMeasure_Export.h
Normal file
54
sourceCode/WD_particleSizeMeasure_Export.h
Normal 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
366
sourceCode/WD_watershed.cpp
Normal 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
|
||||||
Loading…
x
Reference in New Issue
Block a user