补充geotiff输出

main
wangchongwu 2 months ago
parent 7fb0faf6a7
commit 2dc6373278

5
.gitignore vendored

@ -68,4 +68,7 @@ StitchLog
# 保留 3rdparty 整个文件夹内容
!/3rdparty/
!/3rdparty/**
!/3rdparty/**
*.mp4

Binary file not shown.

@ -45,7 +45,7 @@ void bind_API_UnderStitch(py::module_ &m)
m.def("exportGeoTIFF", exportGeoTIFF,
m.def("exportFrameGeoTIFF", exportFrameGeoTIFF,
py::arg("img"), py::arg("info"), py::arg("filename"),
"完成正射校正后输出地理TIFF");
}

@ -61,4 +61,4 @@ public:
// 完成正射校正后输出地理TIFF
void exportGeoTIFF(cv::Mat img,FrameInfo info, const char* filename);
void exportFrameGeoTIFF(cv::Mat img,FrameInfo info, const char* filename);

@ -287,7 +287,7 @@ void BA_Task::OptFrame(vector<KeyType> frameInd,cv::Mat H_map, std::unordered_ma
//ceres::LossFunction* loss_function = new ceres::HuberLoss(1.0);
ceres::LossFunction* scale_loss = new ceres::ScaledLoss(
new ceres::HuberLoss(1.0),
new ceres::HuberLoss(2.0),
1.0, // 降低尺度约束的权重
ceres::TAKE_OWNERSHIP);
@ -297,15 +297,10 @@ void BA_Task::OptFrame(vector<KeyType> frameInd,cv::Mat H_map, std::unordered_ma
#ifdef OPT_H
// ceres::CostFunction* cost_function =
// new ceres::AutoDiffCostFunction<HomographyResidual, 4, 8, 8>(
// new HomographyResidual(keypoint_i, keypoint_j, Hi0, Hj0));
// problemH2.AddResidualBlock(cost_function, nullptr, h_list[i], h_list[j]);
ceres::CostFunction* cost_function =
ceres::CostFunction* cost_function =
new ceres::AutoDiffCostFunction<HomographyResidual, 2, 8, 8>(
new HomographyResidual(keypoint_i, keypoint_j, Hi0, Hj0));
problemH.AddResidualBlock(cost_function, nullptr, h_list[i], h_list[j]);
problemH.AddResidualBlock(cost_function, scale_loss, h_list[i], h_list[j]);
#endif
#ifdef OPT_SE3
@ -353,10 +348,6 @@ void BA_Task::OptFrame(vector<KeyType> frameInd,cv::Mat H_map, std::unordered_ma
// std::cout << "------------" << std::endl;
// }
#ifdef OPT_SE3
// 将优化se3写入H
updateSe3toH();
#endif
//for (int i = 0; i < _origMatrix.size(); i++)
//{
@ -373,6 +364,11 @@ void BA_Task::OptFrame(vector<KeyType> frameInd,cv::Mat H_map, std::unordered_ma
{
auto Key = _frameInd[i];
// 将优化se3写入H
#ifdef OPT_SE3
_currMatrix[i] = Transe3ToH((double*)_currse3[i].data, constK, _depth[i]);
#endif
// 更新缓存
updateCacheH(Key, _currMatrix[i], global_H);
}
@ -463,10 +459,7 @@ int BA_Task::readFrameInfo(vector<KeyType> frameInd)
#include <Arith_GeoSolver.h>
void BA_Task::updateSe3toH()
{
for (size_t i = 0; i < _origse3.size(); i++)
{
_currMatrix[i] = Transe3ToH((double*)_currse3[i].data, constK, _depth[i]);
}
}

@ -0,0 +1,256 @@
#include "Arith_GDALUtils.h"
#include <gdal.h>
#include <gdal_priv.h>
#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
void saveAsGeoTIFF(const cv::Mat& img, double minL, double maxL, double minB, double maxB, const char* filename)
{
// 注册GDAL驱动
GDALAllRegister();
printf("Saving GeoTIFF to %s\n", filename);
// 检查图像类型和通道数
if (img.empty()) {
std::cerr << "Error: Empty image, cannot save GeoTIFF" << std::endl;
return;
}
int nBands = img.channels();
int width = img.cols;
int height = img.rows;
// 确定GDAL数据类型
GDALDataType dataType;
if (img.depth() == CV_8U) {
dataType = GDT_Byte;
} else if (img.depth() == CV_16U) {
dataType = GDT_UInt16;
} else if (img.depth() == CV_32F) {
dataType = GDT_Float32;
} else {
std::cerr << "Error: Unsupported image depth" << std::endl;
return;
}
// 创建输出文件名基于帧ID和时间戳
std::string outputPath = std::string(filename);
// 创建GeoTIFF驱动
GDALDriver* poDriver = GetGDALDriverManager()->GetDriverByName("GTiff");
if (poDriver == nullptr) {
std::cerr << "Error: GTiff driver not available" << std::endl;
return;
}
// 创建数据集
char** papszOptions = nullptr;
papszOptions = CSLSetNameValue(papszOptions, "COMPRESS", "LZW");
papszOptions = CSLSetNameValue(papszOptions, "TILED", "YES");
GDALDataset* poDataset = poDriver->Create(outputPath.c_str(), width, height, nBands, dataType, papszOptions);
CSLDestroy(papszOptions);
if (poDataset == nullptr) {
std::cerr << "Error: Failed to create GeoTIFF file: " << outputPath << std::endl;
return;
}
// 计算GeoTransform参数
// 根据经纬度范围计算像素分辨率(度/像素)
double pixelSizeX = (maxL - minL) / width; // 经度方向像素分辨率
double pixelSizeY = (minB - maxB) / height; // 纬度方向像素分辨率注意图像Y轴向下所以是minB-maxB
// 设置GeoTransform (6个参数)
double adfGeoTransform[6];
adfGeoTransform[0] = minL; // 左上角经度(最小经度)
adfGeoTransform[1] = pixelSizeX; // 经度方向像素分辨率
adfGeoTransform[2] = 0.0; // 旋转参数通常为0
adfGeoTransform[3] = maxB; // 左上角纬度最大纬度因为图像Y轴向下
adfGeoTransform[4] = 0.0; // 旋转参数通常为0
adfGeoTransform[5] = pixelSizeY; // 纬度方向像素分辨率负值因为Y轴向下
poDataset->SetGeoTransform(adfGeoTransform);
// 设置WGS84投影
const char* pszWKT = "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]]";
poDataset->SetProjection(pszWKT);
// 将OpenCV Mat数据写入GDAL数据集
// 如果图像不是8位先转换为8位
cv::Mat imgToSave;
if (img.depth() != CV_8U) {
if (img.depth() == CV_16U) {
img.convertTo(imgToSave, CV_8U, 1.0 / 256.0);
} else if (img.depth() == CV_32F) {
img.convertTo(imgToSave, CV_8U, 255.0);
} else {
img.convertTo(imgToSave, CV_8U);
}
} else {
imgToSave = img;
}
// 如果图像是多通道,需要分离通道
std::vector<cv::Mat> channels;
if (nBands > 1) {
cv::split(imgToSave, channels);
} else {
channels.push_back(imgToSave);
}
// 写入每个通道
for (int band = 0; band < nBands; band++) {
GDALRasterBand* poBand = poDataset->GetRasterBand(band + 1);
// 设置nodata值对于8位图像通常使用0或255这里使用0
double noDataValue = 0.0;
poBand->SetNoDataValue(noDataValue);
cv::Mat bandMat = channels[band];
// 如果数据不连续,先复制为连续存储
if (!bandMat.isContinuous()) {
bandMat = bandMat.clone();
}
// 使用RasterIO写入数据
// 参数说明:
// - nPixelSpace: 每个像素的字节数单通道8位=1
// - nLineSpace: 每行的字节数(连续存储时=width
int pixelSpace = 1; // 每个像素1字节
int lineSpace = bandMat.step[0]; // 使用Mat的实际step
CPLErr err = poBand->RasterIO(GF_Write, 0, 0, width, height,
(void*)bandMat.data, width, height,
GDT_Byte, pixelSpace, lineSpace);
if (err != CE_None) {
std::cerr << "Error: Failed to write band " << (band + 1) << ": " << CPLGetLastErrorMsg() << std::endl;
}
}
// 关闭数据集
GDALClose(poDataset);
std::cout << "GeoTIFF saved successfully: " << outputPath << std::endl;
std::cout << "GeoTransform: [" << adfGeoTransform[0] << ", " << adfGeoTransform[1]
<< ", " << adfGeoTransform[2] << ", " << adfGeoTransform[3]
<< ", " << adfGeoTransform[4] << ", " << adfGeoTransform[5] << "]" << std::endl;
}
bool queryDEMElevation(const char* demPath, double lon, double lat, double& elevation)
{
// 注册GDAL驱动
GDALAllRegister();
// 打开DEM数据集
GDALDataset* poDataset = (GDALDataset*)GDALOpen(demPath, GA_ReadOnly);
if (poDataset == nullptr) {
std::cerr << "Error: Failed to open DEM file: " << demPath << std::endl;
return false;
}
// 获取地理变换参数GeoTransform
// adfGeoTransform[0]: 左上角X坐标经度
// adfGeoTransform[1]: X方向像素分辨率
// adfGeoTransform[2]: 旋转参数通常为0
// adfGeoTransform[3]: 左上角Y坐标纬度
// adfGeoTransform[4]: 旋转参数通常为0
// adfGeoTransform[5]: Y方向像素分辨率通常为负值
double adfGeoTransform[6];
if (poDataset->GetGeoTransform(adfGeoTransform) != CE_None) {
std::cerr << "Error: Failed to get GeoTransform from DEM file" << std::endl;
GDALClose(poDataset);
return false;
}
// 将经纬度转换为像素坐标
// 像素X = (经度 - 左上角经度) / X方向分辨率
// 像素Y = (纬度 - 左上角纬度) / Y方向分辨率
double pixelX = (lon - adfGeoTransform[0]) / adfGeoTransform[1];
double pixelY = (lat - adfGeoTransform[3]) / adfGeoTransform[5];
// 转换为整数像素坐标
int x = (int)std::round(pixelX);
int y = (int)std::round(pixelY);
// 检查坐标是否在有效范围内
int width = poDataset->GetRasterXSize();
int height = poDataset->GetRasterYSize();
if (x < 0 || x >= width || y < 0 || y >= height) {
std::cerr << "Error: Coordinates (" << lon << ", " << lat
<< ") out of DEM bounds" << std::endl;
GDALClose(poDataset);
return false;
}
// 获取第一个波段DEM通常只有一个波段
GDALRasterBand* poBand = poDataset->GetRasterBand(1);
if (poBand == nullptr) {
std::cerr << "Error: Failed to get raster band from DEM file" << std::endl;
GDALClose(poDataset);
return false;
}
// 读取单个像素的高程值
// 根据数据类型读取
GDALDataType dataType = poBand->GetRasterDataType();
double value = 0.0;
if (dataType == GDT_Float32) {
float fValue;
if (poBand->RasterIO(GF_Read, x, y, 1, 1, &fValue, 1, 1, GDT_Float32, 0, 0) == CE_None) {
value = (double)fValue;
} else {
std::cerr << "Error: Failed to read elevation value" << std::endl;
GDALClose(poDataset);
return false;
}
} else if (dataType == GDT_Float64) {
if (poBand->RasterIO(GF_Read, x, y, 1, 1, &value, 1, 1, GDT_Float64, 0, 0) != CE_None) {
std::cerr << "Error: Failed to read elevation value" << std::endl;
GDALClose(poDataset);
return false;
}
} else if (dataType == GDT_Int16) {
short sValue;
if (poBand->RasterIO(GF_Read, x, y, 1, 1, &sValue, 1, 1, GDT_Int16, 0, 0) == CE_None) {
value = (double)sValue;
} else {
std::cerr << "Error: Failed to read elevation value" << std::endl;
GDALClose(poDataset);
return false;
}
} else if (dataType == GDT_UInt16) {
unsigned short usValue;
if (poBand->RasterIO(GF_Read, x, y, 1, 1, &usValue, 1, 1, GDT_UInt16, 0, 0) == CE_None) {
value = (double)usValue;
} else {
std::cerr << "Error: Failed to read elevation value" << std::endl;
GDALClose(poDataset);
return false;
}
} else {
std::cerr << "Error: Unsupported DEM data type" << std::endl;
GDALClose(poDataset);
return false;
}
// 检查是否为NoData值
int bHasNoData = 0;
double noDataValue = poBand->GetNoDataValue(&bHasNoData);
if (bHasNoData && std::abs(value - noDataValue) < 1e-6) {
std::cerr << "Warning: Elevation value is NoData at (" << lon << ", " << lat << ")" << std::endl;
GDALClose(poDataset);
return false;
}
elevation = value;
GDALClose(poDataset);
return true;
}

@ -0,0 +1,25 @@
#pragma once
#include <opencv2/opencv.hpp>
/**
* @brief OpenCV MatGeoTIFF
* @param img OpenCV Mat
* @param minL
* @param maxL
* @param minB
* @param maxB
* @param filename
*/
void saveAsGeoTIFF(const cv::Mat& img, double minL, double maxL, double minB, double maxB, const char* filename);
/**
* @brief DEM TIF
* @param demPath DEM TIF
* @param lon
* @param lat
* @param elevation
* @return truefalse
*/
bool queryDEMElevation(const char* demPath, double lon, double lat, double& elevation);

