diff --git a/.gitignore b/.gitignore index c9ea6cc5..3d6fbadc 100644 --- a/.gitignore +++ b/.gitignore @@ -68,4 +68,7 @@ StitchLog # 保留 3rdparty 整个文件夹内容 !/3rdparty/ -!/3rdparty/** \ No newline at end of file +!/3rdparty/** + + +*.mp4 \ No newline at end of file diff --git a/DJ_stitchVL.mp4 b/DJ_stitchVL.mp4 deleted file mode 100644 index 697a286d..00000000 Binary files a/DJ_stitchVL.mp4 and /dev/null differ diff --git a/stitch/py/API_UnderStitch_binding.cpp b/stitch/py/API_UnderStitch_binding.cpp index bb31dc4c..7598d1fa 100644 --- a/stitch/py/API_UnderStitch_binding.cpp +++ b/stitch/py/API_UnderStitch_binding.cpp @@ -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"); } diff --git a/stitch/src/API_UnderStitch.h b/stitch/src/API_UnderStitch.h index c6a48d64..794dd9e6 100644 --- a/stitch/src/API_UnderStitch.h +++ b/stitch/src/API_UnderStitch.h @@ -61,4 +61,4 @@ public: // 完成正射校正后输出地理TIFF -void exportGeoTIFF(cv::Mat img,FrameInfo info, const char* filename); \ No newline at end of file +void exportFrameGeoTIFF(cv::Mat img,FrameInfo info, const char* filename); \ No newline at end of file diff --git a/stitch/src/Arith_BATask.cpp b/stitch/src/Arith_BATask.cpp index f022be1b..e099b0ff 100644 --- a/stitch/src/Arith_BATask.cpp +++ b/stitch/src/Arith_BATask.cpp @@ -287,7 +287,7 @@ void BA_Task::OptFrame(vector 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 frameInd,cv::Mat H_map, std::unordered_ma #ifdef OPT_H - // ceres::CostFunction* cost_function = - // new ceres::AutoDiffCostFunction( - // 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( 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 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 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 frameInd) #include void BA_Task::updateSe3toH() { - for (size_t i = 0; i < _origse3.size(); i++) - { - _currMatrix[i] = Transe3ToH((double*)_currse3[i].data, constK, _depth[i]); - } + } diff --git a/stitch/src/Arith_GDALUtils.cpp b/stitch/src/Arith_GDALUtils.cpp new file mode 100644 index 00000000..cfa55ba9 --- /dev/null +++ b/stitch/src/Arith_GDALUtils.cpp @@ -0,0 +1,256 @@ +#include "Arith_GDALUtils.h" +#include +#include +#include +#include +#include +#include + + +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 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; +} + diff --git a/stitch/src/Arith_GDALUtils.h b/stitch/src/Arith_GDALUtils.h new file mode 100644 index 00000000..f6481d44 --- /dev/null +++ b/stitch/src/Arith_GDALUtils.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +/** + * @brief 将OpenCV Mat图像保存为GeoTIFF格式 + * @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 成功返回true,失败返回false + */ +bool queryDEMElevation(const char* demPath, double lon, double lat, double& elevation); + diff --git a/stitch/src/Arith_UnderStitch.cpp b/stitch/src/Arith_UnderStitch.cpp index 41762192..3eb398ef 100644 --- a/stitch/src/Arith_UnderStitch.cpp +++ b/stitch/src/Arith_UnderStitch.cpp @@ -20,17 +20,18 @@ #define MKDIR(dir) mkdir(dir, 0777) #define ACCESS access #endif +#include +using std::string; +#if (__cplusplus < 201703L) + #include + namespace fs = std::experimental::filesystem; +#else + namespace fs = std::filesystem; +#endif #include "Logger.h" - - -#include -#include - - - -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(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 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 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; -} - \ No newline at end of file diff --git a/stitch/src/Arith_UnderStitch.h b/stitch/src/Arith_UnderStitch.h index f13bc19d..4a49d19a 100644 --- a/stitch/src/Arith_UnderStitch.h +++ b/stitch/src/Arith_UnderStitch.h @@ -14,7 +14,7 @@ // 定义扫描模式,使用扫描专用的拼接地图策略 -#define SCAN_MODE +//#define SCAN_MODE // 关键帧间邻接信息 @@ -62,7 +62,6 @@ public: private: - // 扫描方向判别 int StatScanDir(FrameInfo& para); diff --git a/stitch/src/StitchStruct.h b/stitch/src/StitchStruct.h index 9ea40987..3ecc2220 100644 --- a/stitch/src/StitchStruct.h +++ b/stitch/src/StitchStruct.h @@ -16,9 +16,12 @@ struct UPanConfig bool bUseBA;//开启BA bool bOutFrameTile;//输出单帧正射图 bool bOutGoogleTile;//输出谷歌瓦片 + bool bOutFrameGeoTIFF;//输出单帧正射图地理TIFF + bool bOutLocalPanGeoTIFF;//输出局部全景图地理TIFF + }; -// 帧内外方位元素 +// 帧内外方位元素 struct FrameInfo { int nFrmID;//帧编号,唯一ID diff --git a/stitch/src/Version.h b/stitch/src/Version.h index 3b7ad185..491e8143 100644 --- a/stitch/src/Version.h +++ b/stitch/src/Version.h @@ -2,5 +2,5 @@ #pragma once #include -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"; diff --git a/tests/cpp/S7215/TsDecoder.hpp b/tests/cpp/S7215/TsDecoder.hpp index 85c0f0b5..0a2f1279 100644 --- a/tests/cpp/S7215/TsDecoder.hpp +++ b/tests/cpp/S7215/TsDecoder.hpp @@ -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 diff --git a/tests/cpp/S732/stitch_S732.cpp b/tests/cpp/S732/stitch_S732.cpp index 1e6ff066..38030a9d 100644 --- a/tests/cpp/S732/stitch_S732.cpp +++ b/tests/cpp/S732/stitch_S732.cpp @@ -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; }