FPGA图像处理中常用的图像缩放算法,包括最近邻插值算法、双线性邻插值算法、双三次邻插值算法,以及双线性的MATLAB与FPGA实现。
双线性邻域插值算法
基础理论
核心思想: 标准的线性插值是在一维直线上,根据已知两点的坐标和值,来计算中间某个未知点的值。而双线性插值指的是做了两次线性插值,是在一个二维平面上的。而 双线性插值 (Bilinear Interpolation),顾名思义,就是做了两次线性插值,但它是在一个二维平面上。当我们要计算目标图像中某个点 (x, y) 的像素值时,我们先找到它对应在源图像中的位置 (src_x, src_y)。这个位置通常是浮点数,落在四个真实像素点之间。
算法步骤:
- 坐标映射:将目标图像的坐标
(dst_x, dst_y)映射回源图像的坐标(src_x, src_y)。 - 找到四个邻近点:根据
(src_x, src_y)找到它周围的四个像素点,即上图的Q11, Q12, Q21, Q22。(如上图) - 第一次线性插值:在 x 方向上,分别对上下两行进行线性插值,计算出两个临时点
R1和R2的值。 - 第二次线性插值:在 y 方向上,对
R1和R2进行线性插值,最终得到P点的像素值。
公式:
$$
f(P) = (1 - d_x)(1 - d_y) f(Q_{11}) + d_x(1 - d_y) f(Q_{21}) + (1 - d_x)d_y f(Q_{12}) + d_x d_y f(Q_{22})
$$
原理实例化:
![[ISP之常用图像缩放算法 2025-07-25 20.53.47.excalidraw]]
笔者理论改进
笔者最终目标是实现再FPGA可以高效运行的双线性插值,为此需要在一开始从算法层面去改进。出于习惯,个人觉得以下公式更清晰:
$$
I(i, j) = [1 - y(i)] \times \left{ [(1 - x(j)) \times T_A(r) + x(j) \times T_B(r)] \right} + y(i) \times \left{ [(1 - x(j)) \times T_C(r) + x(j) \times T_D(r)] \right}
$$
为了清晰地表达,约定以下符号:
- Ws,Hs:源图像的宽度和高度。
- Wd,Hd:目标图像的宽度和高度。
- (dstx,dsty):目标图像中的像素坐标。
- (srcx,srcy):通过映射在源图像中得到的浮点坐标。
- (x1,y1):包围 (srcx,srcy) 的四点矩阵的左上角整数坐标,即 x1=⌊srcx⌋,y1=⌊srcy⌋。
- dx,dy:浮点坐标的小数部分,即 dx=srcx−x1,dy=srcy−y1。
- N:定点运算的小数位数,这里 N=10。
- S:缩放因子 (Scale),S=2N=1024。
- Xfixed:表示变量 X 的定点整数形式。
- TA,TB,TC,TD:分别代表源图像中四个邻近像素 f(x1,y1), f(x1+1,y1), f(x1,y1+1), f(x1+1,y1+1) 的值。
- ≪N:代表逻辑左移N位(乘以 S=2N)。
- ≫N:代表算术右移N位(除以 S=2N 并向下取整)。
改进1:
小数在FPGA中是难以接受的,所以将1扩大1024倍,为此得到
$$
I(i, j) = [1024 - y(i)] \times \left{ [(1024 - x(j)) \times T_A(r) + x(j) \times T_B(r)] \right} + y(i) \times \left{ [(1024 - x(j)) \times T_C(r) + x(j) \times T_D(r)] \right}
$$
改进2:
上述公式存在6次乘法,而且在实际中位宽较大,比较消耗资源,为此采取差分方式来代替上述表达式,差分情况下只需要做三次乘法,对于1024的除法采用移位即可。
$$
I(i,j);=;
\bigl(1024-y(i)\bigr)\Bigl[T_A(r)+\bigl(T_B(r)-T_A(r)\bigr)\tfrac{x(j)}{1024}\Bigr]
;+;
y(i)\Bigl[T_C(r)+\bigl(T_D(r)-T_C(r)\bigr)\tfrac{x(j)}{1024}\Bigr]
$$
分步数学推导
前处理 / 地址生成单元
这部分的目标是根据目标像素坐标 (dst_x, dst_y),计算出插值所需的四个源像素位置和两个权重。
1. 坐标映射:
将目标坐标范围 [0, Wd-1] 映射到源坐标范围 [0, Ws-1]。
标准浮点公式:
$$
\begin{align*}
src_x &= dst_x \cdot \frac{W_s - 1}{W_d - 1} \
src_y &= dst_y \cdot \frac{H_s - 1}{H_d - 1}
\end{align*}
$$
定点实现公式:
$$
\begin{align*}
src_x_{fixed} &= \left\lfloor \frac{dst_x \cdot (W_s - 1) \cdot S}{W_d - 1} \right\rfloor \
src_y_{fixed} &= \left\lfloor \frac{dst_y \cdot (H_s - 1) \cdot S}{H_d - 1} \right\rfloor
\end{align*}
$$
2. 分解整数坐标与小数权重
从定点坐标 src_x_fixed 中分离出整数部分 x1 和归一化的小数权重 x_norm。
整数坐标 x1,y1 公式:
$$
\begin{align*}
x_1 &= \left\lfloor \frac{src_x_{fixed}}{S} \right\rfloor \quad \Leftrightarrow \quad x_1 = src_x_{fixed} \gg N \
y_1 &= \left\lfloor \frac{src_y_{fixed}}{S} \right\rfloor \quad \Leftrightarrow \quad y_1 = src_y_{fixed} \gg N
\end{align*}
$$
小数坐标x_norm ,y_norm公式:
$$
\begin{align*}
dx &= src_x - x_1 \implies dx_{fixed} = (src_x - x_1) \cdot S = src_x_{fixed} - x_1 \cdot S \
x_{norm} &= src_x_{fixed} \pmod{S} \quad \Leftrightarrow \quad x_{norm} = src_x_{fixed} ,&, (S - 1)
\end{align*}
$$
y_norm 同理
插值计算核心
省流:为什么这么做?因为这个版本逻辑对于fpga来说既能保持精度(与浮点存在±1误差),又符合计算逻辑
1 | // R1 = TA << 10 + x * (TB - TA) |
1. X方向的线性插值
计算中间点 R1 (在 y1 行) 和 R2 (在 y1+1 行) 的值。
标准浮点公式:
$$
\begin{align*}
R_1 &= (1 - dx) \cdot TA + dx \cdot TB \
R_2 &= (1 - dx) \cdot TC + dx \cdot TD
\end{align*}
$$
定点公式:
$$
R_{1_{fixed}} = (S - x_{norm}) \cdot TA + x_{norm} \cdot TB
$$
展开
$$
R_1_{fixed} = S \cdot TA - x_{norm} \cdot TA + x_{norm} \cdot TB = (TA \ll N) + x_{norm} \cdot (TB - TA)
$$
R_2fix同理
2. Y方向的线性插值
利用 R1 和 R2 计算最终像素值 I。
标准浮点公式:
$$
I = (1 - dy) \cdot R_1 + dy \cdot R_2
$$
定点实现公式:
$$
I_{final} = \left\lfloor \frac{(S - y_{norm}) \cdot R_{1_{fixed}} + y_{norm} \cdot R_{2_{fixed}}}{S^2} \right\rfloor
$$
拆分展开:
$$
\begin{align*}
I_{final} &= \left\lfloor \frac{S \cdot R_{1_{fixed}} + y_{norm} \cdot (R_{2_{fixed}} - R_{1_{fixed}})}{S^2} \right\rfloor \
I_{final} &= \left\lfloor \frac{R_{1_{fixed}}}{S} + \frac{y_{norm} \cdot (R_{2_{fixed}} - R_{1_{fixed}})}{S^2} \right\rfloor
\end{align*}
$$
3. 最终步骤
$$
I_{final} = (R_{1_{fixed}} \gg N) + \left( (y_{norm} \cdot (R_{2_{fixed}} - R_{1_{fixed}})) \gg 2N \right)
$$
python定点实现
code
1 | import cv2 |
result

