这里要跟大家分享的paper为基于特征线的图像 morphing,对应的英文文献为《Feature-Based Image Metamorphosis》,是1992年SIGGRAPH 上的一篇paper,比较老的一篇paper,然而这篇paper引用率非常高,用于图像变形效果还是挺不错的,这个算法一般用于图像的morphing。因为这篇paper算法原理简单,易于实现,所以不用怕学习这个算法需要多长的时间。

开始之前先声明一下,这篇博文主要参考自:http://www.csie.ntu.edu.tw/~b97074/vfx_html/hw1.html  同时结合我自己的理解,跟大家分享算法,帮助更多爱好者学习,非商业用途。

一、相关理论

我们知道图像变形的本质,其实就是求取光流场,就是求取目标图像的每一个像素点在原图像的对应位置点,然后同过双线性插值的方法就可以求得目标图像。

1、单线段约束变形:

给定原图像Source Image,我们希望把原图像上的像素点P'、Q'位置移动到P、Q位置,那么其它的像素点的位置要怎么移动,才能使得得到的结果图像Destination Image不会发生严重扭曲,这便是图像变形的研究内容。如图所示,如果用逆向映射的变形方法,对于目标图像上的任意一点X,我们只需要求取source image 上的对应点X'就可以了,当然逆向映射X'往往不是整数,需要经过线性插值,获取X'的像素值。

已知PQ,P'Q',X点,我们要怎么求出X的对应点X'呢?

其实很简单,其原理是通过保证图中二维(u,v)坐标不变就可以了,也就是我们可以通过上面的三个计算公式,求出X'。说的简单一点呢,就是保证X相对于PQ的比例位置坐标(u,v)不变。

2、多线段约束变形

上面是对于单线段约束而言的,对于多线段的情况,主要是通过加权平均的方法。

如图,现在已知P1Q1,P2Q2,X,以及源图像的P1'Q1',P2'Q2',我们要求取X'点。

这个时候我们可以先用单线段约束的方法

(1)通过P1Q1 、P1'Q1'、X 计算出X1’;

(2)通过P2Q2 、P2'Q2'、X 计算出X2’;

然后通过加权平均的方法,求出X':

其中权值w的计算方法就是通过点X到线段的距离成反比的函数计算:

其中length表示线段的长度,dist表示点X到线段的最短距离。a,b,p为常数,对于它们的取值我们可以选p = 0 , a = 1 , b = 2。

ok,到了这里算法就结束了,感觉松松,我们就可以计算出X’点,

因为X'计算出来一般不可能刚好位于原图像像素点的位置,因此我们需要通过双线性插值的方法,求取X'的像素值。

看完上面应该知道怎么计算X'点了吧。接着我们要进入算法实现阶段。

二、算法实现

说明以下代码参考自:http://www.csie.ntu.edu.tw/~b97074/vfx_html/hw1.html

Algorithm:

1、根据公式1,2,计算X点相对于各线段的位置(u,v)坐标。

[cpp] view plaincopy
  1. double new_u = dst_line.Getu(X);
  2. double new_v = dst_line.Getv(X);
[cpp] view plaincopy
  1. //公式1
  2. double Line::Getu(Vector2 X)
  3. {
  4. double X_P_x = X.x - P.x;
  5. double X_P_y = X.y - P.y;
  6. double Q_P_x = Q.x - P.x;
  7. double Q_P_y = Q.y - P.y;
  8. double u = ((X_P_x * Q_P_x) + (X_P_y * Q_P_y)) / (len * len)  ;
  9. return u ;
  10. }
  11. //公式2
  12. double Line::Getv(Vector2 X){
  13. double X_P_x = X.x - P.x;
  14. double X_P_y = X.y - P.y;
  15. double Q_P_x = Q.x - P.x;
  16. double Q_P_y = Q.y - P.y;
  17. double Perp_Q_P_x = Q_P_y ;
  18. double Perp_Q_P_y = -Q_P_x ;
  19. double v = ((X_P_x * Perp_Q_P_x) + (X_P_y * Perp_Q_P_y))/len ;
  20. return v ;
  21. }

