文章目录
- 一、效果展示
- 二、前言介绍
- 三、软件使用说明
- 3.1 环境配置
- 3.2 文件结构
- 3.3 准备工作
- 四、快速开始
- 五、主要思路
- 算法思路
- 网格变形和实时操作思路
- 六、总结与反思
- 七、代码链接
- 八、其他完整项目
一、效果展示
校正比萨斜塔:
人脸变形:
图像拼接结果中,重叠区域的对齐warp细节修正:
二、前言介绍
这是一个基于Python+Tkinter+FFD(free-form deformations)的2D彩色图像实时网格自由变形软件,它可以将任意彩色RGB图像划分为若干网格,用户可以使用鼠标点击网格顶点,按住并拖拽移动,释放鼠标后,图像会根据网格的变形而实时变形。
网格的自由变形技术(FFD,mesh warp/grid warp)在图像配准领域广泛使用,包括图像的旋转校正、图像人脸变形(image morphing)等。而在图像拼接领域,网格变形也几乎涵盖了所有的传统方法,在图像翘曲步骤使用。所以,了解网格变形技术对于图像拼接技术的学习也是大有裨益。
做这个软件或者写这篇文章的动机:
- 目前没有公开类似的软件,也没有好的文章来讲网格变形。
- 有图像变形的需求。
- 看到文章Deep Rotation Correction Without Angle Prior里nie等人开发了这样的软件制作数据集(文章第四部分),手痒就做一个类似的,也算复现文章的一部分。
为了进一步提高数据集的质量,我们开发了一个基于网格的程序,对旋转结果进行手动微调。基于网格的程序来手动微调旋转结果。He等人的旋转方法[7]。它允许用户用鼠标拖动拖动网格顶点,以交互方式修改网格变形。
文章链接:【图像拼接】论文精读:Deep Rotation Correction without Angle Prior(DRC)
- 学习网格变形原理,对加深图像拼接中网格变形的原理有帮助。(虽然FFD和图像拼接中的图像warp不太一样)
- 网格变形简单,但是让图像跟随网格的变化而变化比较难。(怎么实现网格到图像的映射呢?我看这也是大多数人比较发愁的地方)
- 学习软件开发,练练tkinter
软件的基础功能:
- 2D图像实时网格变形,包括灰度图、彩色RGB图像
- 保存变形后的图像
软件的特色功能:
- 颜色复原:一般FFD都是变形灰度图,只有形状变化,没有颜色和亮度变化;而本软件可以在颜色和亮度变化后,通过算法将颜色和亮度恢复到和原输入图像一致。(前面效果展示中只有变形步骤,没有颜色恢复步骤,颜色复原的效果见第四部分【快速开始】中的第6步)。
- 支持任意大小的网格,并保证实时处理速度。网格越密,图像微调的越精细。
- 可以应用到各种需要图像变形的场景,比如上面展示的图像内容校正、捏脸、全景图/鱼眼图的扭曲消除、各种细节微调、甚至可以通过拉伸将全景图矩形化。
- 使用极简风格编写,只用了numpy,pillow,torch三个库,没有用opencv等打包后文件比较大的库。
三、软件使用说明
3.1 环境配置
下载源码后,用pycharm打开该项目,setting中设置好所需的python环境。建议新建一个虚拟环境进行配置。
安装依赖:torch可能比较慢,建议使用清华镜像
pip install -r requirements.txt 或
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple/
requirments.txt中的库以及库版本:
numpy用于存储网格结构和图像输入,PIL用于将图像显示在tkinter上,torch用于将网格变化映射到图像时计算损失,即将图像以张量的形式输入及操作。
3.2 文件结构
项目文件结构如下:
- main.py:主函数运行tkinter的GUI窗口
- mesh_warp.py:网格变形相关算法,包括B样条、插值算法等
- tk_utils.py:tkinter的相关操作,包括组件逻辑等
3.3 准备工作
在本地任意位置准备好待处理的图像,支持jpg和png格式。
四、快速开始
- 运行main.py打开软件界面:
- 输入网格大小,然后点击【确定网格大小】。以15*15为例:
- 点击菜单栏【文件】,然后选择自己要处理的图像(或快捷键A)
- 选择好的图像如下所示,如果窗口大小显示不全可以拖拽改变窗口大小:
- 鼠标点击网格顶点,拖拽移动,释放鼠标,图像产生对应的形变,颜色和亮度会发生变化
- 形变完成后,点击【恢复图像颜色】,会弹出提示框,等待较长时间后,会弹窗提示颜色恢复完毕。(注:颜色恢复的时长与你的形变量和图像质量有关,如果形变较多,图像质量较高,请耐心等待)
- 图像恢复原色后,可以点击【文件】,然后【保存图像】;或按快捷键S,可以将图像保存到本地的任意位置。
- 菜单栏的【帮助】中有详细的【使用说明】
- 菜单栏的【帮助】中的【关于】有软件的相关说明
五、主要思路
整体过程描述:将图像放入网格阵中,通过控制网格的变形来定义一个空间变换函数,使各点映射到变形后的空间中,与图像的插值算法结合,从而实现图像变形。
算法思路
空间变换函数使用三次B样条,具体原理见下面文献:
D. Rueckert, L. I. Sonoda, C. Hayes, D. L. G. Hill, M. O. Leach, and D. J. Hawkes, “Nonrigid
registration using free-form deformations: application to breast mr images,” IEEE Transactions
on Medical Imaging, vol. 18, no. 8, pp. 712–721, 1999.
三次B样条的基函数:
重点:将网格变形映射到图像上
一般的方法是利用网格的变换结果对图像中的像素点进行插值运算,场景的有双线性插值等,就是图像resize用的那个插值算法。但是,图像resize并不涉及图像的warp。换句话说,图像resize的变换是线性的,而图像warp的变化是非线性的。那么,如果我想让图像变形后的插值尽量与原变化一致,就需要对每个像素点计算变形的贡献,即添加权重。没有形变的像素位置的权重为0,有形变的像素通过核函数施加权重,最后得到图像每个像素的加权和。
然而,当图像很大,质量很高时,上述方法的计算复杂度将是非常高的,计算缓慢,无法实现我们想达到的实时效果。
于是,我们受到深度学习中损失函数的启发,将目标图求逆得到反向图,将反向图与原图之间的差别视为loss,然后最小化这个loss,最后再变换回来,则得到损失最小的形变图像。(是不是很像图像拼接中网格变形的那些能量函数,定义很多个能量项,然后最小二乘找最优解就是最优的warp。这里我们也是这种思想,只不过我们的损失是简单的二次平方差。)
为了计算上述损失,那么图像的输入numpy流转为tensor流,求解后转为PIL流,用于显示到界面中。
上述原理对应mesh_warp.py中的函数,大家可以根据代码理解上述原理。
numpy流:输入图像,输入网格
tensor流:网格和图像转为tensor输入
pillow流:tkinter的GUI界面显示
先迭代2次,实现实时形变,最后迭代40次,用于恢复图像原色。
注:不是只有训练模型才会用到torch,也会有只用到计算loss,只用反向传播的情况。
网格变形和实时操作思路
用tkinter的canvas显示网格和图像,网格由顶点和线组成。层级关系由上到下依次为:顶点、线、图像。
初始化网格后,要能得到网格顶点的相关信息,包括网格顶点坐标,网格顶点的序号;与网格顶点相交的线的信息,线的id等。用户用鼠标点击网格顶点后,要能知道用户点击的是哪个网格顶点,从而知道与其相交的线,进而移动它们。
初始化网格时,每个顶点对应右、下两条相邻的网格边。除了最后一列和最后一行,每个网格顶点及其右、下临边作为一个整体按先列后行初始化;最后一列是从上到下初始化,最后一行是从左到右初始化。
只移动与顶点相交的线:
上述思路见tk_utils.py,代码注释详细。
六、总结与反思
- 恢复原图颜色和亮度实际上还是有一些不同的,但是已经足够接近了。起码可以变形彩色的RGB图像了,能满足一部分需求。
- 实现过程中,一定要设计好变量流,比如网格mesh是如何变换的,怎么存储顶点和线的坐标,怎么得到它们的索引;图像从输入开始是如何变化的。本例中图像就有numpy,PIL,tensor三种形式。
- 可以用cuda加速颜色复原过程,感兴趣的朋友可以试试。
七、代码链接
项目源码链接:基于Python+Tkinter+FFD(free-form deformations)的2D彩色图像实时网格自由变形软件
八、其他完整项目
【完整项目】基于Python+Tkinter+OpenCV+Yolo+手写OCR的双模式答题卡识别软件的设计与实现