图像处理编程中的mask_图像相关的编程注意事项

做图像处理,很多时候都在跟矩阵打交道,数据结构相对来说比较简单---二维、三维数组。矩阵相加,滤波,求导等基础操作都有opencv打头阵,写代码时最多关心一下内存问题。想要进一步优化速度,算法上的进步就比较难了,涉及证明求解,有无线性解,没有的话如何迭代求解最快······还可以用gpu、npu等来加速。

这么说来是感觉做图像增强业务跟其他编程业务所需要的算法知识分离得有点开,基本上没有数据增减、排序的需求,也就基本不需要链表,树,栈这些数据结构和递归的思想。图像增强的算法操作主要在于加减乘除,指数对数,加权求和,以及矩阵分析,信号处理相关的数学求解,更需要抽象的数学思维能力,通过画图(很多时候不好画,画了别人也看不懂),举例子(也要靠对方的想象能力)是很难来分析的。

但是吧,只知道图像相关的知识是肯定不够的,谁让实现还得靠编程呢?编程习惯也很重要啦。下面记录几个对于刚开始从图像处理算法研究转到算法开发的,我认为特别值得注意的地方。

图像的遍历

在图像处理中,遍历图像是最基础的操作,也是最重要的操作之一,它决定了算法效率的下限。用opencv做图像遍历的时候,一开始会用Mat自带的.at(i,j)或者ptr去访问元素,觉得这样用起来很简单,看起来也清楚。但是这样访问内存是很慢的。因为通常一张图像是很大的,比如800万像素的灰度图,那就是8000000/1024/1024=7.63MB, 使用at每次会对每个像素重新寻址,而如果利用指针,遍历时可以利用偏移地址,两者速度对于800万像素的图像可以相差十几倍。但是如果图像越小,两者的差距就越小,当图像只有10x10大小时,两者的速度是一样的。为什么有这样的变化呢?这就涉及到存储器的层次结构问题了,在《深入理解计算机系统》第6章有这样的描述:

如果你的程序需要的数据时存储在cpu寄存器中的,那么在指令的执行期间,在0个周期里就能访问到他们。如果是在高速缓存中的,需要4~75个周期。如果存储在主存中,需要上百个周期,如果储存在磁盘上,需要大约几千万个周期!

图像的数据通常占用较大的内存,这就需要图像处理工程师对于系统的内存有较好的理解,才能写出高效的代码。

而且,我在opencv4.3-contrib的alpha-matting算法中也见到了这样的写法(的确是惊讶的):

图像处理编程中的mask_图像相关的编程注意事项_第1张图片

不负责任地说,有时候感觉算法研究员们跟程序员是两个世界的......他们关注的点完全不一样。

图像的格式

读取图像,最经常的就是保存成unsigned char无符号8位:0-255取值范围。而在图像处理中,常常需要做一些加权。滤波器,每个像素和它周围的点按滤波器的权重相加;几张图之间的加权,W1*img1+W2*img2, 其中W是和img相同大小的mask。那么这些权重应该设置成什么格式好呢?最直接的想法,设置成总权重为1的浮点数,清晰明了,也不会溢出。最好不要这样做,因为这样直接就把8位的运算变成了32位浮点运算呀,计算成本和存储成本马上上去了。应该把权重设计成整型,比如总权重是100,最小精度是1,加权完了之后再除以100. 当然,这样就需要考虑中间结果的存储要用unsigned short:无符号16位。总权重要按溢出值来考虑。

为什么要说这个呢,还是因为,在学校搞算法经常会用matlab,对于数据格式完全不敏感,用c开发,直接从matlab生成一个卷积核或者是mask就拿过来用了。算法设计者不注意这个,后期维护的人员要花5倍的精力去重新理解和修改。

vector容器的使用

有时候要保存/传递一些中间图像,常常就用vector这个容器啦,可以直接用它的sort之类的功能。我曾经在遍历数组(图像)时,对于局部图像块操作,用了c++ STL的vector这个容器来保留中间变量进行复用,并对它进行增删操作,结果速度奇慢无比。搜了搜原因觉得是vector容器每次添加元素可能会重新开辟内存(当预留不够时)。后来就弃用了。再后来当我读了《STL源码剖析》,vector的本质是数组,想起我对vector的这一顿操作,应该是对头部元素的删除(O(n))大大拖累了效率, 而不是尾部元素的添加(O(1)),再回头改了代码验证了下,果然如此。这里想使用vector主要是想要长度可变,方便增删,但不了解vector的本质直接导致了更低的效率。vector在图像处理中非常常用,建议用的同时也要好好了解一下这个容器,避免踩坑。

总结一下,1. 用指针进行图像的遍历。2. 尽量保持图像的运算是整型运算。3. 了解STL容器,在算法实现中高效使用。

你可能感兴趣的:(图像处理编程中的mask)