2、根据公式3,然后反算X点在源图像的位置对应点X'。

[cpp] view plaincopy
  1. Vector2 src_point = src_line.Get_Point(new_u , new_v);
[cpp] view plaincopy
  1. //根据u,v坐标可计算出pq线段的对应点x
  2. Vector2 Line::Get_Point(double u , double v)
  3. {
  4. double Q_P_x = Q.x - P.x;
  5. double Q_P_y = Q.y - P.y;
  6. double Perp_Q_P_x = Q_P_y ;
  7. double Perp_Q_P_y = -Q_P_x ;
  8. double Point_x = P.x + u * (Q.x - P.x) + ((v * Perp_Q_P_x)/len) ;
  9. double Point_y = P.y + u * (Q.y - P.y) + ((v * Perp_Q_P_y)/len) ;
  10. Vector2 X;
  11. X.x = Point_x;
  12. X.y = Point_y;
  13. return X ;
  14. }

3、计算各个线段的权重。

[cpp] view plaincopy
  1. double src_weight = dst_line.Get_Weight(dst_point);
[cpp] view plaincopy
  1. double Line::Get_Weight(Vector2 X )
  2. {
  3. double a = parameter_a;
  4. double b = parameter_b;
  5. double p = parameter_p;
  6. double d = 0.0;
  7. double u = Getu(X);
  8. if(u > 1.0 )
  9. d = sqrt((X.x - Q.x) * (X.x - Q.x) + (X.y - Q.y) * (X.y - Q.y));
  10. else if(u < 0)
  11. d = sqrt((X.x - P.x) * (X.x - P.x) + (X.y - P.y) * (X.y - P.y));
  12. else
  13. d = abs(Getv(X));
  14. double weight =pow(pow((float)len,(float)p)/(a + d) , b);
  15. return weight;
  16. }

然后对所有的X'点进行加权求和就可以了。

ok,上面过程的代码合在一起,遍历每一条约束线段。

4、最后进行双线性插值。

双线性插值函数如下:

[cpp] view plaincopy
  1. void bilinear(BitmapData *psrcImgData,float X ,float Y,byte*resultpiexl)
  2. {
  3. int x_floor = (int)X ;
  4. int y_floor = (int)Y ;
  5. int x_ceil = x_floor + 1 ;
  6. int y_ceil = y_floor + 1 ;
  7. float a = X - x_floor ;
  8. float b = Y - y_floor ;
  9. if(x_ceil >= psrcImgData->Width-1)
  10. x_ceil =psrcImgData->Width-1 ;
  11. if(y_ceil >= psrcImgData->Height-1)
  12. y_ceil = psrcImgData->Height-1 ;
  13. byte leftdown[3];
  14. byte lefttop[3];
  15. byte rightdown[3];
  16. byte righttop[3];
  17. Get2D(psrcImgData,y_floor,x_floor,leftdown);
  18. Get2D(psrcImgData,y_ceil,x_floor,lefttop);
  19. Get2D(psrcImgData,y_floor,x_ceil,rightdown);
  20. Get2D(psrcImgData,y_ceil,x_ceil,righttop);
  21. for(int i = 0 ; i < 3 ; i ++)
  22. {
  23. resultpiexl[i] = (1-a)*(1-b)*leftdown[i] + a*(1-b)*rightdown[i] + a*b*righttop[i] + (1-a)*b*lefttop[i];
  24. }
  25. }
  26. void Get2D(BitmapData *psrcImgData, int Y,int X, byte*piexl)
  27. {
  28. byte*pdata=(byte*)psrcImgData->Scan0+(psrcImgData->Width*Y+X)*4;
  29. for (int i=0;i<3;i++)
  30. {
  31. piexl[i]=pdata[i];
  32. }
  33. }


最后贴一下整个过程的代码:

