快速快速双线性插值

图像缩放是最常用的图像操作,最近实现了一次双线性插值,适用于单通道8位灰度图像,可以同时处理缩小,放大。

加速手段有三个:

1:使用可分离方式,先后处理行和列

2:构建缓冲区,以避免对某些行重复插值。

3:使用整形运算,避免浮点运算。

performace:

测试图像: C:\Documents and Settings\All Users\Documents\My Pictures\示例图片\Water lilies.jpg

scale

_ieInterpImageBilinear8UC1(ms)

cvResize(ms)

ratio

0.3

346

390

89%

0.5

939

1047

90%

0.8

1671

1507

110%

1.2

2885

1943

148%

1.5

3951

2678

150%

2

6119

3642

170%

上表都是执行1000次的总时间,可以看出,在缩小时速度比opencv的cvResize相当,在放大时,不幸的比cvResize慢上50%。贴出代码求解。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
typedef struct _Image{
     int w;
     int h;
     int c;
     int step;
     int type;
     void * data;
} Image;
 
static void _ieInterpImageBilinear8UC1_RowFilter(unsigned char * src, long * dst, int len, int * leftIdx, int * rightIdx, long * weight, int shift)
{
     int i, j;
     for (i = 0, j = 1; i < len - 2; i+=2, j+=2){
         dst[i] = ((1<<shift) - weight[i])*src[leftIdx[i]] + weight[i]*src[rightIdx[i]];
         dst[j] = ((1<<shift) - weight[j])*src[leftIdx[j]] + weight[j]*src[rightIdx[j]];
 
     }
     for ( ; j < len; j++){
         dst[j] = ((1<<shift) - weight[j])*src[leftIdx[j]] + weight[j]*src[rightIdx[j]];
     }
}
 
static void _ieInterpImageBilinear8UC1(Image* src, Image* dst)
{
     int i, j;
     int sw, sh, sstep;
     int dw, dh, dstep;
     unsigned char *sdata, *ddata;
     float horScaleRatio, verScaleRatio;
     long *rowBuf1, *rowBuf2;
     long *upLinePtr, *downLinePtr, *tempPtr;
     long *horWeight;
     int *horLeftIdx, *horRightIdx;
     int preVerUpIdx, preVerDownIdx;
     int shift = 8;
 
     sw = src->w; sh = src->h; sstep = src->step; sdata = (unsigned char *)(src->data);
     dw = dst->w; dh = dst->h; dstep = dst->step; ddata = (unsigned char *)(dst->data);
 
     horScaleRatio = sw / ( float )(dw);
     verScaleRatio = sh / ( float )(dh);
 
     rowBuf1 = new long [dw];
     rowBuf2 = new long [dw];
     horWeight = new long [dw];
     horLeftIdx = new int [dw];
     horRightIdx = new int [dw];
 
     //col interpolation
     //计算目标图像像素横向的左右邻居序号,和权重。
     for (i = 0; i < dw; i++){
         float pos = (i + 0.5f) * horScaleRatio;
         horLeftIdx[i] = ( int )(IET_MAX(pos - 0.5f, 0));
         horRightIdx[i] = ( int )(IET_MIN(pos + 0.5f, sw-1));
         horWeight[i] = ( long ) ( fabs (pos - 0.5f - horLeftIdx[i]) * (1<<shift));
     }
 
     preVerUpIdx = -1;
     preVerDownIdx = -1;
     upLinePtr = rowBuf1;
     downLinePtr = rowBuf2;
     for (j = 0; j < dh; j++){       
         float pos = (j + 0.5f) * verScaleRatio;
         int verUpIdx = ( int )(IET_MAX(pos - 0.5f, 0));
         int verDownIdx = ( int )(IET_MIN(pos + 0.5f, sh-1));
         long verWeight = ( long ) ( fabs (pos - 0.5f - verUpIdx) * (1<<shift));
 
         if (verUpIdx == preVerUpIdx && verDownIdx == preVerDownIdx){
             ; //do nothing
         }
         else if (verUpIdx == preVerDownIdx){
             IET_SWAP(upLinePtr, downLinePtr, tempPtr);
             _ieInterpImageBilinear8UC1_RowFilter(sdata + sstep*verDownIdx, downLinePtr, dw, horLeftIdx, horRightIdx, horWeight, shift);
         } else {
             _ieInterpImageBilinear8UC1_RowFilter(sdata + sstep*verUpIdx,   upLinePtr, dw, horLeftIdx, horRightIdx, horWeight, shift);
             _ieInterpImageBilinear8UC1_RowFilter(sdata + sstep*verDownIdx, downLinePtr, dw, horLeftIdx, horRightIdx, horWeight, shift);
         }       
 
         for (i = 0; i < dw; i++){
             ddata[dstep*j + i] = (unsigned char ) ( (((1<<shift) - verWeight)*upLinePtr[i] + verWeight*downLinePtr[i]) >> (2*shift) );
         }
         preVerUpIdx = verUpIdx;
         preVerDownIdx = verDownIdx;
     }
     delete []rowBuf1;
     delete []rowBuf2;
     delete []horWeight;
     delete []horLeftIdx;
     delete []horRightIdx;
}

