基于C++、GDAL、OpenCV的矢量数据骨架线提取算法

news2024/11/24 1:14:39

基于C++、GDAL、OpenCV的矢量数据骨架线提取算法

CGAL已经实现了该功能,但由于CGAL依赖于Boost库,编译后过大,因此本文所采用的这套方式实现骨架线提取功能。

效果:
骨架线提取效果

思路:
1、将导入shp按照要素逐一拆分成新的shp
2、将所有拆分后的shp分别转栅格,利用OpenCV提取骨架线
3、将所有骨架线转为shp,并合并输出

详细代码如下:

调用

    basePolygonAlgorithm::SkeletonExtractor extract2; 
	extract2.polygon2Skelton("originFile.shp", "outputFile.shp");

.h

#include "opencv2/core/core.hpp"
#include "opencv2/opencv.hpp"
#include"opencv2/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp" 
#include "ogrsf_frmts.h"
#include "gdal_priv.h"
#include "ogr_geometry.h"
#include "ogr_attrind.h"
#include "ogr_srs_api.h" 

		//提取骨架线
		class SkeletonExtractor
		{
		public:
			SkeletonExtractor();
			void polygon2Skelton(string src_path, string dst_path);//src_path待提取数据  dst_path:提取后数据
			;
			
		private:
			cv::Mat thinning(cv::Mat img);//Mat骨架线提取
			cv::Mat readRasterData(GDALDataset* pDstDataset);//pDstDataset转Mat
			void polygon2subPolygon(const string& src_path, int* featureNum, double adfGeoTransform[6]);
			void subPolygon2skelton(const string& dst_path, const int& featureNum, double adfGeoTransform[6]);
			void mergeShp(vector<string>vecSrcFiles, string pszDstFile);
			void writeShp(string dst_path, cv::Mat skel, double adfGeoTransform[], OGRSpatialReference* oSRS);
		};

