我与插值萍水相逢续(2): 插值用于数据缩放存在的问题及解决方法

来源:互联网 发布:帝国cms采集插件下载 编辑:程序博客网 时间:2024/05/21 00:47

    在上一篇博文的最后部分,讲到了文中的双线性插值用于数据缩放的算法存在缺陷,这一篇博文就好好地分析一下。

1. 缺陷存在于缩放后的矩阵中的位置映射回原矩阵进行双线性插值时

1.1. Matlab编程

    假如有一个矩阵P,维度是m*n,它要进行缩放的倍率是r,那么缩放以后的矩阵PI的维度是mi*ni,mi是m/r的向下取整数,ni是n/r的向下取整数。那么PI中的第一列的位置一开始是全零的,P(:,1)=0;我将这一列映射到原矩阵P中去,那么在原矩阵中的插值位置是(:, 1/r),这个列号1/r是根据r的大小来看的:如果是放大,r>1,那么插值位置的列号是小于1的,向下取整以后,matlab中矩阵索引号不允许小于1啊,那完了,那向上取整啊,向上取整以后,插值四个已知的点是有了,可是插值的位置还是在0和1之间,那么也把它移过去,这样以后,那么其他的看起来是没问题了,原来列索引在1和2之间的怎么办呢,好的我把所有的都往后移动一位,看起来好像行,那插值位置在最后一列和最后第二列之间的呢,完了,这就是问题所在(2/r是不是也会小于1呢,其他的呢,所以是取决于r的大小的)。那么如果缩小,这个1/r是大于1的,看起来好像可以啊,但是r小一点的话是不是大的有点过了,前面几列的数据都没用到,都挤在后面的列去了,但是后面的隔得也很远啊,也没有超出矩阵维度的问题,那缩小是没问题的。以上是关于列的,行也是一样啊。所以对于matlab这种1开始的矩阵索引,使用上一篇博文中的算法,会造成放大时放大之后的矩阵过度使用第一第二列和第一第二行的数据进行插值,而在缩小时缩小之后的矩阵会忽略前面几行和前面几列的数据。我在上一篇博文的算法中提到,对于超出矩阵维度的情况,可以对超出维度的插值使用离插值处最近的边界进行线性插值,虽然我没有在程序中用这样的方法,但是这样仍然是一种很粗糙的插值方式,超出维度的插值都是相等的了。(以下是针对程序,特殊地,当r是整数时,算法在运算时会超出维度,原因就是放大之后的矩阵在映射回原矩阵时,由于r是整数,行号列号在遍历到最后会正好等于最后一行或最后一列,程序中使用的插值会不适用,这种情况,插值点位置我把它移动到前一个单位去插值,当然也可以用提出的用边界去做线性插值,也应该这么做。)

    我用画图软件画一个图来看:


    那为什么之前做出来效果还不错呢,图片还蛮清晰的呢,那是因为图像的数据是很大的,边缘的信息质量下降,对图像很大的数据量来说,肉眼看不出来,而且也并不碍事。但是,缺陷就在于,一旦数据量不是很大,是个小样本,那么这个误差就大了。

1.2. 从0开始存储的语言的编程

    matlab矩阵是从1开始存,而c/c++/python都是从0开始存储的,那么,嘿嘿,好像就避免了这个问题。是这样吗?

    我们来看一下,从0开始存,缩放后的矩阵映射回原矩阵不会有1.1中说的那个问题,因为这次不会有超出矩阵维度这个问题了。乍一看好像没问题。

    假如是一个10*10的矩阵,缩小成5*5的矩阵,那么,原来的矩阵Data(0:9,0:9),缩小后的矩阵Data_ret(0:4,0:4),缩小后的矩阵前几行和前几列映射回原矩阵都没有问题,关注一下最后一行先,4/0.5=8,映射回去的列号是8,也就是说原矩阵第9列的数据没有用到。那么原矩阵第9行同理也是用不到了。乍一看是这样,但是如果看下面的示意图,把变换后的矩阵中位置都映射回去(也就是都把它画完),发现插值点的分配还是比较合理的。所以缩小,可以认为是没问题的。

    假如是一个5*5的矩阵,放大成10*10的矩阵,那么,原来的矩阵Data(0:4,0:4),放大后的矩阵Data_ret(0:9,0:9),放大后的矩阵的前几行和前几列也是没问题的,最后一列我们先来看一看先,9/2=4.5,向下取值是4,和8/2=4是重叠起来了, 是有问题的。那么行也是同理。

    画张图来看一看:


1.3. 缺陷总结

    总而言之,上一篇博文中的算法出现的问题,就是缩放以后的矩阵的位置映射到原矩阵中去的位置分配问题,分配不合理,看了好一些博文也都有这样的问题。

    对于放大,无论是matlab的1开始的矩阵存储还是c/c++/python的0开始的矩阵存储,都是有问题的,虽然只是边缘,但是当数据样本小的时候,误差会很明显。

    对于缩小,matlab应该是没有问题的,还记得上一篇博文比较了放大后数据matlab内部提供的imresize()和自己的self_imresize()的距离,我这里对缩小后数据进行距离比较时,发现缩小时虽然看上去差不多,但是两个函数得出的变换后的矩阵的维度却不相同,自己写的比官方的行数少1。所以自己写的和matlab内部提供的还是有区别,不过放大缩小后的矩阵维度总有不是整数,它有它的规则,我认为咱也有咱的规则,它爱向上取整也好,四舍五入也好(使用向下取整的放大的算法得到的输出矩阵维度和matlab提供的函数的输出矩阵维度是相同的),制定些特殊规则也好,这里咱就是按照自己的想法加上向下取整了。

2. 解决方法

    在这里我要给出我的解决方法,即合理地将变换后的矩阵中的位置映射到原矩阵中去。

    双线性插值缩放算法3:(上一篇博文给出了1、2)

-----------------------------------------------------------------------------------------------------------

    算法准备:1)输入: 原数据矩阵data(1:m,1:n),缩放倍率rate。

    2)输出:返回变换后矩阵data_ret。

    step1:构建变换后的矩阵data_ret(1:m/rate,1:n/rate)。

    step2:根据变换或的矩阵的维度,假设为data_ret(1:m_ret,1:n_ret),在原矩阵中按照固定的间距分配m_ret*n_ret个插值点,并且最边缘的插值点和边界的间距和相邻插值点间的距离等同。

    step3:对所有插值点进行双线性插值。将所有插值结果存入data_ret,data_ret即为所求。

-----------------------------------------------------------------------------------------------------------

    算法实现:

    和上一篇博文的案例相同,写一个m-file,取名self_imresize_adv.m,内容如下:

--------------------------------------------------------------------------------------------------------------------------

% 任务目标:缩放数据
function data_ret = self_imresize_adv(data,rate,method)
if strcmp(method,'bilinear'),
    % step1: 构建变换后矩阵 data_ret
    [lin_orig,col_orig] = size(data);
    lin_ret = floor(lin_orig*rate);
    col_ret = floor(col_orig*rate);
    data_ret = zeros(lin_ret,col_ret);
    % step2: 分配插值的位置
    dis_lin = (lin_orig - 1)/(lin_ret + 1); % 获得行间隔
    dis_col = (col_orig - 1)/(col_ret + 1); % 获得列间隔
    for i = 1:lin_ret,
        for j = 1:col_ret,
            % 得到插值的位置
            x = 1 + i*dis_lin;
            y = 1 + j*dis_col;
            % step3: 在分配好的插值位置进行插值
            i_orig = floor(x);
            j_orig = floor(y);
            u = x - i_orig;
            v = y - j_orig;
            data_ret(i,j) = (1-u)*(1-v)*data(i_orig,j_orig) +(1-u)*v*data(i_orig,j_orig+1)...
            + u*(1-v)*data(i_orig+1,j_orig) +u*v*data(i_orig+1,j_orig+1); 
        end
    end
else
    error('please use the specific method');
end
end

--------------------------------------------------------------------------------------------------------------------------

    运行代码如下:

-----------------------------------------------------------------------------------------------------------

im_gray = imread('logo.tif');
figure;imshow(im_gray);
im_gray_bigger = self_imresize_adv(im_gray,2,'bilinear');
figure;imshow(im_gray_bigger);
im_gray_smaller = self_imresize_adv(im_gray,0.5,'bilinear');
figure;imshow(im_gray_smaller);

-----------------------------------------------------------------------------------------------------------

    结果如下:


    好吧,在放大环节,使用自己这个算法得到的放大的矩阵和matlab内部提供的imresize()得到的图像,同样用双线性插值,距离20.6366。距离好像还没上一篇博文中的算法近,但我还是感觉比起我的上一篇博文有改进了。我喜欢这一篇的方法。至于缩小环节,一如既往的少一行数据,任性。感觉matlab开发大牛们的这个接口的开发思想不太好琢磨啊。等以后有空了再回过来看吧,一般说这种话就不会回过来看了。

    PS: 此博文允许任何人转载。





原创粉丝点击