使用神奇的四次展开循环,速度提升15%+。
code如下。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
static void _ieInterpImageBilinear8UC1_Ver3_RowFilter(unsigned char * src, long * dst, int len, int * leftIdx, int * rightIdx, long * weight, int shift)
{
     int i;
     for (i = 0; i < len - 4; i+=4){
         *dst++ = ((1<<shift) - weight[i])*src[leftIdx[i]] + weight[i]*src[rightIdx[i]];
         *dst++ = ((1<<shift) - weight[i+1])*src[leftIdx[i+1]] + weight[i+1]*src[rightIdx[i+1]];
         *dst++ = ((1<<shift) - weight[i+2])*src[leftIdx[i+2]] + weight[i+2]*src[rightIdx[i+2]];
         *dst++ = ((1<<shift) - weight[i+3])*src[leftIdx[i+3]] + weight[i+3]*src[rightIdx[i+3]];
 
     }
     for ( ; i < len; ++i){
         *dst++ = ((1<<shift) - weight[i])*src[leftIdx[i]] + weight[i]*src[rightIdx[i]];
     }
}
 
static void _ieInterpImageBilinear8UC1_Ver3(Image* src, Image* dst)
{
     int i, j;
     int sw, sh, sstep;
     int dw, dh, dstep;
     unsigned char *sdata, *ddata;
     float horScaleRatio, verScaleRatio;
     long *rowBuf1, *rowBuf2;
     long *upLinePtr, *downLinePtr, *tempPtr;
     long *horWeight;
     int *horLeftIdx, *horRightIdx;
     int preVerUpIdx, preVerDownIdx;
     int shift = 8;
 
     sw = src->w; sh = src->h; sstep = src->step; sdata = (unsigned char *)(src->data);
     dw = dst->w; dh = dst->h; dstep = dst->step; ddata = (unsigned char *)(dst->data);
 
     horScaleRatio = sw / ( float )(dw);
     verScaleRatio = sh / ( float )(dh);
 
     rowBuf1 = new long [dw];
     rowBuf2 = new long [dw];
     horWeight = new long [dw];
     horLeftIdx = new int [dw];
     horRightIdx = new int [dw];
 
     //col interpolation
     //计算目标图像像素横向的左右邻居序号,和权重。
     for (i = 0; i < dw; i++){
         float pos = (i + 0.5f) * horScaleRatio;
         horLeftIdx[i] = ( int )(IET_MAX(pos - 0.5f, 0));
         horRightIdx[i] = ( int )(IET_MIN(pos + 0.5f, sw-1));
         horWeight[i] = ( long ) ( fabs (pos - 0.5f - horLeftIdx[i]) * (1<<shift));
     }
 
     preVerUpIdx = -1;
     preVerDownIdx = -1;
     upLinePtr = rowBuf1;
     downLinePtr = rowBuf2;
     for (j = 0; j < dh; j++){       
         float pos = (j + 0.5f) * verScaleRatio;
         int verUpIdx = ( int )(IET_MAX(pos - 0.5f, 0));
         int verDownIdx = ( int )(IET_MIN(pos + 0.5f, sh-1));
         long verWeight = ( long ) ( fabs (pos - 0.5f - verUpIdx) * (1<<shift));
 
         if (verUpIdx == preVerUpIdx && verDownIdx == preVerDownIdx){
             ; //do nothing
         }
         else if (verUpIdx == preVerDownIdx){
             IET_SWAP(upLinePtr, downLinePtr, tempPtr);
             _ieInterpImageBilinear8UC1_Ver3_RowFilter(sdata + sstep*verDownIdx, downLinePtr, dw, horLeftIdx, horRightIdx, horWeight, shift);
         } else {
             _ieInterpImageBilinear8UC1_Ver3_RowFilter(sdata + sstep*verUpIdx,   upLinePtr, dw, horLeftIdx, horRightIdx, horWeight, shift);
             _ieInterpImageBilinear8UC1_Ver3_RowFilter(sdata + sstep*verDownIdx, downLinePtr, dw, horLeftIdx, horRightIdx, horWeight, shift);
         }       
 
         unsigned char * _ptr = ddata + dstep*j;
         for (i = 0; i < dw-4; i+=4){
             *_ptr++ = (unsigned char ) ( (((1<<shift) - verWeight)*upLinePtr[i] + verWeight*downLinePtr[i]) >> (2*shift) );
             *_ptr++ = (unsigned char ) ( (((1<<shift) - verWeight)*upLinePtr[i+1] + verWeight*downLinePtr[i+1]) >> (2*shift) );
             *_ptr++ = (unsigned char ) ( (((1<<shift) - verWeight)*upLinePtr[i+2] + verWeight*downLinePtr[i+2]) >> (2*shift) );
             *_ptr++ = (unsigned char ) ( (((1<<shift) - verWeight)*upLinePtr[i+3] + verWeight*downLinePtr[i+3]) >> (2*shift) );
         }
         for (; i < dw; i++){
             *_ptr++ = (unsigned char ) ( (((1<<shift) - verWeight)*upLinePtr[i] + verWeight*downLinePtr[i]) >> (2*shift) );
         }
         preVerUpIdx
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
双线性插值是一种图像处理算法,用于在图像缩放或变形时增强视觉效果。在矩阵计算中,双线性插值可以通过以下步骤实现快速计算: 1. 假设有一张 m × n 的原始图像,需要将其缩放到 p × q 的目标大小。 2. 假设缩放比例为 rx = p/m 和 ry = q/n,即行和列分别缩放 rx 和 ry 倍。 3. 首先将原始图像按照行进行缩放,即按照 rx 的比例缩放每一行,得到一个 m×p 的中间图像。这可以通过将每一行看成一个向量,左乘一个 p×m 的行向量来实现,其中行向量的每一个元素都是 rx。 4. 然后按列进行缩放,即按照 ry 的比例缩放每一列,并对行缩放后的中间图像进行插值,得到最终的 p×q 的目标图像。这可以通过对中间图像的每一列看成一个向量,左乘一个 m×q 的列向量来实现,其中列向量的每一个元素都是 ry,并且采用双线性插值公式进行计算。 实际的计算可参考以下代码: def bilinear_interp(input, output_size): batch_size, channels, height, width = input.size() new_height, new_width = output_size # 计算行方向的插值 row_indices = torch.linspace(0, height - 1, new_height).unsqueeze(1).expand(new_height, new_width) floor_rows = torch.floor(row_indices) ceil_rows = torch.ceil(row_indices) row_weights = row_indices - floor_rows floor_rows = floor_rows.clamp(0, height - 1) ceil_rows = ceil_rows.clamp(0, height - 1) top_left = input.index_select(2, floor_rows.long()) top_right = input.index_select(2, ceil_rows.long()) # 计算列方向的插值 col_indices = torch.linspace(0, width - 1, new_width).unsqueeze(0).expand(new_height, new_width) floor_cols = torch.floor(col_indices) ceil_cols = torch.ceil(col_indices) col_weights = col_indices - floor_cols floor_cols = floor_cols.clamp(0, width - 1) ceil_cols = ceil_cols.clamp(0, width - 1) top_left = top_left.index_select(3, floor_cols.long()) top_right = top_right.index_select(3, ceil_cols.long()) # 进行双线性插值 top_weights = (1 - row_weights) * (1 - col_weights) top_left *= top_weights.unsqueeze(dim=1) bottom_weights = row_weights * (1 - col_weights) bottom_left = input.index_select(2, ceil_rows.long()) bottom_left *= bottom_weights.unsqueeze(dim=1) left_weights = (1 - row_weights) * col_weights top_right *= left_weights.unsqueeze(dim=1) right_weights = row_weights * col_weights bottom_right = input.index_select(2, ceil_rows.long()) bottom_right = bottom_right.index_select(3, ceil_cols.long()) bottom_right *= right_weights.unsqueeze(dim=1) output = top_left + bottom_left + top_right + bottom_right return output
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值