You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

556 lines
14 KiB

#include "API_UnderStitch.h"
#include "Arith_UnderStitch.h"
#include "Arith_Utils.h"
#include "Arith_CoordModule.h"
#include "Arith_FeaMatch.h"
#include <opencv2/opencv.hpp>
#include <omp.h>
#ifdef _WIN32
#include <direct.h>
#include <io.h>
#define MKDIR(dir) _mkdir(dir)
#define ACCESS _access
#else
#include <sys/stat.h>
#include <unistd.h>
#define MKDIR(dir) mkdir(dir, 0777)
#define ACCESS access
#endif
using namespace std;
using namespace cv;
API_UnderStitch* API_UnderStitch::Create(std::string cachedir)
{
return new UnderStitch(cachedir);
}
void API_UnderStitch::Destroy(API_UnderStitch* obj)
{
delete obj;
}
UnderStitch::UnderStitch(std::string cachedir)
{
// 创建缓存对象
_cache = new FileCache<FrameCache>(50, cachedir);
_GeoSolver = new GeoSolver();
_FeaMatcher = new FeatureMatcher(DetectorType::SIFT, MatcherType::FLANN);
_BATask = new BA_Task(_cache);
_BlendTask = new MapBlend(_cache);
_panPara = { 0 };
_totalFrameCnt = 0;
}
UnderStitch::~UnderStitch()
{
delete _GeoSolver;
delete _cache;
}
UPanInfo UnderStitch::InitMap(FrameInfo info)
{
// 设置拼接原点
_GeoSolver->SetOriginPoint(info);
cv::Mat H0 = _GeoSolver->findHomography(info);
cv::Point2f ct_geo = warpPointWithH(H0, cv::Point2f(info.nWidth / 2, info.nHeight / 2));// 视场中心的地理坐标
// 中心原始空间分辨率 M/p
double gsd = (info.nEvHeight * info.camInfo.fPixelSize) / (info.camInfo.nFocus * 1000);
// 全景图初始化
UPanInfo panPara = { 0 };
panPara.m_pan_width = MIN(info.nWidth * 5,8000);//全景宽
panPara.m_pan_height = MIN(info.nWidth * 5, 8000);//全景高
panPara.scale = 6;//比例尺,1m = ?pix
// 直接无平移解算
cv::Mat H_0 = getAffineFromGeo2Pan(panPara);
auto cur = warpPointWithH(H_0, ct_geo);
// 计算平移到全景图固定点的平移量,从此处开始拼接
int planX = panPara.m_pan_width/2 + 400 ;
int planY = panPara.m_pan_height;
panPara.map_shiftX = planX - (cur.x);//平移X
panPara.map_shiftY = planY - (cur.y);//平移Y
// 获取全景图的投影矩阵,全景图配置更新前保持不变
_H_pan = getAffineFromGeo2Pan(panPara);
// 计算全景图瓦片相关
auto P1 = getBLHFromPan(cv::Point2f(0, 0), _H_pan);
auto P2 = getBLHFromPan(cv::Point2f(panPara.m_pan_width, 0), _H_pan);
auto P3 = getBLHFromPan(cv::Point2f(panPara.m_pan_width, panPara.m_pan_height), _H_pan);
auto P4 = getBLHFromPan(cv::Point2f(0, panPara.m_pan_height), _H_pan);
auto minL = min(min(min(P1.L, P2.L), P3.L), P4.L);
auto maxL = max(max(max(P1.L, P2.L), P3.L), P4.L);
auto minB = min(min(min(P1.B, P2.B), P3.B), P4.B);
auto maxB = max(max(max(P1.B, P2.B), P3.B), P4.B);
_panTile.boxLatLon.north = maxB;
_panTile.boxLatLon.south = minB;
_panTile.boxLatLon.west = minL;
_panTile.boxLatLon.east = maxL;
_panTile.ind.z = _googleProduct.ZoomLevel(panPara.scale);
// 生成瓦片划分
_taskTilesVec = _googleProduct.CalcTileOverlayVec(_panTile, panPara.m_pan_width, panPara.m_pan_height);
return panPara;
}
UPanInfo UnderStitch::Init(FrameInfo info)
{
_panPara = InitMap(info);
_panImage = cv::Mat(_panPara.m_pan_height, _panPara.m_pan_width, CV_8UC4, cv::Scalar(255, 255, 255, 255));
_panMask = cv::Mat::zeros(_panPara.m_pan_height, _panPara.m_pan_width, CV_8UC1);
// 默认配置参数
_config.bOutGoogleTile = true;
_config.bUseBA = true;
_config.bOutFrameTile = true;
return _panPara;
}
void UnderStitch::SetOutput(std::string filename, std::string outdir)
{
_filename = filename;
_outDir = outdir;
_kmlPath = _outDir + "/" + _filename + ".kml";
// 创建输出目录
if (ACCESS(_outDir.c_str(), 0) != 0)
{
MKDIR(_outDir.c_str());
}
}
void UnderStitch::SetConfig(UPanConfig config)
{
_config = config;
}
SINT32 UnderStitch::Run(GD_VIDEO_FRAME_S img, FrameInfo para)
{
// 快拼
GeoStitch(img, para);
if (_config.bUseBA)
{
// 预处理+缓存
ReceiveFrame(img, para);
// 按照需要执行优化
//if (0)
//{
// ProcessFrame(_recvFrameKey);
//}
}
// todo
// 推扫出视场,重新初始化全景图
return 0;
}
SINT32 UnderStitch::OptAndOutCurrPan()
{
if (_config.bUseBA)
{
// 优化所有帧
ProcessFrame(_recvFrameKey);
}
// 输出当前所有瓦片
_googleProduct.ExportTileSet(_panImage, _taskTilesVec, _outDir, _filename);
return _recvFrameKey.size();
}
//BYTE8 UnderStitch::GeoStitch(GD_VIDEO_FRAME_S img, FrameInfo para)
//{
// Proj t_Proj = _GeoStitcher->AnlayseTform(para);
//
// // 计算帧的map四至
// cv::Point2f leftTop_map = _GeoStitcher->back_project(cv::Point2f(0,0), t_Proj, _panPara);
// cv::Point2f rightTop_map = _GeoStitcher->back_project(cv::Point2f(img.u32Width,0), t_Proj, _panPara);
// cv::Point2f rightBottom_map = _GeoStitcher->back_project(cv::Point2f(img.u32Width,img.u32Height), t_Proj, _panPara);
// cv::Point2f leftBottom_map = _GeoStitcher->back_project(cv::Point2f(0,img.u32Height), t_Proj, _panPara);
//
//
// auto H = _GeoStitcher->findHomography(t_Proj,_panPara);
// leftTop_map = warpPointWithH(H,cv::Point2f(0,0));
// rightTop_map = warpPointWithH(H,cv::Point2f(img.u32Width,0));
// rightBottom_map = warpPointWithH(H,cv::Point2f(img.u32Width,img.u32Height));
// leftBottom_map = warpPointWithH(H,cv::Point2f(0,img.u32Height));
//
// // 计算全景图的范围
// 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(_pan.cols, bottom);
// int valid_left = std::max(0, left);
// int valid_right = std::min(_pan.rows, right);
//
//#pragma omp parallel for
// for (int i = valid_top; i < valid_bottom; i++)
// {
// for (int j = valid_left; j < valid_right; j++)
// {
// //转换为pixel坐标
// cv::Point2f p_img = _GeoStitcher->project(Point2f(j, i), t_Proj, _panPara);
//
// p_img = warpPointWithH(H.inv(),Point2f(j, i));
// if (p_img.x >= 0 && p_img.y >= 0 && p_img.x < img.u32Width && p_img.y < img.u32Height)
// {
// _pan.data[i * _pan.rows + j] =
// FourPointInterpolation(img.u64VirAddr[0], img.u32Width, img.u32Height, p_img.x, p_img.y);
// }
// }
// }
//
//
// return 0;
//}
SINT32 UnderStitch::GeoStitch(GD_VIDEO_FRAME_S img, FrameInfo para)
{
// 获取RGBA图像
cv::Mat src = getRGBAMatFromGDFrame(img,img.u64VirAddr[0]);
// 地理投影H1
cv::Mat H_geo = _GeoSolver->findHomography(para);
// 结合全景图投影H
cv::Mat H = _H_pan * H_geo;
TileInfo info = { 0 };
cv::Mat Orth_Image = getOrthImage(src, para, info);
if (_config.bOutFrameTile)
{
// 输出单帧谷歌瓦片
_googleProduct.ExportGeoPng(Orth_Image, info, _outDir + "/FrameTile");
}
//src = src.colRange(200, src.cols);
// 利用H投影当前帧到全景
cv::Mat imagetmp(_panImage.size(), CV_8UC3, cv::Scalar(0, 0, 0));
//cv::Mat imagetmp(_panImage.size(), CV_8UC3);
cv::warpPerspective(src, imagetmp, H, imagetmp.size(), cv::INTER_LINEAR, cv::BORDER_TRANSPARENT);
cv::Mat mask = cv::Mat::ones(src.size(), CV_8UC1);
cv::Mat warped_mask;
cv::warpPerspective(mask, warped_mask, H, imagetmp.size(), cv::INTER_LINEAR);
imagetmp.copyTo(_panImage, warped_mask);
// 覆盖区域掩码更新
_panMask = _panMask | warped_mask;
return 0;
}
// 废弃,改为输出单帧正射后的瓦片
void UnderStitch::CutTileRealTime()
{
// 在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);
}
}
}
SINT32 UnderStitch::ReceiveFrame(GD_VIDEO_FRAME_S img, FrameInfo para)
{
// 构造单帧缓存
auto _t_frame_cache = std::make_shared<FrameCache>();
_t_frame_cache->_para = para;
_t_frame_cache->_frame_info = img;
int imgSize = 0;
if(img.enPixelFormat == GD_PIXEL_FORMAT_GRAY_Y8)
{
imgSize = img.u32Height * img.u32Width;
}
if(img.enPixelFormat == GD_PIXEL_FORMAT_RGB_PACKED)
{
imgSize = img.u32Height * img.u32Width * 3;
}
if (img.enPixelFormat == GD_PIXEL_FORMAT_NV12)
{
imgSize = img.u32Height * img.u32Width * 1.5;
}
// 深拷贝图像数据
memcpy(_t_frame_cache->_data,img.u64VirAddr[0],imgSize);
// 提取特征点
std::vector<cv::KeyPoint> keypoints;
cv::Mat descriptors;
cv::Mat src = getRGBAMatFromGDFrame(img, img.u64VirAddr[0]);
_FeaMatcher->extractFeatures(src, keypoints, descriptors);
size_t keyNum = MIN(keypoints.size(), FEA_NUM_MAX);
_t_frame_cache->ptNum = keyNum;
// 保存特征点
memcpy(_t_frame_cache->_pt, keypoints.data(), sizeof(cv::KeyPoint) * keyNum);
// 保存描述子
memcpy(_t_frame_cache->_desp, descriptors.data, sizeof(float) * keyNum * FEA_DES_SIZE);
cv::Mat_<double> H0 = _GeoSolver->findHomography(para);
// 保存初始H
memcpy(_t_frame_cache->H, H0.data, sizeof(double) * 9);
// 预处理结果加入文件缓存
_cache->set(para.nFrmID,_t_frame_cache);
_recvFrameKey.push_back(para.nFrmID);
// 优化表初始化
_recvFrameOptFlag[para.nFrmID] = 0;
_totalFrameCnt++;
return _totalFrameCnt;
}
SINT32 UnderStitch::ProcessFrame(vector<KeyType> keys)
{
if (keys.size() == 0)
{
return -1;
}
// 优化所有帧
//_BATask->OptFrame(keys, _H_pan);
// 重投影所有帧到全景
_BlendTask->DirectMap(keys, _H_pan, _panImage,_panMask);
return 0;
}
bool UnderStitch::ExportGeoPng()
{
// 计算全景图的坐标范围
auto P1 = getBLHFromPan(cv::Point2f(0, 0), _H_pan);
auto P2 = getBLHFromPan(cv::Point2f(_panImage.cols, 0), _H_pan);
auto P3 = getBLHFromPan(cv::Point2f(_panImage.cols, _panImage.rows), _H_pan);
auto P4 = getBLHFromPan(cv::Point2f(0, _panImage.rows), _H_pan);
auto minL = min(min(min(P1.L, P2.L), P3.L), P4.L);
auto maxL = max(max(max(P1.L, P2.L), P3.L), P4.L);
auto minB = min(min(min(P1.B, P2.B), P3.B), P4.B);
auto maxB = max(max(max(P1.B, P2.B), P3.B), P4.B);
TileInfo info = { 0 };
info.boxLatLon.north = maxB;
info.boxLatLon.south = minB;
info.boxLatLon.west = minL;
info.boxLatLon.east = maxL;
info.tileName = _filename;
info.href = _filename + ".png";
// 输出谷歌地图产品
_googleProduct.ExportGeoPng(_panImage, info, _outDir);
return 0;
}
cv::Mat UnderStitch::getOrthImage(cv::Mat src, FrameInfo para,TileInfo& tile)
{
cv::Mat H = _GeoSolver->findHomography(para);
cv::Rect2f roi = warpRectWithH_2Rect(H, src.size());
float scale = src.cols / MAX(roi.width, roi.height);
float shiftX = -roi.x * scale;
float shiftY = (roi.y + roi.height)* scale;
Mat H_proj = (Mat_<double>(3, 3) << scale, 0, shiftX,
0, -scale, shiftY,
0, 0, 1
);
cv::Rect2f roi2 = warpRectWithH_2Rect(H_proj * H, src.size());
cv::Mat dst;
warpPerspective(src, dst, H_proj * H, cv::Size(roi2.width, roi2.height));
// 计算全景图的坐标范围
auto P1 = _GeoSolver->getBLHFromFrame(H,cv::Point2f(0, 0));
auto P2 = _GeoSolver->getBLHFromFrame(H, cv::Point2f(src.cols, 0));
auto P3 = _GeoSolver->getBLHFromFrame(H, cv::Point2f(src.cols, src.rows));
auto P4 = _GeoSolver->getBLHFromFrame(H, cv::Point2f(0, src.rows));
auto minL = min(min(min(P1.L, P2.L), P3.L), P4.L);
auto maxL = max(max(max(P1.L, P2.L), P3.L), P4.L);
auto minB = min(min(min(P1.B, P2.B), P3.B), P4.B);
auto maxB = max(max(max(P1.B, P2.B), P3.B), P4.B);
tile.boxLatLon.north = maxB;
tile.boxLatLon.south = minB;
tile.boxLatLon.west = minL;
tile.boxLatLon.east = maxL;
tile.tileName = _filename + std::to_string(para.nFrmID);
tile.href = _filename + std::to_string(para.nFrmID) + ".png";
return dst;
}
GD_VIDEO_FRAME_S UnderStitch::ExportPanAddr()
{
GD_VIDEO_FRAME_S pan_out;
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;
return pan_out;
}
cv::Mat UnderStitch::ExportPanMat()
{
return _panImage;
}
PointBLH UnderStitch::getBLHFromPan(cv::Point2f ptInPan, cv::Mat _H_panPara)
{
cv::Mat H_inv = _H_panPara.inv();
// 解算到局部地理系
cv::Point2f ptInGeo = warpPointWithH(H_inv,ptInPan);
return _GeoSolver->getBLHFromGeo(ptInGeo);
}
cv::Point2f UnderStitch::getPanXYFromBLH(PointBLH ptInBLH, cv::Mat _H_panPara)
{
// 经纬度转局部地理系
cv::Point2f ptGeo = _GeoSolver->getGeoFromBLH(ptInBLH);
// 投影到全景图
return warpPointWithH(_H_panPara, ptGeo);;
}
cv::Mat UnderStitch::getAffineFromGeo2Pan(UPanInfo _pan)
{
Mat H = (Mat_<double>(3, 3) << _pan.scale, 0, _pan.map_shiftX,
0, -_pan.scale, _pan.m_pan_height + _pan.map_shiftY,
0, 0, 1
);
return H;
}