使用C++和Qt的涂鸦程序(三)
使用C++和Qt的涂鸦程序(一)
使用C++和Qt的涂鸦程序(二)
源代码的github
https://github.com/drink-crow/graffiti
这一篇用来记录程序中用到的算法,实现以及修改的细节。
按照思路,图像相似度的计算是这个小程序里的核心之一。图像相似度的算法有很多,比如以颜色信息区分的颜色直方图,峰值信号和背景噪音比的PSNR,结构详细性的SSIM等都是非常常用的图像详细度的算法。最近还有许多机器学习识别的方法。
项目需求
虽然图像相似度的算法有很多,但其中大多数都不大适合这个项目,因为这些算法往往是为原图与计算完成的效果图考虑的。而程序开始涂鸦之时,涂鸦图像与原图的差距非常大,一些算法在这种情况下不是特别的可靠,比如用余弦相似度计算的方法。
计算的方法要同时考虑颜色信息和空间信息。目前主流的算法中为了计算简便或者其它原因大多都没有同时考虑到其中两种信息。比如颜色直方图只考虑了颜色信息而完全没有空间信息,显然不适合。SSIM的计算中也只考虑到对比度,而且计算时彩色图像会转成灰度图像,颜色失真会非常的大。
SSIM(结构相似性)
SSIM的计算方法网上有很多,这里不在赘述。简略的介绍可以查看下维基百科上
https://en.wikipedia.org/wiki/Structural_similarity
程序中关于SSIM的实现可以查看pictureCompare.h 中的函数
double getSSIM_CPU(vector<vector<int>> s1, vector<vector<int>> s2, int h, int w, int bit, double pixelRadius);
SSIM的实现在代码中虽然是最开始编写的部分,但是算法本身对颜色信息的支持不大好,就没有进行太多的优化。
在SSIM的实现中值得注意的是
//划分窗口的数量
int M = ceil((double)height/convolutionWindowSize);
int N = ceil((double)width / convolutionWindowSize);
根据给定的像素半径来对整个图像进行了划分。一个图像分为了MxN个格子。这种做法在许多介绍SSIM的地方也有提到。因为在一定距离下人眼观察画面,一次只能集中在一块地方。按照生物习惯去调节算法,得出来的效果更接近于直觉
而每个窗口大小是多少比较合适,目前没找到理论上的依据,经过多次的实验。这里建议是像素半径在15以上比较好,如果输入图像的分辨率比较大的话,可以设置得更大。
在程序中可以看到、
抛去颜色可以大致的看到轮廓是相似的,但是颜色确实完全不对的。
因为在SSIM的计算过程中输入图x 和y 的平均值 μ x \mu_{x} μx, μ y \mu_{y} μy和标准差 σ x \sigma_{x} σx, σ y \sigma_{y} σy以及它们之间的协方差 σ x y \sigma_{xy} σxy是根据输入图像灰度化后计算的,损失了非常多的颜色信息。
如果把涂鸦图转化成灰度图来看
以灰度信息来看的,还是比较符合。
上文以及提到,造成这种情况的主要原因还是SSIM的算法开发初衷是用来衡量压缩后的图片的质量的。所以对比的两个图片本身应该是非常相似的,至少色彩色调上是一致的,就以明暗度、对比度来代替本来的颜色信息,以制不适用于这个图像程序