基于均值坐标(Mean-Value Coordinates)的图像融合算法的优化实现
1. 概述
我在之前的文章《基于均值坐标(Mean-Value Coordinates)的图像融合算法的具体实现》中,根据《Coordinates for Instant Image Cloning》这篇论文,详细论述了图像融合中泊松融合算法的优化算法——均值坐标(Mean-Value Coordinates)融合算法的具体实现。其实在这篇论文中,还提出了两种优化实现,能够进一步提升效率,这里就论述一下其优化算法的具体实现。
2. 实现
2.1. 原理
均值坐标融合算法的核心思想是算出ROI中每个点的MVC(Mean-Value Coordinates),如果ROI中存在n个点,ROI边界像素为m个点,那么该算法的时间复杂度至少为O(nm)。根据《Coordinates for Instant Image Cloning》的描述,MVC融合算法修正值其实是一个线性区间,只要得到其中一些关键点的融合修正值,其他点的融合修正值就可以根据周围的融合修正值线性插值出来。
因此,可以通过CGAL来对ROI多边形边界构建一个自适应三角网,以边界上每个栅格点作为约束构网,为了满足Delaunay特性,就会在ROI内部新添加一些点,这样就会出现边界小而密集,边界大而稀疏的自适应三角网(可参看这篇文章《通过CGAL将一个多边形剖分成Delaunay三角网》):