[cpp] view plaincopy
  1. int nWidth=prightImgData->Width;
  2. int nHeight=prightImgData->Height;
  3. for(int x = 0 ; x < nWidth ; x++)
  4. {
  5. for(int y = 0 ; y < nHeight ; y++)
  6. {
  7. Vector2 dst_point ;
  8. dst_point.x= x ;
  9. dst_point.y= y;
  10. double leftXSum_x = 0.0;
  11. double leftXSum_y = 0.0;
  12. double leftWeightSum = 0.0;
  13. double rightXSum_x = 0.0;
  14. double rightXSum_y = 0.0;
  15. double rightWeightSum = 0.0;
  16. for(int i = 0 ; i < pairs.size() ; i++)
  17. {
  18. Line src_line = pairs[i].leftLine;
  19. Line dst_line = pairs[i].rightLine;
  20. double new_u = dst_line.Getu(X);//计算(u,v)坐标
  21. double new_v = dst_line.Getv(X);
  22. Vector2 src_point = src_line.Get_Point(new_u , new_v);//计算源图像的对应点X'
  23. double src_weight = dst_line.Get_Weight(dst_point);//计算权重
  24. leftXSum_x = leftXSum_x + (double)src_point.x * src_weight ;//加权求X'的平均位置
  25. leftXSum_y = leftXSum_y + (double)src_point.y * src_weight ;
  26. leftWeightSum = leftWeightSum + src_weight ;
  27. }
  28. double left_src_x = leftXSum_x / leftWeightSum;
  29. double left_src_y = leftXSum_y / leftWeightSum;
  30. double right_src_x = x;
  31. double right_src_y = y;
  32. if(left_src_x<0)//判断是否越界
  33. left_src_x=0;
  34. if(left_src_y<0)
  35. left_src_y=0;
  36. if(left_src_x>=pleftImgData->Width)
  37. left_src_x=pleftImgData->Width-1;
  38. if(left_src_y>=pleftImgData->Height)
  39. left_src_y=pleftImgData->Height-1;
  40. byte leftimg[3];//存储最后的(x,y)点的像素值
  41. bilinear(pleftImgData,left_src_x,left_src_y,leftimg);//线性插值
  42. for (int i=0;i<3;i++)
  43. {
  44. float newpiexl=leftimg[i];
  45. }
  46. }
  47. }

本文地址:http://blog.csdn.net/hjimce/article/details/45531039     作者:hjimce     联系qq:1393852684   更多资源请关注我的博客:http://blog.csdn.net/hjimce                原创文章,转载请保留本行信息。

最后看一下,用这个算法实现的变形融合:

原图像:

变形融合结果:

参考文献:

1、http://www.csie.ntu.edu.tw/~b97074/vfx_html/hw1.html
2、《Feature-Based Image Metamorphosis》