优化
除法计算过于多,每个像素都进行一次除法,这是极其反优化的。为此优化只做一次,预先计算一个高精度的“组合缩放因子”,这个因子包含了原始的缩放比例和用于最终结果的SCALE。
1 | # -*- coding: utf-8 -*- |
FPGA实现
模块设计
![[ISP之常用图像缩放算法 2025-07-26 19.32.04.excalidraw|2000]]
BUG日志
“今天又在很努力的写bug了!”
在Verilog实现地址生成的过程中,难以避免对于除法精度的处理
如下公式,往往会出现小数,就算对其扩大,由于dst也是较大的数字,一旦误差被放大1000也是巨大的偏移,会越来越偏,花了大半天时间,很难在资源和效率之间平衡,每次都用除法资源量是巨大的,不可接受!!又想着去修正数值,但是这样又会写死,难以适配更多的变化。
最后,取消了-1 🤤,不可否认的是这样也存在难以适配的问题,但是这也许是当先最简单最省事最可靠的办法,对于其它分辨率变化,我认为更应该是在 localparam 部分就让他为整数。
$$
\begin{align*}
src_x &= dst_x \cdot \frac{W_s - 1}{W_d - 1} \
src_y &= dst_y \cdot \frac{H_s - 1}{H_d - 1}
\end{align*}
$$