这样,n个点就会将为常数级别个数c,也就是时间复杂度至少为O(cm)。当然从三角面上插值也会有时间消耗,但时间复杂度会明显小于计算均值坐标。
2.2. 核心代码
具体核心代码实现如下(完整的代码实现地址见文章末尾):
//三角网优化
void QImageShowWidget::MVCBlendOptimize(int posX, int posY)
{
QTime startTime = QTime::currentTime();
//Step1:找到边界上所有的像素点
vector<Vector2d> ROIBoundPointList;
CalBoundPoint(ROIBoundPointList);
//
CDT cdt;
vector<Vertex_handle> vertexList;
for(int i = 0; i<ROIBoundPointList.size(); i++)
{
//cout<<ROIBoundPointList[i].x<<','<<ROIBoundPointList[i].y<<'\t';
//vertexList.push_back(cdt.insert(Point(pointList[i].x(), pointList[i].y() )));
vertexList.push_back(cdt.insert(CDTPoint(ROIBoundPointList[i].x, ROIBoundPointList[i].y )));
}
for(unsigned int i =0;i<vertexList.size()-1;i++)
{
cdt.insert_constraint(vertexList[i],vertexList[i+1]);
}
std::cout << "Number of vertices: " << cdt.number_of_vertices() <<std::endl;
std::cout << "Meshing the triangulation..." << std::endl;
CGAL::refine_Delaunay_mesh_2(cdt, Criteria());
std::cout << "Number of vertices: " << cdt.number_of_vertices() <<std::endl;
vector<Vector2d> vertex_list;
map<Vector2d, size_t> vertex_map;
for(CDT::Vertex_iterator vit = cdt.vertices_begin(); vit!= cdt.vertices_end(); ++vit)
{
vertex_map.insert(make_pair(Vector2d(vit->point().x(), vit->point().y()), vertex_list.size()));
vertex_list.push_back(Vector2d(vit->point().x(), vit->point().y()));
}
//计算边界的像素差值
vector<int> diff;
for(size_t i = 0; i < ROIBoundPointList.size()-1; i++)
{
//size_t l = (size_t) srcImg.cols * ROIBoundPointList[i].y + ROIBoundPointList[i].x;
for(int bi = 0; bi < winBandNum; bi++)
{
size_t m = (size_t) dstImg.cols * winBandNum * (ROIBoundPointList[i].y + posY)+ winBandNum * (ROIBoundPointList[i].x + posX) + bi;
size_t n = (size_t) srcImg.cols * winBandNum * ROIBoundPointList[i].y + winBandNum * ROIBoundPointList[i].x + bi;
int d = (int)(dstImg.data[m]) - (int)(srcImg.data[n]);
diff.push_back(d);
//rMat.data[n] = d;
}
//clipMap[l] = false; //在多边形边上的点没法计算MVC
}
cout<<"开始计算 mean-value coordinates..." << endl;
vector<Vec3d> tri_mesh_vertex_R(vertex_list.size());
#pragma omp parallel for //开启OpenMP并行加速
for (int vi = 0; vi < vertex_list.size(); ++vi)
{
//逐点计算MVC
vector<double> alphaAngle(ROIBoundPointList.size());
for(size_t pi = 1; pi < ROIBoundPointList.size(); pi++)
{
alphaAngle[pi] = threePointCalAngle(ROIBoundPointList[pi-1], vertex_list[vi], ROIBoundPointList[pi]);
}
alphaAngle[0] = alphaAngle[ROIBoundPointList.size()-1];
vector<double> MVC(ROIBoundPointList.size()-1, 0);
for(size_t pi = 1; pi < ROIBoundPointList.size(); pi++)
{
double w_a = tan(alphaAngle[pi-1]/2) + tan(alphaAngle[pi]/2);
double w_b = (ROIBoundPointList[pi-1] - vertex_list[vi]).Mod();
MVC[pi-1] = w_a / w_b;
if(_isnan(MVC[pi-1])==1)
{
MVC[pi-1] = 0;
}
}
double sum = 0;
for(size_t pi = 0; pi < MVC.size(); pi++)
{
sum = sum + MVC[pi];
}
for(size_t pi = 0; pi < MVC.size(); pi++)
{
MVC[pi] = MVC[pi] / sum;
}
Vec3d r(0.0,0.0,0.0);
for(size_t pi = 0; pi < MVC.size(); pi++)
{
for(int bi = 0; bi < winBandNum; bi++)
{
r[bi] = r[bi] + MVC[pi] * diff[pi * winBandNum + bi];
}
}
tri_mesh_vertex_R[vi] = r;
}
cout<<"计算完成!" << endl;
//遍历每一个三角面
vector<vector<size_t>> face_vertex_index;
CDT::Face_iterator fit;
for (fit = cdt.faces_begin(); fit!= cdt.faces_end(); ++fit)
{
vector<size_t> index(3);
for(int i = 0; i<3; i++)
{
auto iter = vertex_map.find(Vector2d(fit->vertex(i)->point().x(), fit->vertex(i)->point().y()));
if(iter == vertex_map.end())
{
continue;
}
index[i] = iter->second;
}
face_vertex_index.push_back(index);
}
size_t srcImgBufNum = static_cast<size_t>(srcImg.cols) * static_cast<size_t>(srcImg.rows);
vector<size_t> clipMap(srcImgBufNum, 0); //标识范围内的点: 0标识初始不能写入,1以上标识在那个三角形
#pragma omp parallel for //开启OpenMP并行加速
for(int fi = 0; fi < face_vertex_index.size(); fi++)
{
Vector2d v0 = vertex_list[face_vertex_index[fi][0]];
Vector2d v1 = vertex_list[face_vertex_index[fi][1]];
Vector2d v2 = vertex_list[face_vertex_index[fi][2]];
double minX = std::min(std::min(v0.x, v1.x), v2.x);
double minY = std::min(std::min(v0.y, v1.y), v2.y);
double maxX = std::max(std::max(v0.x, v1.x), v2.x);
double maxY = std::max(std::max(v0.y, v1.y), v2.y);
int sX = std::max(int(floor(minX)), 0);
int sY = std::max(int(floor(minY)), 0);
int eX = std::max(int(ceil(maxX)), srcImg.cols - 1);
int eY = std::max(int(ceil(maxY)), srcImg.rows - 1);
for(int yi = sY; yi <= eY; yi++)
{
for(int xi = sX; xi <= eX; xi++)
{
if(PointinTriangle(Vector3d(v0), Vector3d(v1), Vector3d(v2), Vector3d(xi, yi, 0)))
{
size_t m = static_cast<size_t>(srcImg.cols) * static_cast<size_t>(yi) + xi;
clipMap[m] = fi+1;
}
}
}
}
cout<<"开始插值计算..." << endl;
//Mat result(srcImg.rows, srcImg.cols, CV_8UC1);
#pragma omp parallel for
for (int ri = 0; ri < srcImg.rows; ++ri)
{
for (int ci = 0; ci < srcImg.cols; ++ci)
{
size_t l = (size_t) srcImg.cols * ri + ci;
if(clipMap[l] == 0)
{
continue;
}
if(!Point_In_Polygon_2D(ci, ri, ROIBoundPointList))
{
continue;
}
size_t fi = clipMap[l]-1;
size_t index0 = face_vertex_index[fi][0];
size_t index1 = face_vertex_index[fi][1];
size_t index2 = face_vertex_index[fi][2];
vector<double> r(winBandNum, 0);
for(int bi = 0; bi < winBandNum; bi++)
{
Vector3d p0(vertex_list[index0].x, vertex_list[index0].y, tri_mesh_vertex_R[index0][bi]);
Vector3d p1(vertex_list[index1].x, vertex_list[index1].y, tri_mesh_vertex_R[index1][bi]);
Vector3d p2(vertex_list[index2].x, vertex_list[index2].y, tri_mesh_vertex_R[index2][bi]);
Vector3d vp(ci, ri, 0);
CalPlanePointZ(p0, p1, p2, vp);
r[bi] = vp.z;
}
for(int bi = 0; bi < winBandNum; bi++)
{
size_t n = (size_t) srcImg.cols * winBandNum * ri + winBandNum * ci + bi;
size_t m = (size_t) dstImg.cols * winBandNum * (ri + posY)+ winBandNum * (ci + posX) + bi;
dstImg.data[m] = min(max(srcImg.data[n] + r[bi], 0.0), 255.0);
}
}
}
//imwrite("D:/result.tif", result);
cout<<"插值完成!" << endl;
QTime stopTime = QTime::currentTime();
int elapsed = startTime.msecsTo(stopTime);
cout<<"总结完成用时"<<elapsed<<"毫秒";
}
主要思路还是通过ROI多边形栅格建立三角网,计算网格点的MVC,继而计算融合修正值;而其他点的融合修正值则通过所在三角形顶点的融合修正值插值得到。
注意这里麻烦的地方是还得计算每个点是在那个三角形内,我这里是采取索引数组的办法。如果直接采取遍历每个点与每个三角形的办法,那么时间复杂度可能会超过计算MVC的复杂度。而插值的算法可以参考这篇文章《已知空间三点组成的面求该面上某点的Z值》。
2.3. 第二种优化
在《Coordinates for Instant Image Cloning》这篇论文中,还介绍了第二种优化算法。算法思想是除了减少ROI内的点,还可以减少ROI边界上的点:每个点的MVC值其实可以不用到边界上所有的点,可以通过一种规则算法来指定需要的点。可惜这个规则算法我也没看明白,有机会再进一步研究。
3. 结果
融合的源影像:

融合的目标影像:

融合的结果:

运行的时间:

这里可以看到,优化后的融合效率为501毫秒,而优化之前的效率为1秒,效率提升了50%。
基于均值坐标(Mean-Value Coordinates)的图像融合算法的优化实现的更多相关文章
- 基于均值坐标(Mean-Value Coordinates)的图像融合算法的具体实现
目录 1. 概述 2. 实现 2.1. 准备 2.2. 核心 2.2.1. 均值坐标(Mean-Value Coordinates) 2.2.2. ROI边界栅格化 2.2.3. 核心实现 2.2.4 ...
- paper 101:图像融合算法及视觉艺术应用
1:基于泊松方程的图像融合方法,利用偏微分方程实现了不同图像上区域的无缝融合.比较经典的文章: P. Pérez, M. Gangnet, A. Blake. Poisson image editin ...
- 【图像配准】基于互信息的图像配准算法:MI、EMI、ECC算法
简单介绍: 基于互信息的图像配准算法以其较高的配准精度和广泛的适用性而成为图像配准领域研究的热点之中的一个.而基于互信息的医学图像配准方法被觉得是最好的配准方法之中的一个.基于此.本文将介绍简单的基于 ...
- 基于FPGA的线阵CCD实时图像采集系统
基于FPGA的线阵CCD实时图像采集系统 2015年微型机与应用第13期 作者:章金敏,张 菁,陈梦苇2016/2/8 20:52:00 关键词: 实时采集 电荷耦合器件 现场可编程逻辑器件 信号处理 ...
- 【VS开发】【图像处理】基于灰度世界、完美反射、动态阈值等图像自动白平衡算法的原理、实现及效果
基于灰度世界.完美反射.动态阈值等图像自动白平衡算法的原理.实现及效果 白平衡是电视摄像领域一个非常重要的概念,通过它可以解决色彩还原和色调处理的一系列问题.白平衡是随着电子影像再现色彩真实 ...
- 一种基于均值不等式的Listwise损失函数
一种基于均值不等式的Listwise损失函数 1 前言 1.1 Learning to Rank 简介 Learning to Rank (LTR) , 也被叫做排序学习, 是搜索中的重要技术, 其目 ...
- OpenCV探索之路(二十四)图像拼接和图像融合技术
图像拼接在实际的应用场景很广,比如无人机航拍,遥感图像等等,图像拼接是进一步做图像理解基础步骤,拼接效果的好坏直接影响接下来的工作,所以一个好的图像拼接算法非常重要. 再举一个身边的例子吧,你用你的手 ...
- 【HEVC帧间预测论文】P1.1 基于运动特征的HEVC快速帧间预测算法
基于运动特征的 HEVC 快速帧间预测算法/Fast Inter-Frame Prediction Algorithm for HEVC Based on Motion Features <HE ...
- [译]基于GPU的体渲染高级技术之raycasting算法
[译]基于GPU的体渲染高级技术之raycasting算法 PS:我决定翻译一下<Advanced Illumination Techniques for GPU-Based Volume Ra ...
随机推荐
- Qt QString类及常用函数功能详解
QString 是 Qt 编程中常用的类,除了用作数字量的输入输出之外,QString 还有很多其他功能,熟悉这些常见的功能,有助于灵活地实现字符串处理功能. QString 存储字符串釆用的是 Un ...
- 林轩田机器学习基石笔记2—Learning to Answer Yes/No
机器学习的整个过程:根据模型H,使用演算法A,在训练样本D上进行训练,得到最好的h,其对应的g就是我们最后需要的机器学习的模型函数,一般g接近于目标函数f.本节课将继续深入探讨机器学习问题,介绍感知机 ...
- 微信小游戏广告位iphonex底部适配问题
最近在公司开发游戏,使用cocos creator做微信小游戏,遇到一个很恶心的问题,如图: 如图所示,微信的广告位被iphonex的底部bar给弹出了一点位置,没有靠在底部. 在这里不得不吐槽一下微 ...
- vue项目根目录下index.html中的id="app",与src目录下的App.vue中的id="app"为什么不会冲突
感谢:https://blog.csdn.net/qq_35624642/article/details/78243413 index.html <body> <div id=&qu ...
- Android开发之《libyuv库的使用》
转自:http://bashell.nodemedia.cn/archives/build-google-libyuv-with-ndk-for-android.html 官方网站:https://c ...
- java处理数据库date类型数据
1.使用Date类和TimeStamp类的valueOf转换时间 Date.valueOf(); TimeStamp.valueOf(); 如果这两种转换直接用在sal语句上,那么最外层要 ...
- Flash Builder 使用
1. 解决编译慢的问题:用记事本打开安装目录下的 FlashBuilder.ini ,将里面设置的容量都扩大一半,如-Xms256m改为 -Xms512m,另外几项类似修改: 定位到相应版本的sdks ...
- Docker学习笔记_04 Rancher的部署安装(编排选用K8S)
原文地址:http://dbase.cc/2018/01/12/docker/04_rancher的部署安装/ 为什么要使用Rancher Rancher是一个开源的企业级容器管理平台.通过Ranch ...
- 初识JS之数据类型
JavaScript JavaScript引入方式 Script标签内写代码 <script> // 在这里写你的JS代码 </script> 引入额外的JS文件 <sc ...
- python入门机器学习,3行代码搞定线性回归
本文着重是重新梳理一下线性回归的概念,至于几行代码实现,那个不重要,概念明确了,代码自然水到渠成. “机器学习”对于普通大众来说可能会比较陌生,但是“人工智能”这个词简直是太火了,即便是风云变化的股市 ...