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)的图像融合算法的优化实现的更多相关文章

  1. 基于均值坐标(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 ...

  2. paper 101:图像融合算法及视觉艺术应用

    1:基于泊松方程的图像融合方法,利用偏微分方程实现了不同图像上区域的无缝融合.比较经典的文章: P. Pérez, M. Gangnet, A. Blake. Poisson image editin ...

  3. 【图像配准】基于互信息的图像配准算法:MI、EMI、ECC算法

    简单介绍: 基于互信息的图像配准算法以其较高的配准精度和广泛的适用性而成为图像配准领域研究的热点之中的一个.而基于互信息的医学图像配准方法被觉得是最好的配准方法之中的一个.基于此.本文将介绍简单的基于 ...

  4. 基于FPGA的线阵CCD实时图像采集系统

    基于FPGA的线阵CCD实时图像采集系统 2015年微型机与应用第13期 作者:章金敏,张 菁,陈梦苇2016/2/8 20:52:00 关键词: 实时采集 电荷耦合器件 现场可编程逻辑器件 信号处理 ...

  5. 【VS开发】【图像处理】基于灰度世界、完美反射、动态阈值等图像自动白平衡算法的原理、实现及效果

    基于灰度世界.完美反射.动态阈值等图像自动白平衡算法的原理.实现及效果      白平衡是电视摄像领域一个非常重要的概念,通过它可以解决色彩还原和色调处理的一系列问题.白平衡是随着电子影像再现色彩真实 ...

  6. 一种基于均值不等式的Listwise损失函数

    一种基于均值不等式的Listwise损失函数 1 前言 1.1 Learning to Rank 简介 Learning to Rank (LTR) , 也被叫做排序学习, 是搜索中的重要技术, 其目 ...

  7. OpenCV探索之路(二十四)图像拼接和图像融合技术

    图像拼接在实际的应用场景很广,比如无人机航拍,遥感图像等等,图像拼接是进一步做图像理解基础步骤,拼接效果的好坏直接影响接下来的工作,所以一个好的图像拼接算法非常重要. 再举一个身边的例子吧,你用你的手 ...

  8. 【HEVC帧间预测论文】P1.1 基于运动特征的HEVC快速帧间预测算法

    基于运动特征的 HEVC 快速帧间预测算法/Fast Inter-Frame Prediction Algorithm for HEVC Based on Motion Features <HE ...

  9. [译]基于GPU的体渲染高级技术之raycasting算法

    [译]基于GPU的体渲染高级技术之raycasting算法 PS:我决定翻译一下<Advanced Illumination Techniques for GPU-Based Volume Ra ...

随机推荐

  1. MySQL 之全文索引

    最近在复习数据库索引部分,看到了 fulltext,也即全文索引,虽然全文索引在平时的业务中用到的不多,但是感觉它有点儿意思,所以花了点时间研究一下,特此记录. 引入概念通过数值比较.范围过滤等就可以 ...

  2. 读书笔记之 数字图像处理的MATLAB实现(第2版)

  3. 解析Maven的settings.xml文件

    一.镜像问题 1.为什么需要配置maven国内镜像? 在不配置镜像的情况下,maven默认会使用中央库.--https://repo.maven.apache.org/maven2 maven中央库在 ...

  4. Python---1基础介绍

    因公司有自动化测试需求,开始自学python,跟着廖雪峰老师的教程,一边学习,一遍记笔记,将学习过程中,遇到的大大小小奇奇怪怪的问题,记录与此. 一.安装 Python是跨平台的,它可以运行在Wind ...

  5. 复合文字(Compound Literals)

    复合文字(Compound Literals) 阅读代码时发现了这行 1 setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&(int){1},sizeof(in ...

  6. Acwing 844.裸迷宫

    给定一个n*m的二维整数数组,用来表示一个迷宫,数组中只包含0或1,其中0表示可以走的路,1表示不可通过的墙壁. 最初,有一个人位于左上角(1, 1)处,已知该人每次可以向上.下.左.右任意一个方向移 ...

  7. 使用Lucene.Net做一个简单的搜索引擎-全文索引

    Lucene.Net Lucene.net是Lucene的.net移植版本,是一个开源的全文检索引擎开发包,即它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎. ...

  8. Typora+PicGo+Gitee笔记方案

    前言:需要学习的知识太多,从一开始就在寻找一款能让我完全满意的编辑器,然而一直都没有令我满意的.在前两天Typora新版本更新后,总算是拥有了一套我认为很完美的笔记方案:使用Typora编写markd ...

  9. 整合 KAFKA+Flink 实例(第一部分,趟坑记录)

    2017年后,一大波网络喧嚣,说流式处理如何牛叉,如何高大上,抱歉,工作满负荷,没空玩那个: 今年疫情隔离在家,无聊,开始学习 KAFKA+Flink ,目前的打算是用爬虫抓取网页数据,传递到Kafk ...

  10. 46-Python深浅拷贝

    目录 一.引言 1.1 可变或不可变 二.拷贝 三.浅拷贝 深拷贝 一.引言 在python中,对象赋值实际上是对象的引用.当创建一个对象,然后把它赋给另一个变量的时候,python并没有拷贝这个对象 ...