@ -20,17 +20,18 @@
#define MKDIR(dir) mkdir(dir, 0777)
#define ACCESS access
#endif
#include <filesystem>
using std::string;
#if (__cplusplus < 201703L)
#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
#else
namespace fs = std::filesystem;
#endif
#include "Logger.h"
#include <gdal.h>
#include <gdal_priv.h>
void saveAsGeoTIFF(const cv::Mat& img, double minL, double maxL, double minB, double maxB, const char* filename);
#include "Arith_GDALUtils.h"
@ -139,7 +140,7 @@ UPanInfo UnderStitch::InitMap(FrameInfo para)
auto geo_pro_rect = warpRectWithH_2Rect(H0,cv::Size(para.nWidth, para.nHeight));
// 计算目标比例单帧投影后占全景图的0.25
float target_ratio = 0.08;
float target_ratio = 0.2;
float current_ratio = MAX(geo_pro_rect.width / (target_ratio*panPara.m_pan_width), geo_pro_rect.height / (target_ratio*panPara.m_pan_height));
// 调整scale参数
@ -253,8 +254,8 @@ UPanInfo UnderStitch::InitMap_SCAN(FrameInfo para, float ptRange)
float min_y = min(min(ct_geo_left.y, ct_geo_right.y), min(ct_geo_left_fly.y, ct_geo_right_fly.y));
float max_y = max(max(ct_geo_left.y, ct_geo_right.y), max(ct_geo_left_fly.y, ct_geo_right_fly.y));
float E_range = (max_x - min_x) * 1.3;
float N_range = (max_y - min_y) * 1.3;
float E_range = (max_x - min_x) * 1.2;
float N_range = (max_y - min_y) * 1.2;
// 不损失分辨率的全景图尺寸
panPara.m_pan_width = E_range / scale_max;
@ -390,6 +391,8 @@ void UnderStitch::run_test(FrameInfo para)
UPanInfo UnderStitch::Init(FrameInfo info)
{
// 以向下零位初始化
info.craft.stAtt.fPitch = 0;
info.craft.stAtt.fRoll = 0;
info.servoInfo.fServoPt = 0;
// 设置拼接原点:地理计算的基准点
@ -397,7 +400,7 @@ UPanInfo UnderStitch::Init(FrameInfo info)
// 摆扫45度初始化地图尺寸
#ifdef SCAN_MODE
_panPara = InitMap_SCAN(info,42);
_panPara = InitMap_SCAN(info,15);
#else
// 常规初始化全景图(基于内存限制)
_panPara = InitMap(info);
@ -543,6 +546,8 @@ SINT32 UnderStitch::Run(GD_VIDEO_FRAME_S frame, FrameInfo para)
printf("--------------------\n");
}
//// 视频帧筛选
if (!FilterFrame(para))
{
@ -558,6 +563,7 @@ SINT32 UnderStitch::Run(GD_VIDEO_FRAME_S frame, FrameInfo para)
RtK rtk;
cv::Mat H_geo = _GeoSolver->findHomography(para, rtk);
_frameRtkAll[para.nFrmID] = rtk;
_frameGeoHAll[para.nFrmID] = H_geo;
@ -566,20 +572,29 @@ SINT32 UnderStitch::Run(GD_VIDEO_FRAME_S frame, FrameInfo para)
cv::Size(_panPara.m_pan_width, _panPara.m_pan_height)))
{
cv::Mat pan_ds;
Mat pan_rgb_ds(cv::Size(panImage.cols / 2, panImage.rows / 2),CV_8UC3);
cv::resize(panImage, pan_ds, cv::Size(panImage.cols / 2, panImage.rows / 2));
cv::cvtColor(pan_ds, pan_rgb_ds, cv::COLOR_BGRA2BGR);
imwrite(_outDir + "/" + _filename + std::to_string(_lastFrameKey) + "_z" + "_pan.png", pan_rgb_ds);
//OptAndOutCurrPan(_baCurrViewKey);
// cv::resize(panImage, pan_ds, cv::Size(panImage.cols / 2, panImage.rows / 2));
//cv::cvtColor(pan_ds, pan_rgb_ds, cv::COLOR_BGRA2BGR);
// imwrite(_outDir + "/" + _filename + std::to_string(_lastFrameKey) + "_z" + "_pan_AFTER.png", pan_rgb_ds);
// 输出局部全景图tiff
if (_config.bOutLocalPanGeoTIFF)
{
// 计算经纬度范围
auto P1 = getBLHFromPan(cv::Point2f(0, 0), _H_pan);
auto P2 = getBLHFromPan(cv::Point2f(panImage.cols, 0), _H_pan);
auto P3 = getBLHFromPan(cv::Point2f(panImage.cols, panImage.rows), _H_pan);
auto P4 = getBLHFromPan(cv::Point2f(0, panImage.rows), _H_pan);
auto minL = min(min(min(P1.L, P2.L), P3.L), P4.L);
auto maxL = max(max(max(P1.L, P2.L), P3.L), P4.L);
auto minB = min(min(min(P1.B, P2.B), P3.B), P4.B);
auto maxB = max(max(max(P1.B, P2.B), P3.B), P4.B);
// 保存为GeoTIFF使用经纬度范围
char filename[1024] = {0};
sprintf(filename, "%s/PAN_%s_%04d.tif", _outDir.c_str(), _filename.c_str(), para.nFrmID);
saveAsGeoTIFF(panImage, minL, maxL, minB, maxB, filename);
}
LOG_INFO("Frame {} is out of pan view, reinitializing projection parameters", para.nFrmID);
// 输出当前所有瓦片,再不输出就会丢失当前全景中的瓦片
@ -600,7 +615,10 @@ SINT32 UnderStitch::Run(GD_VIDEO_FRAME_S frame, FrameInfo para)
// 重新初始化投影参数
int shift_x = 0, shift_y = 0;
// 以向下初始化
// 以向下零位初始化
para.craft.stAtt.fPitch = 0;
para.craft.stAtt.fRoll = 0;
para.servoInfo.fServoPt = 0;
_panPara = ShiftMap(para, shift_x, shift_y);
@ -649,6 +667,21 @@ SINT32 UnderStitch::Run(GD_VIDEO_FRAME_S frame, FrameInfo para)
_googleProduct.ExportGeoPng(Orth_Image, info, _outDir + "/FrameTile");
}
// 输出tiff
if(_config.bOutFrameGeoTIFF)
{
TileInfo info = { 0 };
cv::Mat Orth_Image = getOrthImage(src, para, info);
// 保存为GeoTIFF使用经纬度范围
string name = _filename + "_" + std::to_string(para.nFrmID) + ".tif";
saveAsGeoTIFF(Orth_Image, info.boxLatLon.west, info.boxLatLon.east,
info.boxLatLon.south, info.boxLatLon.north, (_outDir + "/FrameTiff/" + name).c_str());
}
// 投影帧到全景
_MapBlender->FrameMap(src, H_geo, _H_pan);
@ -666,6 +699,8 @@ SINT32 UnderStitch::Run(GD_VIDEO_FRAME_S frame, FrameInfo para)
"BA_Optimization"
);
}
LOG_INFO("stitch total times:{}", sw.elapsed_ms());
@ -734,20 +769,40 @@ bool UnderStitch::FilterFrame(FrameInfo para)
return true;
}
// 扫描角过大,跳过
if (ABS(para.servoInfo.fServoPt) > 40)
{
return false;
}
RtK rtk;
cv::Mat H = _GeoSolver->findHomography(para,rtk);
// 计算光轴综合指向与地面法线夹角
// rtk.R是从ENG(东北地)到相机系的旋转矩阵
// 光轴在相机系中是(0,0,1)转换到ENG系后是rtk.R的第三列
// 地面法线在ENG系中是(0,0,1)
// 点积 = rtk.R[2][2],即旋转矩阵的(2,2)元素
double cos_angle = rtk.R.at<double>(2, 2);
// 限制在有效范围内,避免数值误差
cos_angle = std::max(-1.0, std::min(1.0, cos_angle));
double angle_rad = acos(cos_angle);
double angle_deg = angle_rad * 180.0 / CV_PI;
//LOG_INFO("Optical axis angle with ground normal: {:.2f} deg", angle_deg);
// // 扫描角过大,跳过
// if (ABS(para.servoInfo.fServoPt) > 40)
// {
// return false;
// }
// 姿态不稳定,跳过
if(ABS(para.craft.stAtt.fRoll) > 5)
// // 姿态不稳定,跳过
// if(ABS(para.craft.stAtt.fRoll) > 5)
// {
// return false;
// }
// 光轴偏离地面法线过大,跳过
if(ABS(angle_deg) > 45)
{
return false;
}
// 获取当前帧的Geo覆盖
cv::Mat H = _GeoSolver->findHomography(para);
std::vector<cv::Point2f> currCorners = warpRectWithH(H, cv::Size(para.nWidth, para.nHeight));
// 获取上一帧的Geo覆盖
@ -782,7 +837,7 @@ bool UnderStitch::FilterFrame(FrameInfo para)
bool UnderStitch::TriggerBA(FrameInfo para)
{
// 1. 固定帧间隔触发
const int FIXED_FRAME_INTERVAL = 25;
const int FIXED_FRAME_INTERVAL = 5;
if (_totalFrameCnt % FIXED_FRAME_INTERVAL == 0)
{
LOG_INFO("TriggerBA: Fixed frame interval {} reached",FIXED_FRAME_INTERVAL);
@ -1018,7 +1073,7 @@ SINT32 UnderStitch::ReceiveFrame(GD_VIDEO_FRAME_S img, FrameInfo para)
// 保持滑动窗口大小
const size_t WINDOW_SIZE = 60; // 滑动窗口大小
const size_t WINDOW_SIZE = 10; // 滑动窗口大小
if (_baWindowKey.size() > WINDOW_SIZE)
{
_baWindowKey.erase(_baWindowKey.begin());
@ -1113,8 +1168,8 @@ cv::Mat UnderStitch::getOrthImage(cv::Mat src, FrameInfo para,TileInfo& tile)
tile.boxLatLon.south = minB;
tile.boxLatLon.west = minL;
tile.boxLatLon.east = maxL;
tile.tileName = _filename + std::to_string(para.nFrmID);
tile.href = _filename + std::to_string(para.nFrmID) + ".png";
tile.tileName = _filename + "_"+std::to_string(para.nFrmID);
tile.href = tile.tileName + ".png";
@ -1199,7 +1254,7 @@ cv::Mat UnderStitch::getHomography(FrameInfo info)
}
void exportGeoTIFF(cv::Mat src,FrameInfo info, const char* filename)
void exportFrameGeoTIFF(cv::Mat src,FrameInfo info, const char* filename)
{
TransPitch(info);
@ -1253,138 +1308,3 @@ void exportGeoTIFF(cv::Mat src,FrameInfo info, const char* filename)
}
void saveAsGeoTIFF(const cv::Mat& img, double minL, double maxL, double minB, double maxB, const char* filename)
{
// 注册GDAL驱动
GDALAllRegister();
// 检查图像类型和通道数
if (img.empty()) {
std::cerr << "Error: Empty image, cannot save GeoTIFF" << std::endl;
return;
}
int nBands = img.channels();
int width = img.cols;
int height = img.rows;
// 确定GDAL数据类型
GDALDataType dataType;
if (img.depth() == CV_8U) {
dataType = GDT_Byte;
} else if (img.depth() == CV_16U) {
dataType = GDT_UInt16;
} else if (img.depth() == CV_32F) {
dataType = GDT_Float32;
} else {
std::cerr << "Error: Unsupported image depth" << std::endl;
return;
}
// 创建输出文件名基于帧ID和时间戳
std::string outputPath = string(filename);
// 创建GeoTIFF驱动
GDALDriver* poDriver = GetGDALDriverManager()->GetDriverByName("GTiff");
if (poDriver == nullptr) {
std::cerr << "Error: GTiff driver not available" << std::endl;
return;
}
// 创建数据集
char** papszOptions = nullptr;
papszOptions = CSLSetNameValue(papszOptions, "COMPRESS", "LZW");
papszOptions = CSLSetNameValue(papszOptions, "TILED", "YES");
GDALDataset* poDataset = poDriver->Create(outputPath.c_str(), width, height, nBands, dataType, papszOptions);
CSLDestroy(papszOptions);
if (poDataset == nullptr) {
std::cerr << "Error: Failed to create GeoTIFF file: " << outputPath << std::endl;
return;
}
// 计算GeoTransform参数
// 根据经纬度范围计算像素分辨率(度/像素)
double pixelSizeX = (maxL - minL) / width; // 经度方向像素分辨率
double pixelSizeY = (minB - maxB) / height; // 纬度方向像素分辨率注意图像Y轴向下所以是minB-maxB
// 设置GeoTransform (6个参数)
double adfGeoTransform[6];
adfGeoTransform[0] = minL; // 左上角经度(最小经度)
adfGeoTransform[1] = pixelSizeX; // 经度方向像素分辨率
adfGeoTransform[2] = 0.0; // 旋转参数通常为0
adfGeoTransform[3] = maxB; // 左上角纬度最大纬度因为图像Y轴向下
adfGeoTransform[4] = 0.0; // 旋转参数通常为0
adfGeoTransform[5] = pixelSizeY; // 纬度方向像素分辨率负值因为Y轴向下
poDataset->SetGeoTransform(adfGeoTransform);
// 设置WGS84投影
const char* pszWKT = "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]]";
poDataset->SetProjection(pszWKT);
// 将OpenCV Mat数据写入GDAL数据集
// 如果图像不是8位先转换为8位
cv::Mat imgToSave;
if (img.depth() != CV_8U) {
if (img.depth() == CV_16U) {
img.convertTo(imgToSave, CV_8U, 1.0 / 256.0);
} else if (img.depth() == CV_32F) {
img.convertTo(imgToSave, CV_8U, 255.0);
} else {
img.convertTo(imgToSave, CV_8U);
}
} else {
imgToSave = img;
}
// 如果图像是多通道,需要分离通道
std::vector<cv::Mat> channels;
if (nBands > 1) {
cv::split(imgToSave, channels);
} else {
channels.push_back(imgToSave);
}
// 写入每个通道
for (int band = 0; band < nBands; band++) {
GDALRasterBand* poBand = poDataset->GetRasterBand(band + 1);
// 设置nodata值对于8位图像通常使用0或255这里使用0
double noDataValue = 0.0;
poBand->SetNoDataValue(noDataValue);
cv::Mat bandMat = channels[band];
// 如果数据不连续,先复制为连续存储
if (!bandMat.isContinuous()) {
bandMat = bandMat.clone();
}
// 使用RasterIO写入数据
// 参数说明:
// - nPixelSpace: 每个像素的字节数单通道8位=1
// - nLineSpace: 每行的字节数(连续存储时=width
int pixelSpace = 1; // 每个像素1字节
int lineSpace = bandMat.step[0]; // 使用Mat的实际step
CPLErr err = poBand->RasterIO(GF_Write, 0, 0, width, height,
(void*)bandMat.data, width, height,
GDT_Byte, pixelSpace, lineSpace);
if (err != CE_None) {
std::cerr << "Error: Failed to write band " << (band + 1) << ": " << CPLGetLastErrorMsg() << std::endl;
}
}
// 关闭数据集
GDALClose(poDataset);
std::cout << "GeoTIFF saved successfully: " << outputPath << std::endl;
std::cout << "GeoTransform: [" << adfGeoTransform[0] << ", " << adfGeoTransform[1]
<< ", " << adfGeoTransform[2] << ", " << adfGeoTransform[3]
<< ", " << adfGeoTransform[4] << ", " << adfGeoTransform[5] << "]" << std::endl;
}

@ -14,7 +14,7 @@
// 定义扫描模式,使用扫描专用的拼接地图策略
#define SCAN_MODE
//#define SCAN_MODE
// 关键帧间邻接信息
@ -62,7 +62,6 @@ public:
private:
// 扫描方向判别
int StatScanDir(FrameInfo& para);

@ -16,9 +16,12 @@ struct UPanConfig
bool bUseBA;//开启BA
bool bOutFrameTile;//输出单帧正射图
bool bOutGoogleTile;//输出谷歌瓦片
bool bOutFrameGeoTIFF;//输出单帧正射图地理TIFF
bool bOutLocalPanGeoTIFF;//输出局部全景图地理TIFF
};
// 帧内外方位元素
// 帧内外方位元素
struct FrameInfo
{
int nFrmID;//帧编号唯一ID

@ -2,5 +2,5 @@
#pragma once
#include <string>
std::string BUILD_TIME = "BUILD_TIME 2025_12_02-11.42.37";
std::string BUILD_TIME = "BUILD_TIME 2025_12_03-20.25.10";
std::string VERSION = "BUILD_VERSION 1.0.1";

@ -118,14 +118,13 @@ public:
if(!src.empty())
{
info.craft.stPos.B = aTelem.mDroneLatitude;
info.craft.stPos.L = aTelem.mDroneLongtitude;
info.craft.stPos.H = aTelem.mDroneAltitude;
info.craft.stAtt.fYaw = aTelem.mComposedYaw;
info.craft.stAtt.fPitch = aTelem.mComposedPitch;
info.craft.stAtt.fRoll = aTelem.mComposedRoll;
info.nEvHeight = aTelem.mDroneAltitude - 1200;
info.nEvHeight = aTelem.mDroneAltitude - 1250;
info.servoInfo.fServoAz = 0;
info.servoInfo.fServoPt = 0;
@ -138,20 +137,56 @@ public:
info.nHeight = src.rows;
info.nFrmID++;
if (!bUnderInit)
{
pUnderStitch = API_UnderStitch::Create("./cache");
cv::imshow("",src);
cv::waitKey(1);
if (info.nFrmID % 100 == 0)
pUnderStitch->SetOutput("S7215", "./output");
pUnderStitch->Init(info);
bUnderInit = true;
}
else
{
UPanConfig config = {0};
config.bUseBA = 1;
config.bOutGoogleTile = 1;
config.bOutLocalPanGeoTIFF = 1;
pUnderStitch->SetConfig(config);
pUnderStitch->Run(src, info);
cv::Mat pan = pUnderStitch->ExportPanMat();
int dsize = 8;
cv::Mat pan_resized_RGBA, pan_resizedRGB;
cv::resize(pan, pan_resized_RGBA, cv::Size(pan.cols / dsize, pan.rows / dsize));
cv::cvtColor(pan_resized_RGBA, pan_resizedRGB, cv::COLOR_BGRA2BGR);
cv::imshow("pan", pan_resizedRGB);
cv::waitKey(1);
}
// cv::imshow("",src);
// cv::waitKey(1);
// if (info.nFrmID % 100 == 0)
// {
char filename[1024] = {0};
sprintf(filename, "%d_%02d_%02d_%02d_%04d.tif", aTelem.mSensorID, aTelem.mHour, aTelem.mMinute, aTelem.mSecond, aTelem.mMillSecond);
exportGeoTIFF(src, info, filename);
// char filename[1024] = {0};
// sprintf(filename, "%d_%02d_%02d_%02d_%04d.tif", aTelem.mSensorID, aTelem.mHour, aTelem.mMinute, aTelem.mSecond, aTelem.mMillSecond);
// exportFrameGeoTIFF(src, info, filename);
}
// }
}
@ -300,6 +335,9 @@ private:
QFile *file = nullptr;
API_UnderStitch* pUnderStitch = nullptr;
bool bUnderInit = false;
};
#endif

@ -13,7 +13,7 @@ using namespace std;
void ProcessVL(string filePath)
{
auto stitcher = API_UnderStitch::Create("D:/cache");
auto stitcher = API_UnderStitch::Create("./cache");
string filename = filePath.substr(filePath.find_last_of("/") + 1).substr(0, filePath.find_last_of("."));
@ -129,7 +129,7 @@ void ProcessVL(string filePath)
cfg.bUseBA = 1;
stitcher->SetConfig(cfg);
stitcher->SetOutput(filename,"D:/google_tiles");
stitcher->SetOutput(filename,"/home/wang/google_tiles");
pan = stitcher->ExportPanAddr();
@ -203,7 +203,7 @@ void ProcessVL(string filePath)
int main()
{
ProcessVL("Z:/S732/S732挂飞原始数据/扫描数据/20251121_155849310_15.yuv");
ProcessVL("/home/wang/data62/S732/S732挂飞原始数据/20251127/20251127_101038924_13.yuv");
return 0;
}

Loading…
Cancel
Save