图像处理(十)基于特征线的图像变形-Siggraph 1992相关推荐

  1. 图像控制点 形变_基于控制点的图像变形方法的研究与实现

    基于控制点的图像变形方法的研究与实现 林军 ; 李新华 [期刊名称] <北京电力高等专科学校学报 ( 自然科学版 ) > [年 ( 卷 ), 期] 2011(028)005 [摘要] 根据 ...

  2. 图像处理(十九)基于移动最小二乘的图像变形-Siggraph 2006

    基于移动最小二乘的图像变形 原文地址:http://blog.csdn.net/hjimce/article/details/46550001 作者:hjimce 一.背景意义 写这篇博文是应为目前为 ...

  3. fpga图像处理(基于sd卡图像读取和显示)

    [声明:版权所有,欢迎转载,请勿用于商业用途. 联系信箱:feixiaoxing @163.com] 除了cmos摄像头之外,还有一种图像读取和显示的办法,那就是基于sd卡的图像处理方法.相比较需要单 ...

  4. 图像处理:基于cv2.inpaint()图像修补

    前言 今天我们将学习如何通过一种"修复"的方法消除旧照片中的小噪音,笔画等.当然,经过我的测试你也可以将其用于削弱混杂了其他的颜色的图像. 实验背景 大多数人家都会有一些旧的的旧化 ...

  5. 图像处理(九)人物肖像风格转换-Siggraph 2014

    人物肖像风格转换 原文地址:http://blog.csdn.net/hjimce/article/details/45534333 作者:hjimce 一.前言 对于风格转换,2014年siggra ...

  6. 《OpenCv视觉之眼》Python图像处理十二 :Opencv图像轮廓提取之基于一阶导数的Roberts算法、Prewitt算法及Sobel算法

    本专栏主要介绍如果通过OpenCv-Python进行图像处理,通过原理理解OpenCv-Python的函数处理原型,在具体情况中,针对不同的图像进行不同等级的.不同方法的处理,以达到对图像进行去噪.锐 ...

  7. 图像处理(十三)保刚性图像变形算法-Siggraph 2004

    图像变形可以说是很多图像.动画领域的一个非常常见的功能,就说ps.天天P图.美图秀秀.可牛等这些每个软件,有好多个功能都要用到图像变形,比如图像方向校正.图像全景.视频防抖等,在我的另外一篇博文全景矩 ...

  8. [Python图像处理] 十四.基于OpenCV和像素处理的图像灰度化处理

    该系列文章是讲解Python OpenCV图像处理知识,前期主要讲解图像入门.OpenCV基础用法,中期讲解图像处理的各种算法,包括图像锐化算子.图像增强技术.图像分割等,后期结合深度学习研究图像识别 ...

  9. 图像处理(一)图像变形(1)矩形全景图像还原-Siggraph 2014

    最近发现,看过的文章,没几天就忘了,于是开始写点东西记录一下,所学习过的算法.废话不多说,今天看了这篇文献"Rectangling Panoramic Images via Warping& ...

最新文章

  1. python cookbook pdf下载-Python Cookbook 第3版 中文版.pdf
  2. C#正则表达式提取HTML中IMG标签的SRC地址(转)
  3. boost::function模块function_typeof的测试程序
  4. 打破冷漠僵局文章_研究僵局–第3部分
  5. HDU 3530Subsequence(单调队列)
  6. 计算机应用基本技能题库,计算机应用基本技能技能考试题库.pdf
  7. 查看mysql单个表大小限制_查看单个mysql数据库中各个表的大小
  8. java超市管理系统ppt_基于java-web的超市管理系统毕业答辩ppt课件
  9. 2021微信网页跳转APP
  10. 风变python多少钱_请问风变编程Python值得购买吗?
  11. Pandas数据分析第2部分
  12. C++实验02(02)华氏温度转换为摄氏温度
  13. Asp .NetCore 支付宝网页授权登录
  14. 玩平衡车系列——编码器使用教程与测速原理
  15. SharePoint Project导入(mpp文件导入)
  16. NSIS对卸载程序的签名
  17. CARLA 笔记(02)— Ubuntu 安装 CARLA(服务端、客户端、安装 miniconda、创建虚拟环境、更换 pip 源、生成交通流、人工控制车辆按键)
  18. 基于webassembly的前端视频编辑器(未写完)
  19. (要更新)SRAM、DRAM、SDRAM、DDR异同
  20. 趣链科技李伟:我们高估了区块链五年的价值,也低估了它未来二十年的影响力...

热门文章

  1. 如何在时间紧迫情况下进行机器学习:构建标记的新闻 数据 库 开发 标记 网站 阅读1629 原文:How we built Tagger News: machine learning on a
  2. 【caffe-Windows】mnist实例编译之model的使用-classification
  3. 不热衷黄金、地产的美国人喜欢投资什么?
  4. Redis进阶-细说分布式锁
  5. IDEA-使用IDEA创建maven多模块父子工程
  6. Spring-AOP 自动创建代理之AnnotationAwareAspectJAutoProxyCreator
  7. Shell-实际业务操作02
  8. RocketMQ:Producer启动流程与消息发送源码分析
  9. datagrip mysql乱码_DataGrip 2019.1.2 x64 连接MySQL出错解决
  10. Java中ArrayList最大容量为什么是Integer.MAX_VALUE-8?