diff --git a/CMakeLists.txt b/CMakeLists.txt index 3e7ad9f..1612362 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) IF(WIN32) # ======== OpenCV 配置 ======== - set(OpenCV_DIR "C:/Lib/opencv455/build/x64/vc14/lib") + set(OpenCV_DIR "C:/opencv/build/x64/vc14/lib") find_package(OpenCV REQUIRED) include_directories(${OpenCV_INCLUDE_DIRS}) link_libraries(${OpenCV_LIBS}) @@ -71,7 +71,7 @@ IF(WIN32) # ======== FFMPEG配置 ======== set(FFMPEG_DIR "C:/Lib/ffmpeg4") set(FFMPEG_INCLUDE_DIRS ${FFMPEG_DIR}/include) - set(FFMPEG_LIBS_DIR ${FFMPEG_DIR}/lib) + set(FFMPEG_LIBS_DIR ${FFMPEG_DIR}/lib/x64) include_directories(${FFMPEG_DIR}) include_directories(${FFMPEG_INCLUDE_DIRS}) diff --git a/doc/API_MANUAL.pdf b/README.pdf similarity index 98% rename from doc/API_MANUAL.pdf rename to README.pdf index 31b7644..5b89879 100644 Binary files a/doc/API_MANUAL.pdf and b/README.pdf differ diff --git a/ReadMe.md b/ReadMe.md index dcf9145..79e8362 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,20 +1,206 @@ -# 航拍图像拼接系统 +# 航拍拼接模块接口说明 -## 日志 -- 1 2025/1/26 -完成了基础的外参拼接+BA优化过程,目前不支持动态调整拼接大小位置比例尺等功能。当载体持续运动扫描时,无法支持连续拼接以及回溯。因此要求必须加入文件缓存机制,并使用瓦片对数据进行管理,这样可以极大扩展拼接范围,且输出内容更为标准化。 +本模块用于吊舱的扫描全景图拼接,包含前视、下视以及特征拼接三个模式。前视与下视投影面和效果均不同。二者接口大部分相同。 -【初步设想】:建立二级文件缓存,其中第一级存储原始帧(图像+H矩阵等),第二级存储生成的标准瓦片。当系统工作时,根据当前视野范围和XYZ瓦片等级(X),开始从2级缓存搜索瓦片,如果存在瓦片则直接加载,如果不存在则从1级缓存读取原始帧生成瓦片,并将瓦片加入2级缓存。 -【关键技术】:文件缓存技术、标准瓦片转换技术 +特征拼接是完全基于特征的图像算法,本身存在较大的累计误差,仅用于测试和对比出图,暂时没有应用价值。 -- 2 20250205 - -上述开发计划存在问题,拼接算法应是后台服务,应当生成标准产品,而不随着显示更改逻辑,这里考虑生成谷歌瓦片,并通过流传输到显示端,由显控处理显示逻辑。 -- 20250213 +下视拼接是最广泛使用的拼接方式,可以随着载体飞行进行连续长时拼接,并生成含有地理信息 + +![图 0](./doc/images/e18e82d54c6ca00737f00eb3a67586664ac5b6c4f47b019ceebfdc627be2e17d.png) + + + +前视拼接的效果接近360°全景。 +![图 1](./doc/images/b739965a8710a2a6999c2dcee839776d7bdfe99423265288d2cc56b9611ba3e4.png) + + + + + + +## 1. 依赖 +本模块依赖ceres优化库(包含gflag、glog)以及cuda支持,opencv 455支持。 +除cuda库外,其余库均以vc14动态库文件形式提供。 + +使用cuda将极大加速前视极坐标拼接模块。 + + +## 2. 下视接口 + + +```C++ +static API_UnderStitch* Create(std::string cachedir = "./cache"); +``` +模块创建接口,其中默认参数为使用cache的路径,cache将缓存需要处理的帧原始数据。 + +```C++ +static void Destroy(API_UnderStitch* obj); +``` +模块析构接口,使用完毕后释放内存。 + +```C++ +virtual UPanInfo Init(FrameInfo info) = 0; +``` +初始化接口,请使用拼接时刻的内外参信息进行初始化。初始化完成后返回全景图的参数 + + +```C++ + virtual void SetOutput(std::string name, std::string outdir) = 0; +``` +设置输出路径,下视模块按照通用谷歌瓦片生成标准256*256瓦片,提前设置好输出文件夹。 + +```C++ + virtual void SetConfig(UPanConfig config) = 0; +``` +设置运行参数。 + + + +```C++ + virtual BYTE8 Run(GD_VIDEO_FRAME_S img, FrameInfo para) = 0; +``` +运行拼接流程,在该过程中程序自动将当前图像重新投影到全景图上,可以通过访问全景图内存进行实时显示。 + + +```C++ + virtual GD_VIDEO_FRAME_S ExportPanAddr() = 0; +``` +获取全景图接口,其中GD_VIDEO_FRAME_S是高德研发中心通用帧接口,内部维护了图像类型和指针等信息。具体参见GD_VIDEO_FRAME_S接口说明 + + +```C++ + virtual SINT32 OptAndOutCurrPan() = 0; +``` +全景投影完毕后,可以调用该接口进行优化,主要是精细对齐帧以及拼接缝消除等,暂未实现。 + + + +## 前视接口 + +```C++ +static API_FrontStitch* Create(std::string cachedir = "./cache"); +``` + +模块创建接口,其中默认参数为使用cache路径,前视模块不使用cache。 +```C++ +static void Destroy(API_FrontStitch* obj); +``` +模块析构接口,使用完毕后释放内存。 + +```C++ +virtual FPanInfo Init(FrameInfo info, ScanRange stAzRange, ScanRange stPtRange) = 0; +``` +初始化接口,使用拼接时刻的内外参信息进行初始化, + +```C++ + virtual BYTE8 Run(GD_VIDEO_FRAME_S img, FrameInfo para) = 0; +``` +运行拼接流程,在该过程中程序自动将当前图像重新投影到全景图上,可以通过访问全景图内存进行实时显示。 + + + +```C++ + virtual GD_VIDEO_FRAME_S ExportPanAddr() = 0; +``` +获取全景图接口,其中GD_VIDEO_FRAME_S是高德研发中心通用帧接口,内部维护了图像类型和指针等信息。具体参见GD_VIDEO_FRAME_S接口说明 + + +```C++ + virtual SINT32 OptAndOutCurrPan() = 0; +``` +全景投影完毕后,可以调用该接口进行优化,主要是精细对齐帧以及拼接缝消除等,暂未实现。 + + + + + +# 附 下视拼接的标准地理产品 + +下视拼接过程目前是功能最为完善的,主要分为粗拼接+优化两个过程。 +>**粗拼接** 是指利用外部参数信息直接利用无源定位技术对帧进行地理纠正,纠正后的数据天然具备地理属性,只需要简单的仿射变换即可显示为空间一致的底图。 + +>**优化** 是指利用帧间同名点匹配关系建立误差方程,迭代优化映射关系的过程。 + + +粗拼接是整个工作流的核心,形成任意场景均有高可用性的拼接实现。而优化过程是可以关闭的,用于对拼接质量要求较高的场景或者外参误差较大的设备。 + + +如API说明所示,SDK提供了全景图的显示接口。**但我们不建议使用该全景图**,主要原因是全景显示策略与逻辑是为了便于展示算法效果和内部切瓦片使用的。对图层叠加以及漫游、回溯等高级功能难以支持。 + +相反的,在下视算法执行期间,还生成了两种标准地理数据,具有通用的行业接口。能够轻松支持各类地理web、桌面端应用。 + +1. 单帧纠正产品 + +帧输入后,算法利用外参信息对图像进行地理纠正,简单说就是正北+下视校正。且给出帧的kml信息(谷歌地球标准数据层)。 + +```xml + + + + + DJI6830 + + DJI6830.png + + + 30.267059 + 30.264926 + 114.446180 + 114.443523 + + + + +``` + +2. 谷歌瓦片 + +视频输入过程中,算法根据外参变化情况自行判断执行优化时机,优化完成后会将拼接好的信息切成标准谷歌瓦片。 + +谷歌瓦片按照{Z}-{X}-{Y}.png形式进行命名,能够被gis前端正确显示。下图是cesiumjs的可视化效果。 +**因此整个执行流程可以概括为实时显示基于kml的帧,并周期性的或者触发式的读取瓦片覆盖显示。** + + + +![图 2](./doc/images/4c9281d939781e3be92bc09eda67036271172bad357c4c92863aa79385edd0ab.png) + + + + +## 变更说明 + +### 20250919 +1. 修复了长焦初始化崩溃问题,限制了全景图分辨率,最大水平方向像素个数不多于1万。 +2. 增加前视扫描换向判断,内部自动初始化拼接地面起始点,连续拼接不至于畸变。 +3. 俯仰方向的零位由竖直向下改为水平为0,更符合使用习惯。 +4. 新增日志支持,日志为可执行程序下“StitchLog”文件夹。 +5. 原先使用1080p测试可见光,改为720p后,除图像宽高不同外,像元尺寸应乘以1.5 + + +![图 0](./doc/images/6f54e1028cf86058d0ecd9ebe7ab3011e82e4b35b5b193ab8b88a21817c01d50.png) + + + +### 20250924 +1. 按照外场要求,扫描方向切换不进行全景图初始化,改为多扫描几个条带后,根据位移路径大小初始化,这样可以尽可能保留更多的条带。 + + +### 20250925 +1. 补充 前视扫描全景图像方<->经纬高<->伺服导引角的关系计算 + + +### 20251015 +根据领导指示,前视拼接技术不再进一步研究 + + + +### 20251030 +近期完成下视算法的改进: +1. 新增了基于se3李代数优化,降低了优化参数量,优化结果更有物理意义 +2. 对工程结构进行重构,前视/纯特征与下视分开,所有测试代码进入./test目录 +3. map/Cesium 离线下载js文件,现已支持调试网内看瓦片和kml +4. 其他小改动 + -已初步完成地理快拼、BA、谷歌瓦片输出等核心功能开发,能够输出符合谷歌标准的256瓦片。整理以下遗留问题: -【1】全景图的分辨率和尺寸应当根据硬件水平自适应,目前是人工指定的 -【2】长时间推扫导致成像区域离开预设的全景范围,如何自适应处理。 -【3】一定要提供边扫描边输出瓦片能力 \ No newline at end of file diff --git a/doc/API_MANUAL.md b/doc/API_MANUAL.md deleted file mode 100644 index af91b3f..0000000 --- a/doc/API_MANUAL.md +++ /dev/null @@ -1,193 +0,0 @@ -# 航拍拼接模块接口说明 - -本模块用于吊舱的扫描全景图拼接,包含前视、下视以及特征拼接三个模式。前视与下视投影面和效果均不同。二者接口大部分相同。 - -特征拼接是完全基于特征的图像算法,本身存在较大的累计误差,仅用于测试和对比出图,暂时没有应用价值。 - - - -下视拼接是最广泛使用的拼接方式,可以随着载体飞行进行连续长时拼接,并生成含有地理信息 - -![图 0](./images/e18e82d54c6ca00737f00eb3a67586664ac5b6c4f47b019ceebfdc627be2e17d.png) - - - -前视拼接的效果接近360°全景。 -![图 1](./images/b739965a8710a2a6999c2dcee839776d7bdfe99423265288d2cc56b9611ba3e4.png) - - - - - - -## 1. 依赖 -本模块依赖ceres优化库(包含gflag、glog)以及cuda支持,opencv 455支持。 -除cuda库外,其余库均以vc14动态库文件形式提供。 - -使用cuda将极大加速前视极坐标拼接模块。 - - -## 2. 下视接口 - - -```C++ -static API_UnderStitch* Create(std::string cachedir = "./cache"); -``` -模块创建接口,其中默认参数为使用cache的路径,cache将缓存需要处理的帧原始数据。 - -```C++ -static void Destroy(API_UnderStitch* obj); -``` -模块析构接口,使用完毕后释放内存。 - -```C++ -virtual UPanInfo Init(FrameInfo info) = 0; -``` -初始化接口,请使用拼接时刻的内外参信息进行初始化。初始化完成后返回全景图的参数 - - -```C++ - virtual void SetOutput(std::string name, std::string outdir) = 0; -``` -设置输出路径,下视模块按照通用谷歌瓦片生成标准256*256瓦片,提前设置好输出文件夹。 - -```C++ - virtual void SetConfig(UPanConfig config) = 0; -``` -设置运行参数。 - - - -```C++ - virtual BYTE8 Run(GD_VIDEO_FRAME_S img, FrameInfo para) = 0; -``` -运行拼接流程,在该过程中程序自动将当前图像重新投影到全景图上,可以通过访问全景图内存进行实时显示。 - - -```C++ - virtual GD_VIDEO_FRAME_S ExportPanAddr() = 0; -``` -获取全景图接口,其中GD_VIDEO_FRAME_S是高德研发中心通用帧接口,内部维护了图像类型和指针等信息。具体参见GD_VIDEO_FRAME_S接口说明 - - -```C++ - virtual SINT32 OptAndOutCurrPan() = 0; -``` -全景投影完毕后,可以调用该接口进行优化,主要是精细对齐帧以及拼接缝消除等,暂未实现。 - - - -## 前视接口 - -```C++ -static API_FrontStitch* Create(std::string cachedir = "./cache"); -``` - -模块创建接口,其中默认参数为使用cache路径,前视模块不使用cache。 -```C++ -static void Destroy(API_FrontStitch* obj); -``` -模块析构接口,使用完毕后释放内存。 - -```C++ -virtual FPanInfo Init(FrameInfo info, ScanRange stAzRange, ScanRange stPtRange) = 0; -``` -初始化接口,使用拼接时刻的内外参信息进行初始化, - -```C++ - virtual BYTE8 Run(GD_VIDEO_FRAME_S img, FrameInfo para) = 0; -``` -运行拼接流程,在该过程中程序自动将当前图像重新投影到全景图上,可以通过访问全景图内存进行实时显示。 - - - -```C++ - virtual GD_VIDEO_FRAME_S ExportPanAddr() = 0; -``` -获取全景图接口,其中GD_VIDEO_FRAME_S是高德研发中心通用帧接口,内部维护了图像类型和指针等信息。具体参见GD_VIDEO_FRAME_S接口说明 - - -```C++ - virtual SINT32 OptAndOutCurrPan() = 0; -``` -全景投影完毕后,可以调用该接口进行优化,主要是精细对齐帧以及拼接缝消除等,暂未实现。 - - - - - -# 附 下视拼接的标准地理产品 - -下视拼接过程目前是功能最为完善的,主要分为粗拼接+优化两个过程。 ->**粗拼接** 是指利用外部参数信息直接利用无源定位技术对帧进行地理纠正,纠正后的数据天然具备地理属性,只需要简单的仿射变换即可显示为空间一致的底图。 - ->**优化** 是指利用帧间同名点匹配关系建立误差方程,迭代优化映射关系的过程。 - - -粗拼接是整个工作流的核心,形成任意场景均有高可用性的拼接实现。而优化过程是可以关闭的,用于对拼接质量要求较高的场景或者外参误差较大的设备。 - - -如API说明所示,SDK提供了全景图的显示接口。**但我们不建议使用该全景图**,主要原因是全景显示策略与逻辑是为了便于展示算法效果和内部切瓦片使用的。对图层叠加以及漫游、回溯等高级功能难以支持。 - -相反的,在下视算法执行期间,还生成了两种标准地理数据,具有通用的行业接口。能够轻松支持各类地理web、桌面端应用。 - -1. 单帧纠正产品 - -帧输入后,算法利用外参信息对图像进行地理纠正,简单说就是正北+下视校正。且给出帧的kml信息(谷歌地球标准数据层)。 - -```xml - - - - - DJI6830 - - DJI6830.png - - - 30.267059 - 30.264926 - 114.446180 - 114.443523 - - - - -``` - -2. 谷歌瓦片 - -视频输入过程中,算法根据外参变化情况自行判断执行优化时机,优化完成后会将拼接好的信息切成标准谷歌瓦片。 - -谷歌瓦片按照{Z}-{X}-{Y}.png形式进行命名,能够被gis前端正确显示。下图是cesiumjs的可视化效果。 -**因此整个执行流程可以概括为实时显示基于kml的帧,并周期性的或者触发式的读取瓦片覆盖显示。** - - - -![图 2](./images/4c9281d939781e3be92bc09eda67036271172bad357c4c92863aa79385edd0ab.png) - - - - -## 变更说明 - -### 20250919 -1. 修复了长焦初始化崩溃问题,限制了全景图分辨率,最大水平方向像素个数不多于1万。 -2. 增加前视扫描换向判断,内部自动初始化拼接地面起始点,连续拼接不至于畸变。 -3. 俯仰方向的零位由竖直向下改为水平为0,更符合使用习惯。 -4. 新增日志支持,日志为可执行程序下“StitchLog”文件夹。 -5. 原先使用1080p测试可见光,改为720p后,除图像宽高不同外,像元尺寸应乘以1.5 - - -![图 0](images/6f54e1028cf86058d0ecd9ebe7ab3011e82e4b35b5b193ab8b88a21817c01d50.png) - - - -### 20250924 -1. 按照外场要求,扫描方向切换不进行全景图初始化,改为多扫描几个条带后,根据位移路径大小初始化,这样可以尽可能保留更多的条带。 - - -### 20250925 -1. 补充 前视扫描全景图像方<->经纬高<->伺服导引角的关系计算 - - diff --git a/map/Cesium/templates/index.html b/map/Cesium/templates/index.html index 1d29c9b..5a79a8b 100644 --- a/map/Cesium/templates/index.html +++ b/map/Cesium/templates/index.html @@ -50,7 +50,7 @@ new Cesium.UrlTemplateImageryProvider({ url: 'https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}', minimumLevel: 1, - maximumLevel: 20, + maximumLevel: 22, credit: new Cesium.Credit('Google Maps') }) ); @@ -63,7 +63,7 @@ // 添加数据源 viewer.dataSources.add( - Cesium.KmlDataSource.load("/kml/DJI7990_z19.kml", options) + Cesium.KmlDataSource.load("/kml/DJI12670_z19.kml", options) ).then((dataSource) => { console.log("KML数据加载完成:", dataSource); // 自动飞到数据范围 @@ -80,7 +80,7 @@ const customTiles = viewer.imageryLayers.addImageryProvider( new Cesium.UrlTemplateImageryProvider({ url: '/tiles/{z}/{x}/{y}.png', // 修改为X_Y.png格式 - maximumLevel: 19, // 根据你的瓦片层级修改 + maximumLevel: 22, // 根据你的瓦片层级修改 minimumLevel: 0, credit: new Cesium.Credit('Custom Tiles'), // 添加错误处理 @@ -128,10 +128,11 @@ // 设置相机位置到瓦片区域 viewer.camera.setView({ destination: Cesium.Rectangle.fromDegrees( - 114.623397, // west - 30.328361, // south - 114.623397, // east - 30.328361 // north +114.445953, +30.267963, +114.445953, +30.267963, + ), orientation: { heading: 0, @@ -181,10 +182,10 @@ const lastDataSource = dataSources[dataSources.length - 1]; // 使用最后一个KML文件的经纬度范围 const rectangle = Cesium.Rectangle.fromDegrees( - 114.623397, // west - 30.328361, // south - 114.623397, // east - 30.328361 // north +114.445953, +30.267963, +114.445953, +30.267963, ); // 设置相机位置 @@ -239,7 +240,7 @@ addMarker(114.623397, 30.328361, '熊司一起飞点', '这是一个测试标记点
点击查看详细信息'); // 初始加载KML文件(设置为false可以禁用KML加载) - loadKMLFiles(true); + diff --git a/stitch/CMakeLists.txt b/stitch/CMakeLists.txt index 7d4ed04..1511b2d 100644 --- a/stitch/CMakeLists.txt +++ b/stitch/CMakeLists.txt @@ -54,7 +54,11 @@ set(ArithSrcDIR_LOG "src/Log") # 日志文件路径 set(ArithSrcDIR_DeepSort "src/deepsort/src") # deepsort # 使用当前目录、Common目录源码构建 -file(GLOB libsrcs ${ArithSrcDIR_MAIN}/mapKernel.cu ${ArithSrcDIR_MAIN}/*.cpp ${ArithSrcDIR_MAIN}/*.c ${ArithSrcDIR_MAIN}/*.h ${ArithSrcDIR_MAIN}/*.hpp) +file(GLOB libsrcs ${ArithSrcDIR_MAIN}/FrontStitch/mapKernel.cu +${ArithSrcDIR_MAIN}/*.cpp ${ArithSrcDIR_MAIN}/*.c ${ArithSrcDIR_MAIN}/*.h ${ArithSrcDIR_MAIN}/*.hpp +${ArithSrcDIR_MAIN}/FrontStitch/*.h ${ArithSrcDIR_MAIN}/FrontStitch/*.cpp +${ArithSrcDIR_MAIN}/FeaStitch/*.h ${ArithSrcDIR_MAIN}/FeaStitch/*.cpp +) file(GLOB CommonSrc ${ArithSrcDIR_MAIN}/utils/*.cpp ${ArithSrcDIR_MAIN}/utils/*.c ${ArithSrcDIR_MAIN}/utils/*.h ${ArithSrcDIR_MAIN}/utils/*.hpp) file(GLOB LogSrc ${ArithSrcDIR_LOG}/*.cpp ${ArithSrcDIR_LOG}/*.c ${ArithSrcDIR_LOG}/*.h ${ArithSrcDIR_LOG}/*.hpp) file(GLOB DeepSortSrc ${ArithSrcDIR_DeepSort}/*.cpp ${ArithSrcDIR_DeepSort}/*.c) @@ -71,6 +75,8 @@ target_include_directories(${LIB_STITCH} PUBLIC ${ArithSrcDIR_MAIN}/utils ${ArithSrcDIR_MAIN}/Log ${ArithSrcDIR_MAIN}/deepsort/include + ${ArithSrcDIR_MAIN}/FeaStitch + ${ArithSrcDIR_MAIN}/FrontStitch ) link_directories(${Ceres_DIR}/lib) diff --git a/stitch/src/Arith_BATask.cpp b/stitch/src/Arith_BATask.cpp index 82ee418..cd909a4 100644 --- a/stitch/src/Arith_BATask.cpp +++ b/stitch/src/Arith_BATask.cpp @@ -360,13 +360,13 @@ void BA_Task::OptFrame(vector frameInd,cv::Mat H_map) // 配置求解器 ceres::Solver::Options options; - options.max_num_iterations = 20; // 增加最大迭代次数 + options.max_num_iterations = 10; // 增加最大迭代次数 options.function_tolerance = 1e-5; // 设置更严格的函数值容忍度 options.gradient_tolerance = 1e-5; // 设置更严格的梯度容忍度 options.parameter_tolerance = 1e-5; // 设置更严格的参数容忍度 options.minimizer_progress_to_stdout = true; //options.linear_solver_type = ceres::SPARSE_NORMAL_CHOLESKY; // 使用稀疏求解器 - options.num_threads = 8; // 使用多线程 + options.num_threads = 12; // 使用多线程 ceres::Solver::Summary summary; @@ -388,7 +388,7 @@ void BA_Task::OptFrame(vector frameInd,cv::Mat H_map) // 输出结果 - std::cout << summary.FullReport() << std::endl; + //std::cout << summary.BriefReport() << std::endl; #ifndef OPT_H // 将优化se3写入H diff --git a/stitch/src/Arith_BlendMap.cpp b/stitch/src/Arith_BlendMap.cpp index 1e96f58..5c1ce09 100644 --- a/stitch/src/Arith_BlendMap.cpp +++ b/stitch/src/Arith_BlendMap.cpp @@ -1,28 +1,56 @@ #include "Arith_BlendMap.h" #include "Arith_Utils.h" -#include "Arith_FrontStitch.h" - +#include "Arith_GeoSolver.h" using std::max; using std::min; -MapBlend::MapBlend(FileCache* cache) +MapBlend::MapBlend(FileCache* cache, UPanInfo upan_info) { _cache = cache; + + // 创建底图 + _panImage = cv::Mat(upan_info.m_pan_height, upan_info.m_pan_width, CV_8UC4, cv::Scalar(0, 0, 0, 0)); + + // 创建覆盖标记mask + _panMask = cv::Mat::zeros(upan_info.m_pan_height, upan_info.m_pan_width, CV_8UC1); + + + // 临时空间 + _panTmp = cv::Mat(upan_info.m_pan_height, upan_info.m_pan_width, CV_8UC4, cv::Scalar(0, 0, 0, 0)); + + _maskTmp = cv::Mat::zeros(upan_info.m_pan_height, upan_info.m_pan_width, CV_8UC1); + + + _upan_info = upan_info; } MapBlend::~MapBlend() { } -void MapBlend::MapFrame(cv::Mat src, cv::Mat H_Geo, cv::Mat mapH, cv::Mat pan, cv::Mat pan_mask) +cv::Mat MapBlend::getPanImageMat() { + return _panImage; +} + +cv::Mat MapBlend::getPanMaskMat() +{ + return _panMask; +} + + + +void MapBlend::FrameMap(cv::Mat src, cv::Mat H_Geo, cv::Mat mapH, BLEND_TYPE blendType) +{ + //return FrameMap_1(src, H_Geo, mapH); + // 与全景图的H相乘 cv::Mat H = mapH * H_Geo; // 利用H投影当前帧到全景 - cv::Mat imagetmp(pan.size(), CV_8UC3, cv::Scalar(0, 0, 0)); - - cv::warpPerspective(src, imagetmp, H, imagetmp.size(), cv::INTER_LINEAR, cv::BORDER_TRANSPARENT); + //cv::Mat imagetmp(_panImage.size(), CV_8UC3, cv::Scalar(0, 0, 0)); + _panTmp.setTo(0); + cv::warpPerspective(src, _panTmp, H, _panTmp.size(), cv::INTER_NEAREST, cv::BORDER_TRANSPARENT); cv::Mat mask = cv::Mat::ones(cv::Size(src.cols, src.rows), CV_8UC1); @@ -34,97 +62,155 @@ void MapBlend::MapFrame(cv::Mat src, cv::Mat H_Geo, cv::Mat mapH, cv::Mat pan, c mask(cv::Rect(mask.cols - margin, margin, margin, mask.rows - 2 * margin)) = 0; // 右边 - cv::Mat warped_mask; - cv::warpPerspective(mask, warped_mask, H, imagetmp.size(), cv::INTER_NEAREST, cv::BORDER_CONSTANT, cv::Scalar(0)); + _maskTmp.setTo(0); + cv::warpPerspective(mask, _maskTmp, H, _panTmp.size(), cv::INTER_NEAREST, cv::BORDER_CONSTANT, cv::Scalar(0)); // 收缩一圈消除虚线 - //cv::erode(warped_mask, warped_mask, + //cv::erode(_maskTmp, _maskTmp, // cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(3, 3))); + blendType = DIRECT; + // 增量拼接 + if (blendType == DIRECT) + { + // + } + else if (blendType == INCREASE) + { + _maskTmp = _maskTmp & (~_panMask); //只拷贝未覆盖区域 + } + else if (blendType == DIS_BLEND) + { + //cv::Mat dist1, dist2; + //cv::distanceTransform(_maskTmp, dist1, cv::DIST_L2, 3); //当前帧 + //cv::distanceTransform(_panMask, dist2, cv::DIST_L2, 3); //底图 + //dist1.convertTo(dist1, CV_32FC1); + //dist2.convertTo(dist2, CV_32FC1); + //// 限制最大距离,比如只在 30 像素内有效 + //double maxDist = 30.0; + //dist1 = cv::min(dist1, maxDist); + //dist2 = cv::min(dist1, maxDist); - imagetmp.copyTo(pan, warped_mask); + //// 归一化到 0~1 之间(越靠近边界权重越小) + //cv::normalize(dist1, dist1, 0.0, 1.0, cv::NORM_MINMAX); + //cv::normalize(dist2, dist2, 0.0, 1.0, cv::NORM_MINMAX); - // 保存遮罩 - warped_mask.copyTo(pan_mask, warped_mask); -} + //cv::Mat weight1 = dist1 / (dist1 + dist2 + 1e-6); + //cv::Mat weight2 = 1.0f - weight1; -void MapBlend::DirectMap(vector frameInd, cv::Mat mapH, cv::Mat pan, cv::Mat pan_mask) -{ - // 从文件缓存获取帧 - auto _t_frame_cache = std::make_shared(); - for (size_t i = 0; i < frameInd.size(); i++) - { - KeyType key = frameInd[i]; - bool flag = _cache->get(key, _t_frame_cache); - if (!flag) - { - continue; - } + //cv::Mat img1f, img2f; + //_panTmp.convertTo(img1f, CV_32FC4, 1.0 / 255.0); + //_panImage.convertTo(img2f, CV_32FC4, 1.0 / 255.0); - // 读取当前H - cv::Mat H1 = cv::Mat(3, 3, CV_64FC1, _t_frame_cache->H); + //std::vector c1, c2; + //cv::split(img1f, c1); + //cv::split(img2f, c2); - // 与全景图的H相乘 - cv::Mat H = mapH * H1; + //for (int i = 0; i < 4; ++i) { + // c1[i] = c1[i].mul(weight1) + c2[i].mul(weight2); + //} - // 利用H投影当前帧到全景 - cv::Mat imagetmp(pan.size(), CV_8UC3, cv::Scalar(0, 0, 0)); + //cv::Mat blended; + //cv::merge(c1, blended); + //blended.convertTo(blended, CV_8UC4, 255.0); - // 获取图像数据 - cv::Mat src = getRGBAMatFromGDFrame(_t_frame_cache->_frame_info, _t_frame_cache->_data); - cv::warpPerspective(src, imagetmp, H, imagetmp.size(), cv::INTER_LINEAR, cv::BORDER_TRANSPARENT); - cv::Mat mask = cv::Mat::ones(cv::Size(src.cols, src.rows), CV_8UC1); + //cv::Mat blended_show; + //cv::resize(blended, blended_show, cv::Size(weight1.rows / 8, weight1.cols / 8)); + //imshow("blended", blended_show); + //cv::waitKey(1); + - // 设置上下左右边缘区域为 0 - int margin = 5; // 可根据实际需求调整,5 像素作为边缘区域 - mask(cv::Rect(0, 0, mask.cols, margin)) = 0; // 上边 - mask(cv::Rect(0, mask.rows - margin, mask.cols, margin)) = 0; // 下边 - mask(cv::Rect(0, margin, margin, mask.rows - 2 * margin)) = 0; // 左边 - mask(cv::Rect(mask.cols - margin, margin, margin, mask.rows - 2 * margin)) = 0; // 右边 + //cv::Mat disshow; + //cv::resize(dis, disshow, cv::Size(warped_mask.rows / 8, warped_mask.cols / 8)); + // + //imshow("dis", disshow); + //cv::waitKey(1); - cv::Mat warped_mask; - cv::warpPerspective(mask, warped_mask, H, imagetmp.size(), cv::INTER_NEAREST, cv::BORDER_CONSTANT, cv::Scalar(0)); - - //// 收缩一圈消除虚线 - //cv::erode(warped_mask, warped_mask, - // cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(3, 3))); + } - imagetmp.copyTo(pan, warped_mask); + //// 显示mask + //cv::Mat maskshow; + //cv::resize(warped_mask, maskshow, cv::Size(warped_mask.rows / 8, warped_mask.cols / 8)); + //imshow("warped_mask", maskshow *255); + //cv::waitKey(1); - // 保存遮罩 - warped_mask.copyTo(pan_mask, warped_mask); + _panTmp.copyTo(_panImage, _maskTmp); - //imshow("mask", pan_mask); - //waitKey(1); - - } + // 保存遮罩 + _maskTmp.copyTo(_panMask, _maskTmp); } - -void MapBlend::BlendMap(vector frameInd, cv::Mat mapH, cv::Mat pan, cv::Mat pan_mask) +#include +void MapBlend::FrameMap_1(cv::Mat src, cv::Mat H_Geo, cv::Mat mapH) { -} +//#pragma omp parallel +// { +// printf("Thread %d of %d\n", +// omp_get_thread_num(), omp_get_num_threads()); +// } + // 与全景图的H相乘 + cv::Mat H = mapH * H_Geo; -FrontMapBlend::FrontMapBlend(FileCache* cache) -{ - _cache = cache; -} + cv::Point2f leftTop_map = warpPointWithH(H,cv::Point2f(0,0)); + cv::Point2f rightTop_map = warpPointWithH(H,cv::Point2f(src.cols,0)); + cv::Point2f rightBottom_map = warpPointWithH(H,cv::Point2f(src.cols,src.rows)); + cv::Point2f leftBottom_map = warpPointWithH(H,cv::Point2f(0,src.rows)); + + // 计算全景图的范围 + int right = max(max(max(leftTop_map.x, leftBottom_map.x), rightTop_map.x), rightBottom_map.x); + int left = min(min(min(leftTop_map.x, leftBottom_map.x), rightTop_map.x), rightBottom_map.x); + int top = min(min(min(leftTop_map.y, leftBottom_map.y), rightTop_map.y), rightBottom_map.y); + int bottom = max(max(max(leftTop_map.y, leftBottom_map.y), rightTop_map.y), rightBottom_map.y); + + + int xRange = right - left; + int yRnage = bottom - top; + + //反映射到像素坐标 + int valid_top = std::max(0, top); + int valid_bottom = std::min(_panImage.rows, bottom); + int valid_left = std::max(0, left); + int valid_right = std::min(_panImage.cols, right); + + cv::Mat H_inv = H.inv(); + +#pragma omp parallel for + for (int i = valid_top; i < valid_bottom; i++) + { + uchar* row = _panImage.ptr(i); + for (int j = valid_left; j < valid_right; j++) + { + //转换为pixel坐标 + auto p_img = warpPointWithH(H_inv,Point2f(j, i)); + + if (p_img.x >= 0 && p_img.y >= 0 && p_img.x < src.cols && p_img.y < src.rows) + { + auto rgbVal = Interpolation_RGBA32(src.data, src.cols, src.rows, p_img.x, p_img.y); + + row[4 * j + 0] = rgbVal.R; + row[4 * j + 1] = rgbVal.G; + row[4 * j + 2] = rgbVal.B; + row[4 * j + 3] = rgbVal.A; + + + } + } + } + + + return; -FrontMapBlend::~FrontMapBlend() -{ } -void FrontMapBlend::DirectMap(vector frameInd, FPanInfo _panPara, cv::Mat _panImage, cv::Mat pan_mask, cuda_Mem cuda_resor) +void MapBlend::BatchMap(vector frameInd, cv::Mat mapH, BLEND_TYPE blendType) { - _panImage.setTo(0); - // 从文件缓存获取帧 auto _t_frame_cache = std::make_shared(); for (size_t i = 0; i < frameInd.size(); i++) @@ -138,71 +224,47 @@ void FrontMapBlend::DirectMap(vector frameInd, FPanInfo _panPara, cv::M } // 读取当前H - cv::Mat H = cv::Mat(3, 3, CV_64FC1, _t_frame_cache->H); - - auto info = _t_frame_cache->_para; + cv::Mat H1 = cv::Mat(3, 3, CV_64FC1, _t_frame_cache->H); - float dep = info.nEvHeight; + // 与全景图的H相乘 + cv::Mat H = mapH * H1; - GD_VIDEO_FRAME_S img = _t_frame_cache->_frame_info; + // 利用H投影当前帧到全景 + //cv::Mat imagetmp(_panImage.size(), CV_8UC3, cv::Scalar(0, 0, 0)); // 获取图像数据 - cv::Mat rgb = getRGBMatFromGDFrame(_t_frame_cache->_frame_info, _t_frame_cache->_data); - - - //Pole CT_map = getPoleFromImgWithH(H, cv::Point2f(img.u32Width/2, img.u32Height/2), info.nEvHeight); + cv::Mat src = getRGBAMatFromGDFrame(_t_frame_cache->_frame_info, _t_frame_cache->_data); + cv::warpPerspective(src, _panTmp, H, _panTmp.size(), cv::INTER_LINEAR, cv::BORDER_TRANSPARENT); - // 计算帧的map四至,注意这里不是线性变化,不能完全保证四至约束下的原图能完全覆盖,即反采样后图像可能不完整 - Pole leftTop_map = getPoleFromImgWithH(H, cv::Point2f(0, 0), info.nEvHeight); - Pole rightTop_map = getPoleFromImgWithH(H, cv::Point2f(img.u32Width, 0), info.nEvHeight); - Pole rightBottom_map = getPoleFromImgWithH(H, cv::Point2f(img.u32Width, img.u32Height), info.nEvHeight); - Pole leftBottom_map = getPoleFromImgWithH(H, cv::Point2f(0, img.u32Height), info.nEvHeight); + cv::Mat mask = cv::Mat::ones(cv::Size(src.cols, src.rows), CV_8UC1); - //leftTop_map.beta = DEGLIM360(leftTop_map.beta); - //rightTop_map.beta = DEGLIM360(rightTop_map.beta); - //rightBottom_map.beta = DEGLIM360(rightBottom_map.beta); - //leftBottom_map.beta = DEGLIM360(leftBottom_map.beta); + // 设置上下左右边缘区域为 0 + int margin = 5; // 可根据实际需求调整,5 像素作为边缘区域 + mask(cv::Rect(0, 0, mask.cols, margin)) = 0; // 上边 + mask(cv::Rect(0, mask.rows - margin, mask.cols, margin)) = 0; // 下边 + mask(cv::Rect(0, margin, margin, mask.rows - 2 * margin)) = 0; // 左边 + mask(cv::Rect(mask.cols - margin, margin, margin, mask.rows - 2 * margin)) = 0; // 右边 - // 转全景图的像方 - cv::Point2f p_leftTop_map = getFPanFromPole(leftTop_map, _panPara); - cv::Point2f p_rightTop_map = getFPanFromPole(rightTop_map, _panPara); - cv::Point2f p_rightBottom_map = getFPanFromPole(rightBottom_map, _panPara); - cv::Point2f p_leftBottom_map = getFPanFromPole(leftBottom_map, _panPara); - // 计算全景图上范围 - int right = max(max(max(p_leftTop_map.x, p_rightTop_map.x), p_rightBottom_map.x), p_leftBottom_map.x); - int left = min(min(min(p_leftTop_map.x, p_rightTop_map.x), p_rightBottom_map.x), p_leftBottom_map.x); - int top = min(min(min(p_leftTop_map.y, p_rightTop_map.y), p_rightBottom_map.y), p_leftBottom_map.y); - int bottom = max(max(max(p_leftTop_map.y, p_rightTop_map.y), p_rightBottom_map.y), p_leftBottom_map.y); + cv::warpPerspective(mask, _maskTmp, H, _panTmp.size(), cv::INTER_NEAREST, cv::BORDER_CONSTANT, cv::Scalar(0)); - int xRange = right - left; - int yRnage = bottom - top; + //// 收缩一圈消除虚线 + //cv::erode(warped_mask, warped_mask, + // cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(3, 3))); - //反映射到像素坐标 - MINMAXRECT32S RANGES = { 0 }; - RANGES.minY = std::max(0, top); - RANGES.maxY = std::min(_panImage.rows - 1, bottom); + _panTmp.copyTo(_panImage, _maskTmp); - RANGES.minX = std::max(0, left); - RANGES.maxX = std::min(_panImage.cols - 1, right); + // 保存遮罩 + _maskTmp.copyTo(_panMask, _maskTmp); - cv::Rect2d roi(RANGES.minX, RANGES.minY, RANGES.maxX - RANGES.minX, RANGES.maxY - RANGES.minY); + //imshow("mask", pan_mask); + //waitKey(1); - if (roi.width < 0) - { - int a = 0; - } + } - cv::Mat H_inv = H.inv(); +} -#ifdef ENABLE_CUDA - UpdatePan_CUDA(rgb, _panImage, roi, H_inv, _panPara, dep, cuda_resor); -#else - UpdatePan_CPU(rgb, _panImage, roi, H_inv, _panPara, dep); -#endif - } -} diff --git a/stitch/src/Arith_BlendMap.h b/stitch/src/Arith_BlendMap.h index 659222e..0375424 100644 --- a/stitch/src/Arith_BlendMap.h +++ b/stitch/src/Arith_BlendMap.h @@ -1,6 +1,6 @@ /*********版权所有(C)2025,武汉高德红外股份有限公司*************************************** * 文件名称:Arith_BlendMap.h -* 文件标识:拼接融合,提升全景图质量 +* 文件标识:全景图实体 * 内容摘要: * 其它说明: * 当前版本:V0.5 @@ -16,38 +16,55 @@ #include "FileCache.h" + +// 融合形式 +enum BLEND_TYPE +{ + DIRECT, //覆盖投影 + INCREASE, //增量投影 + DIS_BLEND //线性羽化 +}; + + + // 下视批量绘制 class MapBlend { public: - MapBlend(FileCache* cache); + MapBlend(FileCache* cache,UPanInfo upan_info); ~MapBlend(); + cv::Mat getPanImageMat(); + cv::Mat getPanMaskMat(); + public: - void MapFrame(cv::Mat src,cv::Mat H_Geo, cv::Mat mapH, cv::Mat pan, cv::Mat pan_mask); // 单帧投影帧到全景图 + void FrameMap(cv::Mat src,cv::Mat H_Geo, cv::Mat mapH, BLEND_TYPE blendType = DIRECT); // 单帧投影帧到全景图 + + void FrameMap_1(cv::Mat src, cv::Mat H_Geo, cv::Mat mapH); // 手写反向投影 - void DirectMap(vector frameInd, cv::Mat mapH, cv::Mat pan, cv::Mat pan_mask); //批量投影 - void BlendMap(vector frameInd, cv::Mat mapH, cv::Mat pan, cv::Mat pan_mask);//融合投影 +public: + void BatchMap(vector frameInd, cv::Mat mapH, BLEND_TYPE blendType = DIRECT); //从文件缓存批量投影 + private: FileCache* _cache; -}; -// 前视批量绘制 -class FrontMapBlend -{ -public: - FrontMapBlend(FileCache* cache); - ~FrontMapBlend(); -public: - void DirectMap(vector frameInd, FPanInfo _panPara, cv::Mat pan, cv::Mat pan_mask, cuda_Mem cuda_resor); //直接投影 + UPanInfo _upan_info; + + cv::Mat _H_pan;//全景图投影矩阵:从地理系到全景地图 + + cv::Mat _panImage; // 全景图 + + cv::Mat _panMask; //覆盖区域遮罩 private: - FileCache* _cache; + cv::Mat _panTmp; // 临时全景图 + cv::Mat _maskTmp;// 临时mask }; + #endif \ No newline at end of file diff --git a/stitch/src/Arith_UnderStitch.cpp b/stitch/src/Arith_UnderStitch.cpp index e327563..72d2447 100644 --- a/stitch/src/Arith_UnderStitch.cpp +++ b/stitch/src/Arith_UnderStitch.cpp @@ -57,7 +57,7 @@ UnderStitch::UnderStitch(std::string cachedir) _BATask = new BA_Task(_cache); - _BlendTask = new MapBlend(_cache); + /*_BlendTask = new MapBlend(_cache);*/ _panPara = { 0 }; @@ -83,6 +83,7 @@ UPanInfo UnderStitch::InitMap(FrameInfo info) cv::Mat H0 = _GeoSolver->findHomography(info); + // 计算视场中心的地理坐标 cv::Point2f ct_geo = warpPointWithH(H0, cv::Point2f(info.nWidth / 2, info.nHeight / 2)); @@ -90,7 +91,7 @@ UPanInfo UnderStitch::InitMap(FrameInfo info) double yaw_angle = info.craft.stAtt.fYaw; // 根据飞行方向调整全景图尺寸 - int base_size = MIN(info.nWidth * 5, 10000); + int base_size = MIN(info.nWidth * 10, 8000); UPanInfo panPara = { 0 }; // 根据偏航角计算宽高比 @@ -122,7 +123,7 @@ UPanInfo UnderStitch::InitMap(FrameInfo info) auto geo_pro_rect = warpRectWithH_2Rect(H0,cv::Size(info.nWidth,info.nHeight)); // 计算目标比例(单帧投影后占全景图的0.25) - float target_ratio = 0.2; + float target_ratio = 0.1; 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参数 @@ -220,6 +221,9 @@ UPanInfo UnderStitch::ReLocateMap(FrameInfo info) UPanInfo UnderStitch::Init(FrameInfo info) { + // 以向下初始化 + info.servoInfo.fServoPt = -90; + // 转为内部俯仰向下零位 info.servoInfo.fServoPt += 90; @@ -229,9 +233,8 @@ UPanInfo UnderStitch::Init(FrameInfo info) // 初始化全景图 _panPara = InitMap(info); - _panImage = cv::Mat(_panPara.m_pan_height, _panPara.m_pan_width, CV_8UC4, cv::Scalar(0,0,0,0)); - - _panMask = cv::Mat::zeros(_panPara.m_pan_height, _panPara.m_pan_width, CV_8UC1); + // 创建blender + _MapBlender = new MapBlend(_cache, _panPara); _lastFrameKey = 0; @@ -239,13 +242,11 @@ UPanInfo UnderStitch::Init(FrameInfo info) // 默认配置参数 _config.bOutGoogleTile = true; - _config.bUseBA = true; + _config.bUseBA = false; _config.bOutFrameTile = false; - // 重置全景图 - _panImage.setTo(0); - _panMask.setTo(0); + LOG_INFO("Init success"); return _panPara; @@ -295,7 +296,7 @@ SINT32 UnderStitch::Run(GD_VIDEO_FRAME_S frame, FrameInfo para) // 转为内部俯仰向下零位 para.servoInfo.fServoPt += 90; - // 视频帧筛选 + //// 视频帧筛选 if (!FilterFrame(para)) { return -1; @@ -303,6 +304,9 @@ SINT32 UnderStitch::Run(GD_VIDEO_FRAME_S frame, FrameInfo para) LOG_INFO("Run frame {} ,_totalFrameCnt {}",para.nFrmID,_totalFrameCnt); + cv::Mat panImage = _MapBlender->getPanImageMat(); + cv::Mat panMask = _MapBlender->getPanMaskMat(); + // 获取地理投影H RtK rtk; cv::Mat H_geo = _GeoSolver->findHomography(para, rtk); @@ -316,15 +320,15 @@ SINT32 UnderStitch::Run(GD_VIDEO_FRAME_S frame, FrameInfo para) // 输出当前所有瓦片,再不输出就会丢失当前全景中的瓦片 LOG_INFO("ExportTileSet"); - _googleProduct.ExportTileSet(_panImage, _panMask, _taskTilesVec, _outDir, _filename + std::to_string(_lastFrameKey)); + _googleProduct.ExportTileSet(panImage, panMask, _taskTilesVec, _outDir, _filename + std::to_string(_lastFrameKey)); // 重新初始化投影参数 _panPara = ReLocateMap(para); - _panImage.setTo(0); - _panMask.setTo(0); + panImage.setTo(0); + panMask.setTo(0); - _BlendTask->DirectMap(_recvFrameKey, _H_pan, _panImage,_panMask); + //_MapBlender->BatchMap(_recvFrameKey, _H_pan); return 0; } @@ -332,21 +336,26 @@ SINT32 UnderStitch::Run(GD_VIDEO_FRAME_S frame, FrameInfo para) // 获取RGBA图像 cv::Mat src = getRGBAMatFromGDFrame(frame,frame.u64VirAddr[0]); - // 地理纠正 - TileInfo info = { 0 }; - cv::Mat Orth_Image = getOrthImage(src, para, info); - // 输出单帧谷歌瓦片 if (_config.bOutFrameTile) { + TileInfo info = { 0 }; + cv::Mat Orth_Image = getOrthImage(src, para, info); _googleProduct.ExportGeoPng(Orth_Image, info, _outDir + "/FrameTile"); } // 投影帧到全景 - _BlendTask->MapFrame(src, H_geo, _H_pan, _panImage,_panMask); + _MapBlender->FrameMap(src, H_geo, _H_pan); // 预处理+缓存 - ReceiveFrame(frame, para); + if (_config.bUseBA) + { + ReceiveFrame(frame, para); + } + + + // + if (_config.bUseBA && TriggerBA(para)) { @@ -380,11 +389,12 @@ SINT32 UnderStitch::OptAndOutCurrPan() } // 重投影所有帧到全景 - _BlendTask->DirectMap(_baWindowKey, _H_pan, _panImage,_panMask); + _MapBlender->BatchMap(_baWindowKey, _H_pan); // 输出当前所有瓦片 LOG_INFO("ExportTileSet"); - _googleProduct.ExportTileSet(_panImage, _panMask, _taskTilesVec, _outDir, _filename + std::to_string(_lastFrameKey)); + _googleProduct.ExportTileSet(_MapBlender->getPanImageMat(), _MapBlender->getPanMaskMat(), + _taskTilesVec, _outDir, _filename + std::to_string(_lastFrameKey)); return _baWindowKey.size(); } @@ -402,9 +412,9 @@ bool UnderStitch::FilterFrame(FrameInfo para) } // 扫描角过大,跳过 - if (ABS(para.servoInfo.fServoPt) > 45) + if (ABS(para.servoInfo.fServoPt) > 25) { - //return false; + return false; } // 获取当前帧的Geo覆盖 @@ -523,61 +533,67 @@ bool UnderStitch::TriggerBA(FrameInfo para) return false; } -// 废弃,改为输出单帧正射后的瓦片 -void UnderStitch::CutTileRealTime() +bool UnderStitch::TriggerCutTile(FrameInfo para) { - // 在4倍降采样图上计算瓦片覆盖情况 - int downSampleR = 4; - - cv::Mat panMasksmall; - cv::resize(_panMask, panMasksmall, cv::Size(_panMask.cols / downSampleR, _panMask.rows / downSampleR)); - - // 计算覆盖图的积分图 - cv::Mat integralMask; - cv::integral(panMasksmall, integralMask, CV_32S); - - // 遍历已经在初始化阶段得到的瓦片划分 - auto tiles = _taskTilesVec; - - int cnt = 0; - for (size_t i = 0; i < tiles.size(); i++) - { - TileInfo* ptile = &_taskTilesVec[i]; - RECT32S box = ptile->boxInPan; - - // 跳过已输出块 - if (ptile->nStatus > 0) - { - //cnt++; - continue; - } - - SINT32 x1 = box.x/ downSampleR; - SINT32 y1 = box.y/ downSampleR; - SINT32 x2 = box.x/ downSampleR + box.w/ downSampleR; - SINT32 y2 = box.y/ downSampleR + box.h/ downSampleR; - - // 使用积分图快速计算区域和 - float maskSum = integralMask.at(y2, x2) - - integralMask.at(y1, x2) - - integralMask.at(y2, x1) - + integralMask.at(y1, x1); - - // 计算瓦片影像覆盖率 - float Coverage = maskSum * downSampleR * downSampleR / (box.w * box.h); - - if (Coverage > 0.95) - { - // 输出并标记 - ptile->nStatus = 1; - _googleProduct.ExportOneTile(_panImage, *ptile, _outDir, _filename); - } - - } - - + return false; } +//// 废弃,改为输出单帧正射后的瓦片 +//void UnderStitch::CutTileRealTime() +//{ +// // 在4倍降采样图上计算瓦片覆盖情况 +// int downSampleR = 4; +// +// cv::Mat panMasksmall; +// cv::resize(_MapBlender->getPanMaskMat(), panMasksmall, +// cv::Size(_MapBlender->getPanMaskMat().cols / downSampleR, _MapBlender->getPanMaskMat().rows / downSampleR)); +// +// // 计算覆盖图的积分图 +// cv::Mat integralMask; +// cv::integral(panMasksmall, integralMask, CV_32S); +// +// // 遍历已经在初始化阶段得到的瓦片划分 +// auto tiles = _taskTilesVec; +// +// int cnt = 0; +// for (size_t i = 0; i < tiles.size(); i++) +// { +// TileInfo* ptile = &_taskTilesVec[i]; +// RECT32S box = ptile->boxInPan; +// +// // 跳过已输出块 +// if (ptile->nStatus > 0) +// { +// //cnt++; +// continue; +// } +// +// SINT32 x1 = box.x/ downSampleR; +// SINT32 y1 = box.y/ downSampleR; +// SINT32 x2 = box.x/ downSampleR + box.w/ downSampleR; +// SINT32 y2 = box.y/ downSampleR + box.h/ downSampleR; +// +// // 使用积分图快速计算区域和 +// float maskSum = integralMask.at(y2, x2) +// - integralMask.at(y1, x2) +// - integralMask.at(y2, x1) +// + integralMask.at(y1, x1); +// +// // 计算瓦片影像覆盖率 +// float Coverage = maskSum * downSampleR * downSampleR / (box.w * box.h); +// +// if (Coverage > 0.95) +// { +// // 输出并标记 +// ptile->nStatus = 1; +// _googleProduct.ExportOneTile(_MapBlender->getPanImageMat(), *ptile, _outDir, _filename); +// } +// +// } +// +// +//} + SINT32 UnderStitch::ReceiveFrame(GD_VIDEO_FRAME_S img, FrameInfo para) { @@ -662,6 +678,13 @@ SINT32 UnderStitch::ReceiveFrame(GD_VIDEO_FRAME_S img, FrameInfo para) // 优化表初始化 _recvFrameOptFlag[para.nFrmID] = 0; + + // 缓存包围多边形 + _polygonAll.push_back(warpRectWithH(H0, cv::Size(img.u32Width, img.u32Height))); + + + + _totalFrameCnt++; return _totalFrameCnt; @@ -673,12 +696,14 @@ SINT32 UnderStitch::ReceiveFrame(GD_VIDEO_FRAME_S img, FrameInfo para) bool UnderStitch::ExportGeoPng() { + cv::Mat pan = _MapBlender->getPanImageMat(); + // 计算全景图的坐标范围 auto P1 = getBLHFromPan(cv::Point2f(0, 0), _H_pan); - auto P2 = getBLHFromPan(cv::Point2f(_panImage.cols, 0), _H_pan); + auto P2 = getBLHFromPan(cv::Point2f(pan.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 P3 = getBLHFromPan(cv::Point2f(pan.cols, pan.rows), _H_pan); + auto P4 = getBLHFromPan(cv::Point2f(0, pan.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); @@ -696,11 +721,16 @@ bool UnderStitch::ExportGeoPng() info.href = _filename + ".png"; // 输出谷歌地图产品 - _googleProduct.ExportGeoPng(_panImage, info, _outDir); + _googleProduct.ExportGeoPng(pan, info, _outDir); return 0; } +void UnderStitch::UpdateAdjacencyMap() +{ + // 根据 +} + cv::Mat UnderStitch::getOrthImage(cv::Mat src, FrameInfo para,TileInfo& tile) { @@ -752,18 +782,18 @@ cv::Mat UnderStitch::getOrthImage(cv::Mat src, FrameInfo para,TileInfo& tile) GD_VIDEO_FRAME_S UnderStitch::ExportPanAddr() { GD_VIDEO_FRAME_S pan_out; - + cv::Mat pan = _MapBlender->getPanImageMat(); pan_out.enPixelFormat = GD_PIXEL_FORMAT_RGB_PACKED; pan_out.u32Width = _panPara.m_pan_width; pan_out.u32Height = _panPara.m_pan_height; - pan_out.u64VirAddr[0] = _panImage.data; + pan_out.u64VirAddr[0] = pan.data; return pan_out; } cv::Mat UnderStitch::ExportPanMat() { - return _panImage; + return _MapBlender->getPanImageMat(); } PointBLH UnderStitch::getBLHFromPan(cv::Point2f ptInPan, cv::Mat _H_panPara) diff --git a/stitch/src/Arith_UnderStitch.h b/stitch/src/Arith_UnderStitch.h index 1b73227..8e931c6 100644 --- a/stitch/src/Arith_UnderStitch.h +++ b/stitch/src/Arith_UnderStitch.h @@ -9,8 +9,17 @@ #include "Arith_BlendMap.h" #include #include "utils/Arith_ThreadPool.h" +#include #include "Logger.h" + +// 关键帧间邻接信息 +struct EdgeInfo +{ + float IOU; +}; + + class UnderStitch :public API_UnderStitch { public: @@ -49,8 +58,11 @@ private: // 触发BA优化时机 bool TriggerBA(FrameInfo para); + // 触发瓦片裁切输出 + bool TriggerCutTile(FrameInfo para); + // 实时裁切瓦片 - void CutTileRealTime(); + //void CutTileRealTime(); // 接收帧 SINT32 ReceiveFrame(GD_VIDEO_FRAME_S img, FrameInfo para); @@ -58,6 +70,9 @@ private: // 输出地理产品:kml png全景 bool ExportGeoPng(); + // 更新邻接关系矩阵 + void UpdateAdjacencyMap(); + // 正射校正 cv::Mat getOrthImage(cv::Mat src, FrameInfo para, TileInfo& tile); @@ -75,6 +90,12 @@ private: vector _baWindowKey;// BA滑动窗口 std::map _recvFrameOptFlag;//接收帧的优化标记 + // 邻接关系表 + std::unordered_map> _AdjacencyMap; + + vector> _polygonAll;//帧包围四边形 + + // FileCache* _cache;//文件缓存,存储外部传入的原始帧信息以及预处理结果 // 保存上一帧信息 @@ -90,7 +111,7 @@ private: BA_Task* _BATask;//BA - MapBlend* _BlendTask;// 融合模块 + MapBlend* _MapBlender;// 融合模块 private: UPanInfo _panPara;//全景图配置 @@ -101,13 +122,9 @@ private: vector _taskTilesVec;//划分的小瓦片方案 - cv::Mat _panImage; - - cv::Mat _panMask; //覆盖区域遮罩 - int _totalFrameCnt;//处理帧计数 - UPanConfig _config; + UPanConfig _config; // 算法控制参数 private: @@ -118,4 +135,5 @@ private: std::threadpool _threadPool; // 线程池 + }; \ No newline at end of file diff --git a/stitch/src/Arith_FeaStitch.cpp b/stitch/src/FeaStitch/Arith_FeaStitch.cpp similarity index 100% rename from stitch/src/Arith_FeaStitch.cpp rename to stitch/src/FeaStitch/Arith_FeaStitch.cpp diff --git a/stitch/src/Arith_FeaStitch.h b/stitch/src/FeaStitch/Arith_FeaStitch.h similarity index 100% rename from stitch/src/Arith_FeaStitch.h rename to stitch/src/FeaStitch/Arith_FeaStitch.h diff --git a/stitch/src/FrontStitch/Arith_FrontBlend.cpp b/stitch/src/FrontStitch/Arith_FrontBlend.cpp new file mode 100644 index 0000000..033c3f0 --- /dev/null +++ b/stitch/src/FrontStitch/Arith_FrontBlend.cpp @@ -0,0 +1,129 @@ +#include "Arith_FrontBlend.h" +#include "Arith_Utils.h" +#include "Arith_FrontSolver.h" + + +using std::max; +using std::min; + + +FrontMapBlend::FrontMapBlend(FileCache* cache) +{ + _cache = cache; +} + +FrontMapBlend::~FrontMapBlend() +{ +} + +void FrontMapBlend::DirectMap(vector frameInd, FPanInfo _panPara, cv::Mat _panImage, cv::Mat pan_mask, cuda_Mem cuda_resor) +{ + _panImage.setTo(0); + + // 从文件缓存获取帧 + auto _t_frame_cache = std::make_shared(); + for (size_t i = 0; i < frameInd.size(); i++) + { + KeyType key = frameInd[i]; + bool flag = _cache->get(key, _t_frame_cache); + + if (!flag) + { + continue; + } + + // 读取当前H + cv::Mat H = cv::Mat(3, 3, CV_64FC1, _t_frame_cache->H); + + auto info = _t_frame_cache->_para; + + float dep = info.nEvHeight; + + GD_VIDEO_FRAME_S img = _t_frame_cache->_frame_info; + + // 获取图像数据 + cv::Mat rgb = getRGBMatFromGDFrame(_t_frame_cache->_frame_info, _t_frame_cache->_data); + + + //Pole CT_map = getPoleFromImgWithH(H, cv::Point2f(img.u32Width/2, img.u32Height/2), info.nEvHeight); + + // 计算帧的map四至,注意这里不是线性变化,不能完全保证四至约束下的原图能完全覆盖,即反采样后图像可能不完整 + Pole leftTop_map = getPoleFromImgWithH(H, cv::Point2f(0, 0), info.nEvHeight); + Pole rightTop_map = getPoleFromImgWithH(H, cv::Point2f(img.u32Width, 0), info.nEvHeight); + Pole rightBottom_map = getPoleFromImgWithH(H, cv::Point2f(img.u32Width, img.u32Height), info.nEvHeight); + Pole leftBottom_map = getPoleFromImgWithH(H, cv::Point2f(0, img.u32Height), info.nEvHeight); + + //leftTop_map.beta = DEGLIM360(leftTop_map.beta); + //rightTop_map.beta = DEGLIM360(rightTop_map.beta); + //rightBottom_map.beta = DEGLIM360(rightBottom_map.beta); + //leftBottom_map.beta = DEGLIM360(leftBottom_map.beta); + + // 转全景图的像方 + cv::Point2f p_leftTop_map = getFPanFromPole(leftTop_map, _panPara); + cv::Point2f p_rightTop_map = getFPanFromPole(rightTop_map, _panPara); + cv::Point2f p_rightBottom_map = getFPanFromPole(rightBottom_map, _panPara); + cv::Point2f p_leftBottom_map = getFPanFromPole(leftBottom_map, _panPara); + + // 计算全景图上范围 + int right = max(max(max(p_leftTop_map.x, p_rightTop_map.x), p_rightBottom_map.x), p_leftBottom_map.x); + int left = min(min(min(p_leftTop_map.x, p_rightTop_map.x), p_rightBottom_map.x), p_leftBottom_map.x); + int top = min(min(min(p_leftTop_map.y, p_rightTop_map.y), p_rightBottom_map.y), p_leftBottom_map.y); + int bottom = max(max(max(p_leftTop_map.y, p_rightTop_map.y), p_rightBottom_map.y), p_leftBottom_map.y); + + int xRange = right - left; + int yRnage = bottom - top; + + //反映射到像素坐标 + MINMAXRECT32S RANGES = { 0 }; + RANGES.minY = std::max(0, top); + RANGES.maxY = std::min(_panImage.rows - 1, bottom); + + RANGES.minX = std::max(0, left); + RANGES.maxX = std::min(_panImage.cols - 1, right); + + cv::Rect2d roi(RANGES.minX, RANGES.minY, RANGES.maxX - RANGES.minX, RANGES.maxY - RANGES.minY); + + if (roi.width < 0) + { + int a = 0; + } + + cv::Mat H_inv = H.inv(); + + +#ifdef ENABLE_CUDA + UpdatePan_CUDA(rgb, _panImage, roi, H_inv, _panPara, dep, cuda_resor); +#else + UpdatePan_CPU(rgb, _panImage, roi, H_inv, _panPara, dep); +#endif + + + } + +} + + + +void UpdatePan_CPU(cv::Mat rgbFrame, cv::Mat pan, cv::Rect2d roi, cv::Mat H_inv, FPanInfo _panPara, float dep) +{ + cv::Mat subImg = pan(roi); + // 遍历整个子图 +#pragma omp parallel for + for (int i = 0; i < (int)roi.height; i++) + { + for (int j = 0; j < (int)roi.width; j++) + { + cv::Point2f p_img = getImgPosFromPole(H_inv, getPoleFromFPan(Point2f(j + roi.x, i + roi.y), _panPara), dep); + if (p_img.x >= 200 && p_img.y >= 200 && p_img.x < rgbFrame.cols - 200 && p_img.y < rgbFrame.rows - 200) + { + auto rgbVal = Interpolation_RGB24(rgbFrame.data, rgbFrame.cols, rgbFrame.rows, p_img.x, p_img.y); + uchar* row = subImg.ptr(i); + row[3 * j + 0] = rgbVal.R; + row[3 * j + 1] = rgbVal.G; + row[3 * j + 2] = rgbVal.B; + + } + } + } + +} diff --git a/stitch/src/FrontStitch/Arith_FrontBlend.h b/stitch/src/FrontStitch/Arith_FrontBlend.h new file mode 100644 index 0000000..b40bd16 --- /dev/null +++ b/stitch/src/FrontStitch/Arith_FrontBlend.h @@ -0,0 +1,19 @@ +#include "FileCache.h" +#include "utils/Arith_Utils.h" +// 前视批量绘制 +class FrontMapBlend +{ +public: + FrontMapBlend(FileCache* cache); + ~FrontMapBlend(); +public: + void DirectMap(vector frameInd, FPanInfo _panPara, cv::Mat pan, cv::Mat pan_mask, cuda_Mem cuda_resor); //直接投影 + +private: + FileCache* _cache; +}; +void UpdatePan_CPU(cv::Mat rgbFrame, cv::Mat pan, cv::Rect2d roi, cv::Mat h_inv, FPanInfo _panPara, float dep); + +#ifdef ENABLE_CUDA +void UpdatePan_CUDA(cv::Mat rgbFrame, cv::Mat pan, cv::Rect2d roi, cv::Mat h_inv, FPanInfo _panPara, float dep, cuda_Mem cuda_resor); +#endif diff --git a/stitch/src/Arith_FrontSolver.cpp b/stitch/src/FrontStitch/Arith_FrontSolver.cpp similarity index 100% rename from stitch/src/Arith_FrontSolver.cpp rename to stitch/src/FrontStitch/Arith_FrontSolver.cpp diff --git a/stitch/src/Arith_FrontSolver.h b/stitch/src/FrontStitch/Arith_FrontSolver.h similarity index 100% rename from stitch/src/Arith_FrontSolver.h rename to stitch/src/FrontStitch/Arith_FrontSolver.h diff --git a/stitch/src/Arith_FrontStitch.cpp b/stitch/src/FrontStitch/Arith_FrontStitch.cpp similarity index 89% rename from stitch/src/Arith_FrontStitch.cpp rename to stitch/src/FrontStitch/Arith_FrontStitch.cpp index 9cdbff9..bd9b067 100644 --- a/stitch/src/Arith_FrontStitch.cpp +++ b/stitch/src/FrontStitch/Arith_FrontStitch.cpp @@ -62,7 +62,7 @@ FPanInfo FrontStitch::Init(FrameInfo info, ScanRange stAzRange, ScanRange stPtRa info.camInfo.fAglReso = info.camInfo.fPixelSize / info.camInfo.nFocus * 0.06; } - LOG_INFO("init--info.FCOUS:{}", info.camInfo.nFocus); + //LOG_INFO("init--info.FCOUS:{}", info.camInfo.nFocus); // 视场角计算 float FOV_W = info.camInfo.fAglReso * info.nWidth; @@ -105,10 +105,10 @@ FPanInfo FrontStitch::Init(FrameInfo info, ScanRange stAzRange, ScanRange stPtRa cv::Mat H4 = _GeoSolver->findHomography(info); Pole Pole_bottom = getPoleFromImgWithH(H4, cv::Point2f(info.nWidth, info.nHeight), info.nEvHeight); - LOG_INFO("init--Pole_west {} {}", Pole_west.beta, Pole_west.alpha); - LOG_INFO("init--Pole_east {} {}", Pole_east.beta, Pole_east.alpha); - LOG_INFO("init--Pole_up {} {}", Pole_up.beta, Pole_up.alpha); - LOG_INFO("init--Pole_bottom {} {}", Pole_bottom.beta, Pole_bottom.alpha); + //LOG_INFO("init--Pole_west {} {}", Pole_west.beta, Pole_west.alpha); + //LOG_INFO("init--Pole_east {} {}", Pole_east.beta, Pole_east.alpha); + //LOG_INFO("init--Pole_up {} {}", Pole_up.beta, Pole_up.alpha); + //LOG_INFO("init--Pole_bottom {} {}", Pole_bottom.beta, Pole_bottom.alpha); // 俯仰up范围直接使用预设,不再计算 Pole_up.alpha = MAX(stPtRange.Agl1 - 90, Pole_up.alpha); @@ -127,15 +127,15 @@ FPanInfo FrontStitch::Init(FrameInfo info, ScanRange stAzRange, ScanRange stPtRa _panPara.fAglRes = MAX(info.camInfo.fAglReso * 2, XmapLimitAglRes); - LOG_INFO("_panPara.fAglRes:{},XmapLimitAglRes:{}", _panPara.fAglRes, XmapLimitAglRes); + //LOG_INFO("_panPara.fAglRes:{},XmapLimitAglRes:{}", _panPara.fAglRes, XmapLimitAglRes); // 全景图规模 _panPara.m_pan_width = DEGLIM360(Pole_east.beta - Pole_west.beta) / _panPara.fAglRes; _panPara.m_pan_height = DEGLIM360(Pole_up.alpha - Pole_bottom.alpha) / _panPara.fAglRes; - LOG_INFO("AzRange:{}", Pole_east.beta - Pole_west.beta); - LOG_INFO("PtRange:{}", Pole_up.alpha - Pole_bottom.alpha); + //LOG_INFO("AzRange:{}", Pole_east.beta - Pole_west.beta); + //LOG_INFO("PtRange:{}", Pole_up.alpha - Pole_bottom.alpha); _panImage = cv::Mat::zeros(_panPara.m_pan_height, _panPara.m_pan_width, CV_8UC3); @@ -148,7 +148,7 @@ FPanInfo FrontStitch::Init(FrameInfo info, ScanRange stAzRange, ScanRange stPtRa cudaMalloc((void**)&_cuda_mem.global_cuda_pan_mask, _panPara.m_pan_width * _panPara.m_pan_height * 1);//gray #endif - LOG_INFO("Init pan w:{} h:{}", _panPara.m_pan_width, _panPara.m_pan_height); + //LOG_INFO("Init pan w:{} h:{}", _panPara.m_pan_width, _panPara.m_pan_height); //POINT32F pt = { 1000,200 }; @@ -478,27 +478,3 @@ POINT32F FrontStitch::getPanUVFromBLH(PointBLH pos) return pt; } -void UpdatePan_CPU(cv::Mat rgbFrame, cv::Mat pan, cv::Rect2d roi, cv::Mat H_inv, FPanInfo _panPara, float dep) -{ - cv::Mat subImg = pan(roi); - // 遍历整个子图 - #pragma omp parallel for - for (int i = 0; i < (int)roi.height; i++) - { - for (int j = 0; j < (int)roi.width; j++) - { - cv::Point2f p_img = getImgPosFromPole(H_inv, getPoleFromFPan(Point2f(j + roi.x, i + roi.y), _panPara), dep); - if (p_img.x >= 200 && p_img.y >= 200 && p_img.x < rgbFrame.cols - 200 && p_img.y < rgbFrame.rows - 200) - { - auto rgbVal = Interpolation_RGB24(rgbFrame.data, rgbFrame.cols, rgbFrame.rows, p_img.x, p_img.y); - uchar* row = subImg.ptr(i); - row[3 * j + 0] = rgbVal.R; - row[3 * j + 1] = rgbVal.G; - row[3 * j + 2] = rgbVal.B; - - } - } - } - -} - diff --git a/stitch/src/Arith_FrontStitch.h b/stitch/src/FrontStitch/Arith_FrontStitch.h similarity index 93% rename from stitch/src/Arith_FrontStitch.h rename to stitch/src/FrontStitch/Arith_FrontStitch.h index a4ec8f4..f7d7d8f 100644 --- a/stitch/src/Arith_FrontStitch.h +++ b/stitch/src/FrontStitch/Arith_FrontStitch.h @@ -22,7 +22,7 @@ #include "Arith_BATask.h" #include #include "Arith_FrontSolver.h" -#include "Arith_BlendMap.h" +#include "Arith_FrontBlend.h" // 水平扫描方向 enum FSCAN_DIR @@ -168,11 +168,6 @@ private: }; -void UpdatePan_CPU(cv::Mat rgbFrame, cv::Mat pan, cv::Rect2d roi, cv::Mat h_inv, FPanInfo _panPara, float dep); - -#ifdef ENABLE_CUDA -void UpdatePan_CUDA(cv::Mat rgbFrame, cv::Mat pan, cv::Rect2d roi, cv::Mat h_inv, FPanInfo _panPara, float dep, cuda_Mem cuda_resor); -#endif diff --git a/stitch/src/mapKernel.cu b/stitch/src/FrontStitch/mapKernel.cu similarity index 99% rename from stitch/src/mapKernel.cu rename to stitch/src/FrontStitch/mapKernel.cu index 0e9f6ce..7bb6204 100644 --- a/stitch/src/mapKernel.cu +++ b/stitch/src/FrontStitch/mapKernel.cu @@ -3,6 +3,7 @@ #include "Arith_SysStruct.h" #include "StitchStruct.h" + __device__ Pole device_getPoleFromFPan(POINT32S pt, FPanInfo _panPara) { Pole _pole = { 0 }; diff --git a/stitch/src/StitchStruct.h b/stitch/src/StitchStruct.h index 09e1184..80f5049 100644 --- a/stitch/src/StitchStruct.h +++ b/stitch/src/StitchStruct.h @@ -53,6 +53,8 @@ struct Match_Net + + #define IMG_CACHE_SIZE (1920 * 1080 * 3) //图像缓存尺寸 #define FEA_NUM_MAX 500 // 单帧特征点数量 #define FEA_DES_SIZE 128 // 特征点描述子尺度 diff --git a/stitch/src/Version.h b/stitch/src/Version.h index 8ccfd7b..aa79ebf 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_10_29-16.20.58"; +std::string BUILD_TIME = "BUILD_TIME 2025_11_02-22.09.40"; std::string VERSION = "BUILD_VERSION 1.0.1"; diff --git a/stitch/src/utils/Arith_Utils.cpp b/stitch/src/utils/Arith_Utils.cpp index 7f10dde..09b5f6e 100644 --- a/stitch/src/utils/Arith_Utils.cpp +++ b/stitch/src/utils/Arith_Utils.cpp @@ -565,4 +565,142 @@ PXL_VAL Interpolation_RGB24(UBYTE8* pSrcImage, int iWidth, int iHeight, float x, return VAL; -} \ No newline at end of file +} + +PXL_VAL Interpolation_RGBA32(UBYTE8* pSrcImage, int iWidth, int iHeight, float x, float y) +{ + PXL_VAL VAL = { 0 }; + int nStride = iWidth * 4; + + // 四个最临近象素的坐标(i1, j1), (i2, j1), (i1, j2), (i2, j2) + int i1, i2; + int j1, j2; + + // 四个最临近象素值 + UBYTE8 f1[4], f2[4], f3[4], f4[4]; + + // 计算四个最临近象素的坐标 + i1 = MAX((int)x, 0); + i2 = MIN(i1 + 1, iWidth - 1); + j1 = MAX((int)y, 0); + j2 = MIN(j1 + 1, iHeight - 1); + + float t, s; //t = x - [x], s = y- [y]; + + t = x - i1; + s = y - j1; + + //四个最临近象素点的值的加权值 + float uv0, uv1, uv2, uv3; //权重之和为1:uv0+uv1+uv2+uv3 = 1 + uv0 = (1 - s) * (1 - t); + uv1 = (1 - s) * t; + uv2 = (1 - t) * s; + uv3 = s * t; + + + // 最近邻吧 不差这点质量了 + VAL.R = *(pSrcImage + nStride * j1 + i1 * 4); + VAL.G = *(pSrcImage + nStride * j1 + i1 * 4 + 1); + VAL.B = *(pSrcImage + nStride * j1 + i1 * 4 + 2); + VAL.A = *(pSrcImage + nStride * j1 + i1 * 4 + 3); + return VAL; + + + + //根据不同情况分别处理 + if (x - iWidth + 1 > 0) + { + // 要计算的点在图像右边缘上 + if (y - iHeight + 1 < 0) + { + // 要计算的点正好是图像最右/左下角那一个象素,直接返回该点象素值 + VAL.R = *(pSrcImage + nStride * j1 + i1 * 4); + VAL.G = *(pSrcImage + nStride * j1 + i1 * 4 + 1); + VAL.B = *(pSrcImage + nStride * j1 + i1 * 4 + 2); + VAL.A = *(pSrcImage + nStride * j1 + i1 * 4 + 3); + return VAL; + } + else + { + // 在图像右/左边缘上且不是最后一点,直接一次插值即可 + f1[0] = *(pSrcImage + nStride * j1 + i1 * 4); + f1[1] = *(pSrcImage + nStride * j1 + i1 * 4 + 1); + f1[2] = *(pSrcImage + nStride * j1 + i1 * 4 + 2); + f1[3] = *(pSrcImage + nStride * j1 + i1 * 4 + 3); + + f3[0] = *(pSrcImage + nStride * j2 + i1 * 4 + 0); + f3[1] = *(pSrcImage + nStride * j2 + i1 * 4 + 1); + f3[2] = *(pSrcImage + nStride * j2 + i1 * 4 + 2); + f3[3] = *(pSrcImage + nStride * j2 + i1 * 4 + 3); + + VAL.R = f1[0] + s * (f3[0] - f1[0]); + VAL.G = f1[1] + s * (f3[1] - f1[1]); + VAL.B = f1[2] + s * (f3[2] - f1[2]); + VAL.A = f1[3] + s * (f3[3] - f1[3]); + + // 返回插值结果 + return VAL; + } + } + else if (y - iHeight + 1 > 0) + { + // 要计算的点在图像下边缘上且不是最后一点,直接一次插值即可 + //f1 = *(pSrcImage + iWidth * j1 + i1); + //f2 = *(pSrcImage + iWidth * j1 + i2); + + f1[0] = *(pSrcImage + nStride * j1 + i1 * 4); + f1[1] = *(pSrcImage + nStride * j1 + i1 * 4 + 1); + f1[2] = *(pSrcImage + nStride * j1 + i1 * 4 + 2); + f1[3] = *(pSrcImage + nStride * j1 + i1 * 4 + 3); + + f2[0] = *(pSrcImage + nStride * j1 + i2 * 4 + 0); + f2[1] = *(pSrcImage + nStride * j1 + i2 * 4 + 1); + f2[2] = *(pSrcImage + nStride * j1 + i2 * 4 + 2); + f2[3] = *(pSrcImage + nStride * j1 + i2 * 4 + 3); + + VAL.R = f1[0] + t * (f2[0] - f1[0]); + VAL.G = f1[1] + t * (f2[1] - f1[1]); + VAL.B = f1[2] + t * (f2[2] - f1[2]); + VAL.A = f1[3] + t * (f2[3] - f1[3]); + // 返回插值结果 + return VAL; + } + else + { + // 计算四个最临近象素值 + f1[0] = *(pSrcImage + nStride * j1 + i1 * 4); + f1[1] = *(pSrcImage + nStride * j1 + i1 * 4 + 1); + f1[2] = *(pSrcImage + nStride * j1 + i1 * 4 + 2); + f1[3] = *(pSrcImage + nStride * j1 + i1 * 4 + 3); + + f2[0] = *(pSrcImage + nStride * j1 + i2 * 4); + f2[1] = *(pSrcImage + nStride * j1 + i2 * 4 + 1); + f2[2] = *(pSrcImage + nStride * j1 + i2 * 4 + 2); + f2[3] = *(pSrcImage + nStride * j1 + i2 * 4 + 3); + + f3[0] = *(pSrcImage + nStride * j2 + i1 * 4); + f3[1] = *(pSrcImage + nStride * j2 + i1 * 4 + 1); + f3[2] = *(pSrcImage + nStride * j2 + i1 * 4 + 2); + f3[3] = *(pSrcImage + nStride * j2 + i1 * 4 + 3); + + f4[0] = *(pSrcImage + nStride * j2 + i2 * 4); + f4[1] = *(pSrcImage + nStride * j2 + i2 * 4 + 1); + f4[2] = *(pSrcImage + nStride * j2 + i2 * 4 + 2); + f4[3] = *(pSrcImage + nStride * j2 + i2 * 4 + 3); + + VAL.R = uv0 * f1[0] + uv1 * f2[0] + uv2 * f3[0] + uv3 * f4[0]; + VAL.G = uv0 * f1[1] + uv1 * f2[1] + uv2 * f3[1] + uv3 * f4[1]; + VAL.B = uv0 * f1[2] + uv1 * f2[2] + uv2 * f3[2] + uv3 * f4[2]; + VAL.A = uv0 * f1[3] + uv1 * f2[3] + uv2 * f3[3] + uv3 * f4[3]; + return VAL; + } + + + + return VAL; +} + +PXL_VAL Nearest_RGBA32(UBYTE8* pSrcImage, int iWidth, int iHeight, float x, float y) +{ + return PXL_VAL(); +} diff --git a/stitch/src/utils/Arith_Utils.h b/stitch/src/utils/Arith_Utils.h index 8302362..4d0eb8d 100644 --- a/stitch/src/utils/Arith_Utils.h +++ b/stitch/src/utils/Arith_Utils.h @@ -15,6 +15,7 @@ typedef struct tagPXL_VAL UBYTE8 R; UBYTE8 G; UBYTE8 B; + UBYTE8 A; }PXL_VAL; @@ -25,6 +26,10 @@ cv::Mat getRGBMatFromGDFrame(GD_VIDEO_FRAME_S frame, void* pArr); unsigned char FourPointInterpolation(unsigned char* pSrcImage, int iWidth, int iHeight, float x, float y); PXL_VAL Interpolation_RGB24(UBYTE8* pSrcImage, int iWidth, int iHeight, float x, float y); +PXL_VAL Interpolation_RGBA32(UBYTE8* pSrcImage, int iWidth, int iHeight, float x, float y); + +PXL_VAL Nearest_RGBA32(UBYTE8* pSrcImage, int iWidth, int iHeight, float x, float y); + // 矩阵加法 void AddMat(double* A, double* B, int mn, double* AaddB); diff --git a/tests/ProcDJ.cpp b/tests/ProcDJ.cpp index e2a96f7..f271da7 100644 --- a/tests/ProcDJ.cpp +++ b/tests/ProcDJ.cpp @@ -233,7 +233,7 @@ void ProcDJVideo(vector videoPathList, vector srtPathL cv::VideoCapture cap(videoPathList[vid]); std::vector srt = parseDJISRT(srtPathList[vid]); - cap.set(cv::CAP_PROP_POS_FRAMES, nStart - 1); + cap.set(cv::CAP_PROP_POS_FRAMES, nStart); int frmID = nStart; while (true) { @@ -315,6 +315,8 @@ void ProcDJVideo(vector videoPathList, vector srtPathL tm.stop(); + printf("cost time:%f\n", tm.getTimeSec()); + Mat pan_rgb, pan_rgb_ds; cv::cvtColor(mat_pan, pan_rgb, cv::COLOR_BGRA2BGR); @@ -344,6 +346,18 @@ void ProcDJVideo(vector videoPathList, vector srtPathL output.release(); + Mat pan_rgb, pan_rgb_ds; + cv::cvtColor(mat_pan, pan_rgb, cv::COLOR_BGRA2BGR); + + cv::resize(pan_rgb, pan_rgb_ds, cv::Size(pan_rgb.cols / 8, pan_rgb.rows / 8)); + + output.write(pan_rgb_ds); + + imshow("pan_rgb", pan_rgb_ds); + + cv::waitKey(0); + + } } @@ -360,7 +374,7 @@ int main() string folder = "F:/DJI_202504181507_016/"; - folder = "F:/"; + folder = "D:/H30T云台/DJI_202504181507_016/"; //string folder = "/media/wang/data/DJI_202504181507_016/"; // //videoPathList.push_back(folder + "DJI_20250418152649_0005_W.MP4"); @@ -368,9 +382,11 @@ int main() // videoPathList.push_back(folder + "DJI_20250418153043_0006_W.MP4"); //srtPathList.push_back(folder + "DJI_20250418153043_0006_W.srt"); + videoPathList.push_back(folder + "DJI_20250418152649_0005_W.MP4"); + srtPathList.push_back(folder + "DJI_20250418152649_0005_W.srt"); - videoPathList.push_back(folder + "DJI_20251024165313_0001_W.MP4"); - srtPathList.push_back(folder + "DJI_20251024165313_0001_W.srt"); + //videoPathList.push_back(folder + "DJI_20250418153043_0006_W.MP4"); + //srtPathList.push_back(folder + "DJI_20250418153043_0006_W.srt"); ProcDJVideo(videoPathList, srtPathList); return 0; diff --git a/tests/decodedata.cpp b/tests/decodedata.cpp index c9e1883..50b1c2a 100644 --- a/tests/decodedata.cpp +++ b/tests/decodedata.cpp @@ -195,6 +195,8 @@ void DecodeData::Init() m_ptrThread_DataDealwith = new std::thread(std::bind(&DecodeData::ThreadEntry, &DecodeData::ThreadFunData, (void*)this)); m_frontStitcher = API_FrontStitch::Create(); + + m_underStitcher = API_UnderStitch::Create("E:/google_tiles"); } void DecodeData::UnInit() @@ -628,12 +630,57 @@ int DecodeData::DecodeRGBImageAndPara(unsigned char *RGBBuffer, H264SEI_Para_732 - // 前视拼接初始化 + //// 前视拼接初始化 + //if (m_FrameNo == 1) + //{ + // ScanRange scanAz = { -30,30 }; + // ScanRange scanPt = { -10,-10 }; + // m_frontStitcher->Init(para, scanAz, scanPt); + //} + //else + //{ + // // 基于外参的快拼 + // GD_VIDEO_FRAME_S frame = { 0 };//输入帧 + // frame.enPixelFormat = GD_PIXEL_FORMAT_RGB_PACKED; + // frame.u32Width = bgr_mat.cols; + // frame.u32Height = bgr_mat.rows; + // frame.u64VirAddr[0] = bgr_mat.data; + // GD_VIDEO_FRAME_S pan = { 0 };//输出全景 + // //m_frontStitcher->Run(frame, para); + + + // AI_Target tt = { 0 }; + // tt.score = 0.6; + // tt.x1 = 100; + // tt.x2 = 110; + // tt.y1 = 200; + // tt.y2 = 250; + + // m_frontStitcher->Run(frame, para, &tt, 1); + + + // pan = m_frontStitcher->ExportPanAddr(); + // cv::Mat mat_pan = cv::Mat(pan.u32Height, pan.u32Width, CV_8UC3, pan.u64VirAddr[0]); + // // 显示全景图 + // cv::Mat res; + // cv::resize(mat_pan, res, cv::Size(pan.u32Width / 2, pan.u32Height / 2)); + // imshow("pan", res); + // cv::waitKey(1); + //} + + + if (m_FrameNo == 1) { - ScanRange scanAz = { -30,30 }; - ScanRange scanPt = { -10,-10 }; - m_frontStitcher->Init(para, scanAz, scanPt); + m_underStitcher->Init(para); + + UPanConfig cfg = { 0 }; + + cfg.bOutGoogleTile = true; + + m_underStitcher->SetConfig(cfg); + + m_underStitcher->SetOutput("baotou", "E:/google_tiles"); } else { @@ -644,29 +691,26 @@ int DecodeData::DecodeRGBImageAndPara(unsigned char *RGBBuffer, H264SEI_Para_732 frame.u32Height = bgr_mat.rows; frame.u64VirAddr[0] = bgr_mat.data; GD_VIDEO_FRAME_S pan = { 0 };//输出全景 - //m_frontStitcher->Run(frame, para); + cv::TickMeter tm; + tm.start(); - AI_Target tt = { 0 }; - tt.score = 0.6; - tt.x1 = 100; - tt.x2 = 110; - tt.y1 = 200; - tt.y2 = 250; + m_underStitcher->Run(frame, para); - m_frontStitcher->Run(frame, para, &tt, 1); + tm.stop(); + printf("time cost:%f\n", tm.getTimeSec()); - pan = m_frontStitcher->ExportPanAddr(); - cv::Mat mat_pan = cv::Mat(pan.u32Height, pan.u32Width, CV_8UC3, pan.u64VirAddr[0]); + pan = m_underStitcher->ExportPanAddr(); + cv::Mat mat_pan = cv::Mat(pan.u32Height, pan.u32Width, CV_8UC4, pan.u64VirAddr[0]); // 显示全景图 cv::Mat res; - cv::resize(mat_pan, res, cv::Size(pan.u32Width / 2, pan.u32Height / 2)); + cv::resize(mat_pan, res, cv::Size(pan.u32Width / 8, pan.u32Height / 8)); imshow("pan", res); cv::waitKey(1); } - return 0; + return 0; } diff --git a/tests/decodedata.h b/tests/decodedata.h index 50abceb..d24a8fb 100644 --- a/tests/decodedata.h +++ b/tests/decodedata.h @@ -23,6 +23,7 @@ extern "C" { #endif #include "API_FrontStitch.h" +#include "API_UnderStitch.h" #define UDP_BUFFER_LENGTHIMG 1024 #define UDPIMGWIDTH 1920 @@ -148,6 +149,8 @@ public: void DecodeH264Data(uint8_t *h264Data, long dataLength); API_FrontStitch* m_frontStitcher; + + API_UnderStitch* m_underStitcher; }; #endif // DECODEDATA_H diff --git a/tests/stitch_Genaral.cpp b/tests/stitch_Genaral.cpp index 87ff744..52e7e16 100644 --- a/tests/stitch_Genaral.cpp +++ b/tests/stitch_Genaral.cpp @@ -151,7 +151,7 @@ void Run_UnderStitch(std::string picFloder) Mat pan_rgb, pan_rgb_ds; cv::cvtColor(mat_pan, pan_rgb, cv::COLOR_BGRA2BGR); - cv::resize(pan_rgb, pan_rgb_ds, cv::Size(pan_rgb.cols / 4, pan_rgb.rows /4)); + cv::resize(pan_rgb, pan_rgb_ds, cv::Size(pan_rgb.cols / 8, pan_rgb.rows /4)); imshow("", pan_rgb_ds); @@ -176,11 +176,9 @@ void Run_UnderStitch(std::string picFloder) int main(int argc, char** argv) { printf("Hello World General\n"); - //Run_FrontStitch("F:/S729/1_output"); - //Run_FrontStitch("F:/20250928_145957066_9前视扫描_output"); - //Run_UnderStitch("F:/20250928_145957066_9前视扫描_output"); - //Run_UnderStitch("F:/20250928_155516413_28房子_output"); + + Run_UnderStitch("F:/DJI_20250418152649_0005_W_output"); return 0; } \ No newline at end of file diff --git a/tests/stitch_udp.cpp b/tests/stitch_udp.cpp index d854015..5a37076 100644 --- a/tests/stitch_udp.cpp +++ b/tests/stitch_udp.cpp @@ -5,12 +5,12 @@ int main(int argc, char* argv[]) { DecodeData* m_DecodeData = new DecodeData(); - std::string filestr = "D:/Project/S732/udp_datat/720vi.dat"; + std::string filestr = "C:/Users/75929/Desktop/S732/1101VL.dat"; //std::string filestr = "E:/03855jcw/S732/H265data/1080ir2.dat"; m_DecodeData->m_CurrentVideo = 1;//0:红外,1:可见光 - m_DecodeData->m_LocalHeight = 10;// 本地海拔 + m_DecodeData->m_LocalHeight = 1365;// 本地海拔 m_DecodeData->Init(); m_DecodeData->processFileInChunks(filestr, 10 * 1024);