.cpp

        SkeletonExtractor::SkeletonExtractor()
		{
		}

		void SkeletonExtractor::polygon2Skelton(string src_path, string dst_path)
		{
			int featureNum = 0;
			double adfGeoTransform[6];
			polygon2subPolygon(src_path, &featureNum, adfGeoTransform);
			subPolygon2skelton(dst_path, featureNum, adfGeoTransform);
			return;
		}
		
		cv::Mat SkeletonExtractor::thinning(cv::Mat img)
		{
			cv::Mat skel(img.size(), CV_8UC1, cv::Scalar(0));
			cv::Mat temp(img.size(), CV_8UC1);

			cv::Mat element = cv::getStructuringElement(cv::MORPH_CROSS, cv::Size(3, 3));

			bool done;
			do
			{
				cv::morphologyEx(img, temp, cv::MORPH_OPEN, element);
				cv::bitwise_not(temp, temp);
				cv::bitwise_and(img, temp, temp);
				cv::bitwise_or(skel, temp, skel);
				cv::erode(img, img, element);

				double maxVal = 0;
				cv::minMaxLoc(img, nullptr, &maxVal);
				done = (maxVal == 0);
			} while (!done);
 
			img.release();
			temp.release();
			return skel;  
		}

		cv::Mat SkeletonExtractor::readRasterData(GDALDataset* pDstDataset)
		{
			// Read the first band of the raster dataset
			GDALRasterBand* pBand = pDstDataset->GetRasterBand(1);

			// Allocate memory for the raster data
			int nXSize = pBand->GetXSize();
			int nYSize = pBand->GetYSize();
			GByte* pData = new GByte[nXSize * nYSize];

			// Read the raster data
			pBand->RasterIO(GF_Read, 0, 0, nXSize, nYSize, pData, nXSize, nYSize, GDT_Byte, 0, 0);
			//转化为Mat
			cv::Mat _Mymat(nYSize, nXSize, CV_8UC1);
			for (int i = 0; i < nXSize; i++)
			{
				for (int j = 0; j < nYSize; j++)
				{
					_Mymat.at<uchar>(j, i) = (uchar)pData[j * nXSize + i];
				}
			}
			// 执行骨架化
			cv::Mat skel;
			skel = thinning(_Mymat);

			// 定义结构元素(如3x3的矩形)
			int morph_size = 1;
			cv::Mat element = cv::getStructuringElement(cv::MORPH_RECT,
				cv::Size(2 * morph_size + 1, 2 * morph_size + 1),
				cv::Point(morph_size, morph_size));

			// 膨胀操作
			cv::dilate(skel, skel, element);

			// 腐蚀操作
			cv::erode(skel, skel, element);

			delete[] pData;
			return skel;
		}

		void SkeletonExtractor::polygon2subPolygon(const string& src_path, int* featureNum, double adfGeoTransform[6])
		{
			// Register GDAL drivers
			GDALAllRegister();
			OGRRegisterAll();
			//	NULL, NULL, NULL)); 
			GDALDataset* pSrcDataset = (GDALDataset*)GDALOpenEx(src_path.c_str(), GDAL_OF_VECTOR, NULL, NULL, NULL);
			// The one layer.
			OGRLayer* pSrcLayer = pSrcDataset->GetLayer(0);
			OGRSpatialReference* oSRS = NULL;
			oSRS = new OGRSpatialReference;
			oSRS = pSrcLayer->GetSpatialRef();
			*featureNum = pSrcLayer->GetFeatureCount();
			pSrcDataset->GetGeoTransform(adfGeoTransform);
			for (int i = 0; i < *featureNum; i++) {
				// 获取要素
				OGRFeature* pSrcFeature = pSrcLayer->GetFeature(i);

				// 创建新的GDALDataset和图层
				char outputFilename[256];
				sprintf(outputFilename, "temp_%d.shp", i);
				GDALDriver* pDriver = GetGDALDriverManager()->GetDriverByName("ESRI Shapefile");
				GDALDataset* temp_pDstDataset = pDriver->Create(outputFilename, 0, 0, 0, GDT_Unknown, NULL);
				OGRLayer* pDstLayer = temp_pDstDataset->CreateLayer(pSrcLayer->GetName(), oSRS, pSrcLayer->GetGeomType(), NULL);

				// 复制要素到新的图层中
				OGRFeature* pDstFeature = pSrcFeature->Clone();
				pDstLayer->CreateFeature(pDstFeature);
				OGRFeature::DestroyFeature(pDstFeature);
				GDALFlushCache(temp_pDstDataset);
				// 释放资源
				GDALClose(temp_pDstDataset);
				OGRFeature::DestroyFeature(pSrcFeature);
			}

			GDALClose(pSrcDataset); 
		}

		void SkeletonExtractor::mergeShp(vector<string> vecSrcFiles, string pszDstFile)
		{
			GDALAllRegister();

			// 获取Shapefile驱动
			GDALDriver* poDriver = GetGDALDriverManager()->GetDriverByName("ESRI Shapefile");
			if (poDriver == nullptr) {
				cerr << "ESRI Shapefile driver not available." << endl;
				return;
			}

			// 创建目标shapefile
			GDALDataset* poDstDS = poDriver->Create(pszDstFile.c_str(), 0, 0, 0, GDT_Unknown, nullptr);
			if (poDstDS == nullptr) {
				cerr << "Creation of output file failed." << endl;
				return;
			}

			OGRLayer* poDstLayer = nullptr;

			// 遍历所有源shapefile
			for (const auto& srcFile : vecSrcFiles) {
				GDALDataset* poSrcDS = static_cast<GDALDataset*>(GDALOpenEx(srcFile.c_str(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr));

				if (poSrcDS == nullptr) {
					cerr << "Open failed: " << srcFile << endl;
					continue;
				}

				OGRLayer* poSrcLayer = poSrcDS->GetLayer(0);
				if (poSrcLayer == nullptr) {
					cerr << "Failed to get layer from source file: " << srcFile << endl;
					GDALClose(poSrcDS);
					continue;
				}

				// 如果目标图层不存在,则根据源图层创建一个
				if (poDstLayer == nullptr) {
					poDstLayer = poDstDS->CreateLayer(poSrcLayer->GetName(), poSrcLayer->GetSpatialRef(), poSrcLayer->GetGeomType(), nullptr);
					if (poDstLayer == nullptr) {
						cerr << "Failed to create destination layer." << endl;
						GDALClose(poSrcDS);
						GDALClose(poDstDS);
						return;
					}

					// 复制属性表结构
					OGRFeatureDefn* poSrcFDefn = poSrcLayer->GetLayerDefn();
					for (int i = 0; i < poSrcFDefn->GetFieldCount(); ++i) {
						poDstLayer->CreateField(poSrcFDefn->GetFieldDefn(i));
					}
				}

				// 复制要素
				OGRFeature* poFeature;
				poSrcLayer->ResetReading();
				while ((poFeature = poSrcLayer->GetNextFeature()) != nullptr) {
					OGRFeature* poDstFeature = OGRFeature::CreateFeature(poDstLayer->GetLayerDefn());
					poDstFeature->SetFrom(poFeature);
					poDstFeature->SetGeometry(poFeature->GetGeometryRef());

					if (poDstLayer->CreateFeature(poDstFeature) != OGRERR_NONE) {
						cerr << "Failed to create feature in destination shapefile." << endl;
						OGRFeature::DestroyFeature(poFeature);
						OGRFeature::DestroyFeature(poDstFeature);
						GDALClose(poSrcDS);
						GDALClose(poDstDS);
						return;
					}

					OGRFeature::DestroyFeature(poFeature);
					OGRFeature::DestroyFeature(poDstFeature);
				}

				GDALClose(poSrcDS);
			}

			GDALClose(poDstDS);
		}

		void SkeletonExtractor::writeShp(string dst_path, cv::Mat skel, double adfGeoTransform[], OGRSpatialReference* oSRS)
		{
			GDALAllRegister();
			OGRRegisterAll();
			GDALDriver* poDriver = GetGDALDriverManager()->GetDriverByName("ESRI Shapefile");
			GDALDataset* poDS = poDriver->Create(dst_path.c_str(), 0, 0, 0, GDT_Unknown, NULL);
			OGRLayer* poLayer = poDS->CreateLayer("layer", oSRS, wkbUnknown, NULL);
			std::vector<cv::Vec4i> hierarchy;
			std::vector<std::vector<cv::Point>> contours;
			cv::findContours(skel, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);
			OGRMultiLineString multiLineString;
			OGRFeature* poFeature = OGRFeature::CreateFeature(poLayer->GetLayerDefn());
			for (int i = 0; i < contours.size(); i++)
			{
				OGRLineString lineString;
				for (int j = 0; j < contours[i].size(); j++)
				{
					double x = adfGeoTransform[0] + contours[i][j].x * adfGeoTransform[1] + contours[i][j].y * adfGeoTransform[2];
					double y = adfGeoTransform[3] + contours[i][j].x * adfGeoTransform[4] + contours[i][j].y * adfGeoTransform[5];
					lineString.addPoint(x, y);
				}
				multiLineString.addGeometry(&lineString);
			}
			poFeature->SetGeometry(&multiLineString);
			skelton_result = poFeature->GetGeometryRef()->clone();//clone将skelton_result复制到一个新的 OGRGeometry 对象中,防止因poFeature释放而变成空
			poLayer->CreateFeature(poFeature);
			OGRFeature::DestroyFeature(poFeature);
			GDALClose(poDS);
			hierarchy.clear();
			contours.clear(); 
		}

		void SkeletonExtractor::subPolygon2skelton(const string& dst_path, const int& featureNum, double adfGeoTransform[6])
		{
			GDALAllRegister();
			OGRRegisterAll();

			//栅格参数设置
			char** argv = NULL;
			//nodata
			argv = CSLAddString(argv, "-a_nodata");
			argv = CSLAddString(argv, "-9999");
			
            //初始值
			argv = CSLAddString(argv, "-init");
			argv = CSLAddString(argv, "0");

            //分辨率
			argv = CSLAddString(argv, "-ts");
			argv = CSLAddString(argv, "1024");
			argv = CSLAddString(argv, "1024");

            //存储类型
			argv = CSLAddString(argv, "-ot");
			argv = CSLAddString(argv, "Byte");

			GDALRasterizeOptions* pOptions = GDALRasterizeOptionsNew(argv, NULL);
			OGRSpatialReference* oSRS = NULL;
			vector<string>filePaths;
			for (int i = 0; i < featureNum; i++) { 
				//重新打开新的SHP文件
				char outputFilename[256];
				sprintf(outputFilename, "temp_%d.shp", i);
				GDALDataset* pNewDS = (GDALDataset*)GDALOpenEx(outputFilename, GDAL_OF_VECTOR, NULL, NULL, NULL);
				OGRLayer* pNewLayer = pNewDS->GetLayer(0);
				oSRS = new OGRSpatialReference;
				oSRS = pNewLayer->GetSpatialRef();
				//获取转换6参数
				int pbUsageError = FALSE;
				GDALDataset* pImageDataset = static_cast<GDALDataset*>(GDALRasterize("output.tif", NULL, pNewDS, pOptions, &pbUsageError));

				double adfGeoTransform[6];
				pImageDataset->GetGeoTransform(adfGeoTransform);

				//提取骨架线
				cv::Mat skel = readRasterData(pImageDataset);

				//写入结果
				outputFilename[256];
				sprintf(outputFilename, "res_%d.shp", i);
				writeShp(outputFilename, skel, adfGeoTransform, oSRS);
				filePaths.push_back(outputFilename);
				GDALClose(pNewDS);
				GDALClose(pImageDataset);
				skel.release();
			}

			// Close the datasets
			GDALRasterizeOptionsFree(pOptions);

			//合并shp
			mergeShp(filePaths, dst_path);

			//删除临时文件
			for (int i = 0; i < featureNum; i++) {
				char tempFile[256];
				char resFile[256];
				sprintf(tempFile, "temp_%d.shp", i);
				std::remove(tempFile);
				sprintf(tempFile, "temp_%d.shx", i);
				std::remove(tempFile);
				sprintf(tempFile, "temp_%d.dbf", i);
				std::remove(tempFile);
				sprintf(tempFile, "temp_%d.prj", i);
				std::remove(tempFile);

				sprintf(resFile, "res_%d.shp", i);
				std::remove(resFile);
				sprintf(resFile, "res_%d.shx", i);
				std::remove(resFile);
				sprintf(resFile, "res_%d.dbf", i);
				std::remove(resFile);
				sprintf(resFile, "res_%d.prj", i);
				std::remove(resFile);
			}
			std::remove("output.tif");
			return;
		}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/721734.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java中如何将一个集合list转成以逗号隔开的字符串

事例代码 代码&#xff1a; package com.air.app;import java.util.ArrayList; import java.util.List;public class ListToStringTest {public static void main(String[] args) {//定义list集合List<String> list new ArrayList<>();list.add("1");…

SolidUI AI生成可视化,开创性开源项目,版本0.1.0 功能讲解

文章目录 背景项目名字含义登录页含义产品思维0.1.0 版本内涵功能列表数据源管理项目管理设计页面 背景 随着文本生成图像的语言模型兴起&#xff0c;SolidUI想帮人们快速构建可视化工具&#xff0c;可视化内容包括2D,3D,3D场景&#xff0c;从而快速构三维数据演示场景。Solid…

多元分类预测 | Matlab基于麻雀算法优化深度置信网络(SSA-DBN)的分类预测,多特征输入模型,SSA-DBN分类预测

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 多元分类预测 | Matlab基于麻雀算法优化深度置信网络(SSA-DBN)的分类预测,多特征输入模型,SSA-DBN分类预测 多特征输入单输出的二分类及多分类模型。程序内注释详细,直接替换数据就可以用。程序语言为matlab,程…

linux_driver_day03

作业1 题目&#xff1a; 通过ioctl函数选择不同硬件的控制&#xff0c;LED 蜂鸣器 马达 风扇 代码&#xff1a; 代码太多只展示 led 部分&#xff0c;点击查看完整代码 led.c #include "led.h" #include "head.h"static void all_led_init(void);stati…

问题1:矩阵置零 问题2:搜索二维矩阵

问题1&#xff1a;矩阵置零 给定一个 *m* x *n* 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 解题思路&#xff1a; 1.先遍历一遍矩阵&#xff0c;将元素为0的行和列都标记为true 2.再遍历一遍矩阵&#xff0c…

Element 实现动态增加多个输入框并校验

文章目录 前言实现通过按钮动态增加表单并验证必填实现动态多个输入框为行内模式&#xff0c;其它为行外模式 前言 在做复杂的动态表单&#xff0c;实现业务动态变动&#xff0c;比如有一条需要动态添加的el-form-item中包含了多个输入框&#xff0c;并实现表单验证&#xff0…

Visual Studio Code 如何设置整体界面字体的大小?

在某次操作中&#xff0c;我不小心误点了什么&#xff0c;导致 Visual Studio Code 界面的字体变小了很小&#xff0c;如下图所示&#xff1a; 我想把字体调整回来&#xff0c;该如何操作呢&#xff1f; 首先&#xff0c;第一步&#xff0c;打开设置&#xff1a; 第二步&#…

MiniLED是什么?有怎样的发展前景

Mini LED又叫做“次毫米发光二极管”&#xff0c;也是LED器件的一种&#xff0c;其芯片尺寸介于50~200μm之间。Mini LED的组成包括Mini LED像素阵列以及驱动电路&#xff0c;而且像素中心间距的单元较小&#xff0c;仅为0.3-1.5mm单元。随着 Mini LED 显示技术的快速发展&…

Nginx+Tomcat负载均衡(反向代理)、动静分离集群

NginxTomcat负载均衡、动静分离 一、正向代理与反向代理二、负载均衡--with-stream #启用 stream模块&#xff0c;提供4层调度 一、正向代理与反向代理 Nginx:正向代理&#xff08;知道目标服务器&#xff09; 反向代理&#xff08;不知道目标服务器&#xff09; Nginx配置反…

QWebEngine应用---执行javascript

我们都知道现代前端技术是基于html、css和javascript进行显示交互的&#xff0c;其中html和css属于静态界面显示&#xff0c;辅以javascript使页面交互更丰富。浏览器作为前端页面显示的基石&#xff0c;提供一套显示、交互、调试的东西。QWebEngine同样也提供了这些功能&#…

CodeTop整理-数组篇

目录 53. 最大子序和 33. 搜索旋转排序数组 三数之和 121. 买卖股票的最佳时机 4. 寻找两个正序数组的中位数 695. 岛屿的最大面积 54. 螺旋矩阵 88. 合并两个有序数组 152. 乘积最大子数组 42. 接雨水 64. 最小路径和 1. 两数之和 123. 买卖股票的最佳时机 III …

IDEA完全免费AI辅助编程插件BITO-GPT4安装及中文国产化设置

打开IDEA的plugins 搜索BITO&#xff1a; 下载后右边工具栏上会出现BITO的小蓝标&#xff0c;这样就可以使用了但是里面是全英文的 设置中文 1.打开BITO点击右上角设置 点击里面的Settings 进入BITO的Web网页 右边这个改成中文&#xff1a; 这样回到IDEA AI就会生成中…

【HTML】使用 div 自定义实现 input、textarea 输入框(适合定制化场景)

文章目录 一、实现 Input 组件二、实现 Textarea 组件三、React 实践案例 API参考文档: contenteditable : https://developer.mozilla.org/zh-CN/docs/Web/HTML/Global_attributes/contenteditableresize &#xff1a;https://developer.mozilla.org/zh-CN/docs/Web/CSS/resiz…

【Linux】shell脚本: 基本语法 和 高级特性

Shell脚本是一种用Shell语言编写的程序&#xff0c;它可以实现自动化的任务&#xff0c;如批量处理文件、监控系统状态、定时备份等。本文包括&#xff1a; Shell脚本的定义和作用&#xff1a;介绍什么是Shell脚本&#xff0c;它有哪些优点和缺点&#xff0c;它可以用来做什么。…

【macOS 系列】如何在mac下安装nvm实现多版本nodejs

文章目录 一、安装 nvm二、提示 commond not found:nvm的问题 一、安装 nvm 注意&#xff1a;mac下用nvm。win下用nvm-windows 以下步骤都是在命令行工具下执行&#xff1a; 1、安装 curl -o- [https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh](https://…

SPEC CPU 2006 在 CentOS 5.0 x86_64 古老系统测试

下载镜像 CentOS 2 3 4 5 6 等历史老版本下载地址 国内镜像地址_hkNaruto的博客-CSDN博客 下载CentOS 5.0 1-7 ISO文件 注意&#xff1a;尝试过下载DVD版本&#xff0c;速度太慢了。还是通过国内镜像下载这几个iso快。 安装虚拟机 VirtualBox 挂载第一个iso&#xff0c;启动…

计算机基础--->数据结构(7)【红黑树】

文章目录 二三树二三树的性质二三树一个简单的插入例子二三树的特点 红黑树红黑树的特点红黑树的节点红黑树的插入操作1. 左旋2. 右旋颜色翻转3. 颜色翻转插入实例 二三树 二三树与红黑树的性质非常相似&#xff0c;但是二三树能更直观的让人理解构建过程 二三树的性质 二三树是…

【机器学习核心总结】什么是GBDT(梯度提升树)

什么是GBDT(梯度提升树) 虽然GBDT同样由许多决策树组成&#xff0c;但它与随机森林由许多不同。 其中之一是GBDT中的树都是回归树&#xff0c;树有分类有回归&#xff0c;区分它们的方法很简单。将苹果单纯分为好与坏的是分类树&#xff0c;如果能为苹果的好坏程度打个分&…

云原生(第四篇)-k8s yaml文件

Kubernetes 支持 YAML 和 JSON 格式管理资源对象 JSON 格式&#xff1a;主要用于 api 接口之间消息的传递 YAML 格式&#xff1a;用于配置和管理&#xff0c;YAML 是一种简洁的非标记性语言&#xff0c;内容格式人性化&#xff0c;较易读 YAML 语法格式&#xff1a; ●大小写敏…

CentOS Linux的替代品(六)_BigCloud Enterprise Linux R8 U2 基础安装教程

文章目录 CentOS Linux的替代品&#xff08;六&#xff09;_BigCloud Enterprise Linux R8 U2 基础安装教程一、BC-Linux简介二、BigCloud Enterprise Linux R8 U2 基础安装2.1 下载地址2.2 安装过程 三、简单使用3.1 关闭selinux3.1.1 临时关闭selinux3.1.2 永久关闭selinux 3…