工程目录整理

main
wangchongwu 1 month ago
parent 6df1bd31e3
commit 0a7723da16

@ -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})

Binary file not shown.

@ -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
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document>
<GroundOverlay>
<name>DJI6830</name>
<Icon>
<href>DJI6830.png</href>
</Icon>
<LatLonBox>
<north>30.267059</north>
<south>30.264926</south>
<east>114.446180</east>
<west>114.443523</west>
</LatLonBox>
</GroundOverlay>
</Document>
</kml>
```
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】一定要提供边扫描边输出瓦片能力

@ -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
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document>
<GroundOverlay>
<name>DJI6830</name>
<Icon>
<href>DJI6830.png</href>
</Icon>
<LatLonBox>
<north>30.267059</north>
<south>30.264926</south>
<east>114.446180</east>
<west>114.443523</west>
</LatLonBox>
</GroundOverlay>
</Document>
</kml>
```
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. 补充 前视扫描全景图像方<->经纬高<->伺服导引角的关系计算

@ -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, '熊司一起飞点', '这是一个测试标记点<br>点击查看详细信息');
// 初始加载KML文件设置为false可以禁用KML加载
loadKMLFiles(true);
<!-- loadKMLFiles(true); -->
</script>
</body>

@ -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)

@ -360,13 +360,13 @@ void BA_Task::OptFrame(vector<KeyType> 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<KeyType> frameInd,cv::Mat H_map)
// 输出结果
std::cout << summary.FullReport() << std::endl;
//std::cout << summary.BriefReport() << std::endl;
#ifndef OPT_H
// 将优化se3写入H

@ -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<FrameCache>* cache)
MapBlend::MapBlend(FileCache<FrameCache>* 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<KeyType> frameInd, cv::Mat mapH, cv::Mat pan, cv::Mat pan_mask)
{
// 从文件缓存获取帧
auto _t_frame_cache = std::make_shared<FrameCache>();
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<cv::Mat> 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<KeyType> frameInd, cv::Mat mapH, cv::Mat pan, cv::Mat pan_mask)
#include <omp.h>
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<FrameCache>* 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<uchar>(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<KeyType> frameInd, FPanInfo _panPara, cv::Mat _panImage, cv::Mat pan_mask, cuda_Mem cuda_resor)
void MapBlend::BatchMap(vector<KeyType> frameInd, cv::Mat mapH, BLEND_TYPE blendType)
{
_panImage.setTo(0);
// 从文件缓存获取帧
auto _t_frame_cache = std::make_shared<FrameCache>();
for (size_t i = 0; i < frameInd.size(); i++)
@ -138,71 +224,47 @@ void FrontMapBlend::DirectMap(vector<KeyType> 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
}
}

