基于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转Matvoid 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 datasetGDALRasterBand* pBand = pDstDataset->GetRasterBand(1);// Allocate memory for the raster dataint nXSize = pBand->GetXSize();int nYSize = pBand->GetYSize();GByte* pData = new GByte[nXSize * nYSize];// Read the raster datapBand->RasterIO(GF_Read, 0, 0, nXSize, nYSize, pData, nXSize, nYSize, GDT_Byte, 0, 0);//转化为Matcv::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 driversGDALAllRegister();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;}// 创建目标shapefileGDALDataset* 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;// 遍历所有源shapefilefor (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;//nodataargv = 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 datasetsGDALRasterizeOptionsFree(pOptions);//合并shpmergeShp(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;}