@ -1,6 +1,6 @@
/*********版权所有C2025武汉高德红外股份有限公司***************************************
* Arith_BlendMap.h
*
*
*
*
* V0.5
@ -16,38 +16,55 @@
#include "FileCache.h"
// 融合形式
enum BLEND_TYPE
{
DIRECT, //覆盖投影
INCREASE, //增量投影
DIS_BLEND //线性羽化
};
// 下视批量绘制
class MapBlend
{
public:
MapBlend(FileCache<FrameCache>* cache);
MapBlend(FileCache<FrameCache>* 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<KeyType> frameInd, cv::Mat mapH, cv::Mat pan, cv::Mat pan_mask); //批量投影
void BlendMap(vector<KeyType> frameInd, cv::Mat mapH, cv::Mat pan, cv::Mat pan_mask);//融合投影
public:
void BatchMap(vector<KeyType> frameInd, cv::Mat mapH, BLEND_TYPE blendType = DIRECT); //从文件缓存批量投影
private:
FileCache<FrameCache>* _cache;
};
// 前视批量绘制
class FrontMapBlend
{
public:
FrontMapBlend(FileCache<FrameCache>* cache);
~FrontMapBlend();
public:
void DirectMap(vector<KeyType> 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<FrameCache>* _cache;
cv::Mat _panTmp; // 临时全景图
cv::Mat _maskTmp;// 临时mask
};
#endif

@ -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<int>(y2, x2)
- integralMask.at<int>(y1, x2)
- integralMask.at<int>(y2, x1)
+ integralMask.at<int>(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<int>(y2, x2)
// - integralMask.at<int>(y1, x2)
// - integralMask.at<int>(y2, x1)
// + integralMask.at<int>(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)

@ -9,8 +9,17 @@
#include "Arith_BlendMap.h"
#include <map>
#include "utils/Arith_ThreadPool.h"
#include <unordered_map>
#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<KeyType> _baWindowKey;// BA滑动窗口
std::map<KeyType, int> _recvFrameOptFlag;//接收帧的优化标记
// 邻接关系表
std::unordered_map<KeyType, std::unordered_map<KeyType, EdgeInfo>> _AdjacencyMap;
vector<vector<cv::Point2f>> _polygonAll;//帧包围四边形
//
FileCache<FrameCache>* _cache;//文件缓存,存储外部传入的原始帧信息以及预处理结果
// 保存上一帧信息
@ -90,7 +111,7 @@ private:
BA_Task* _BATask;//BA
MapBlend* _BlendTask;// 融合模块
MapBlend* _MapBlender;// 融合模块
private:
UPanInfo _panPara;//全景图配置
@ -101,13 +122,9 @@ private:
vector<TileInfo> _taskTilesVec;//划分的小瓦片方案
cv::Mat _panImage;
cv::Mat _panMask; //覆盖区域遮罩
int _totalFrameCnt;//处理帧计数
UPanConfig _config;
UPanConfig _config; // 算法控制参数
private:
@ -118,4 +135,5 @@ private:
std::threadpool _threadPool; // 线程池
};

@ -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<FrameCache>* cache)
{
_cache = cache;
}
FrontMapBlend::~FrontMapBlend()
{
}
void FrontMapBlend::DirectMap(vector<KeyType> frameInd, FPanInfo _panPara, cv::Mat _panImage, cv::Mat pan_mask, cuda_Mem cuda_resor)
{
_panImage.setTo(0);
// 从文件缓存获取帧
auto _t_frame_cache = std::make_shared<FrameCache>();
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<uchar>(i);
row[3 * j + 0] = rgbVal.R;
row[3 * j + 1] = rgbVal.G;
row[3 * j + 2] = rgbVal.B;
}
}
}
}

@ -0,0 +1,19 @@
#include "FileCache.h"
#include "utils/Arith_Utils.h"
// 前视批量绘制
class FrontMapBlend
{
public:
FrontMapBlend(FileCache<FrameCache>* cache);
~FrontMapBlend();
public:
void DirectMap(vector<KeyType> frameInd, FPanInfo _panPara, cv::Mat pan, cv::Mat pan_mask, cuda_Mem cuda_resor); //直接投影
private:
FileCache<FrameCache>* _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

@ -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<uchar>(i);
row[3 * j + 0] = rgbVal.R;
row[3 * j + 1] = rgbVal.G;
row[3 * j + 2] = rgbVal.B;
}
}
}
}

@ -22,7 +22,7 @@
#include "Arith_BATask.h"
#include <map>
#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

@ -3,6 +3,7 @@
#include "Arith_SysStruct.h"
#include "StitchStruct.h"
__device__ Pole device_getPoleFromFPan(POINT32S pt, FPanInfo _panPara)
{
Pole _pole = { 0 };

@ -53,6 +53,8 @@ struct Match_Net
#define IMG_CACHE_SIZE (1920 * 1080 * 3) //图像缓存尺寸
#define FEA_NUM_MAX 500 // 单帧特征点数量
#define FEA_DES_SIZE 128 // 特征点描述子尺度

@ -2,5 +2,5 @@
#pragma once
#include <string>
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";

@ -565,4 +565,142 @@ PXL_VAL Interpolation_RGB24(UBYTE8* pSrcImage, int iWidth, int iHeight, float x,
return VAL;
}
}
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; //权重之和为1uv0+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();
}

@ -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);

@ -233,7 +233,7 @@ void ProcDJVideo(vector<std::string> videoPathList, vector<std::string> srtPathL
cv::VideoCapture cap(videoPathList[vid]);
std::vector<FrameData> 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<std::string> videoPathList, vector<std::string> 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<std::string> videoPathList, vector<std::string> 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;

@ -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;
}

@ -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

@ -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;
}

@ -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);

Loading…
Cancel
Save