相位 unwrap 与 wrap 算法详解(附代码)

最近接手了一个项目,光通信方面的,我负责编写初测结果的数据处理算法,其中有一个算法,叫做 unwrap 与 wrap,之前没有听说过。通过询问同事与搜索引擎,原来,所谓的 unwrap 是将在 +180 度到 -180 度之间跳变的相位信号,也就是函数值,变为不跳变的形态。而 wrap 则是其逆运算,即将信号恢复成跳变状态。
这样概括实在太模糊,我们用数据与图象来详细说明。

基本概念与数学关系

假设有一组相位数据(在后续说明中,为方便起见,我们只粗略地用整数部分来进行说明,比如第一个值我们就当作 -59 度):

-59.634834
-92.078697
-126.824913
-158.910297
166.959301
133.708772
96.759715
61.819861
26.786247
-7.944068
...(后面还有很多数据)
 

在这里插入图片描述


如图,这些数据呈现一种类似周期性的循环,当然这并不是真的周期函数,只是因为当它的数值跨越正负 180 度时,就会根据其变化趋势,跳变到边界重新计算。这种状态我们称为相位的卷绕/折叠(网上说法不一),用 wrap 这个单词表示,所以相位的解卷绕/展开被称为 unwrap。

具体到这个示例数据,我们可以得到以下信息:

  • 数据本身是递减趋势,从 -59 度不断递减,如 -59、-92、-126、-158。
  • 数据中有一个跳变点,即 -158 度到 166 度,也就是图像中第一个正的峰值,跳变后继续下降,直到下次跳变。

unwrap 要做的事情,就是消除这个跳变,让 wrap 函数中 166 度这个点,恢复为 -158 - n 度(n 是正值,为两者之间的间隔)。那么 n 是多少呢,其实很简单,-158 度距离 -180 度为 22 度,达到 -180 度后溢出,起点跳变为 180 度,溢出的值从 180 度继续计算,直到 166 度,距离为 14 度,所以合起来,n 为 22 + 14 = 36 度。

根据这样的规则,wrap 中 166 度这个点,对应的其实是 unwrap 下的 -158 - 36 = -194 度。同时你会发现,166 + 194 恰好是 360 度,而从 -180 跳到 180,也是将函数值增加了 360 度。所以 wrap 的本质,就是当相位超出边界时,补偿 360 度并继续计算,而 unwrap 就是去除这个补偿。所以,wrap 状态下的 166 度对应 unwrap 状态下的 -194 度,之后的 133 度对应 133 - 360 = -227 度,后面每个 wrap 值也都减去 360 度,直到遇见下一个跳变点。

显而易见,第二个 -180 度跳变点,对应的 unwrap 度数为 -180 - 360 = -540 度,其后的值由于跳变,将再次被补偿 360 度,合起来就是 720 度。所以,第二次跳变后的 166 度值,对应的 unwrap 值其实是 -554 度。如此循环,每跳变一次,就要让 wrap 值在之前的基础上,再减去一次 360 度,才能变为正确的 unwrap 值。

由于这里的例子是递减的,wrap 从 -180 跳变到 +180,增加了 360 度,因此 unwrap 计算时需要反向减去 360 度。若是递增函数,那么每次 wrap 自然是从 +180 跳变到 -180,减少 360 度,那么每次 unwrap 就需要重新加上 360 度。你可以将示例数据全部取反,就可以很直观的理解。

因此 unwrap 与 wrap 之间的关系,可以表示为:
𝑢𝑛𝑤𝑟𝑎𝑝(𝑥)=𝑤𝑟𝑎𝑝(𝑥)−360∗𝑤𝑟𝑎𝑝𝑁𝑢𝑚(𝑥)unwrap(x)=wrap(x)−360∗wrapNum(x)

𝑢𝑛𝑤𝑟𝑎𝑝(𝑥)=𝑤𝑟𝑎𝑝(𝑥)+360∗𝑤𝑟𝑎𝑝𝑁𝑢𝑚(𝑥)unwrap(x)=wrap(x)+360∗wrapNum(x)
分别对应递减与递增的情况。其中 unwrap(x) 和 wrap(x) 表示两种状态下的函数值,wrapNum 表示跳变次数。

在这里我们将 +360 与 -360 统一使用变量 wrapCycle 来代替,即递减时,wrapCycle 为 +360,递增时为 -360。上面的公式就可以统一起来:
𝑢𝑛𝑤𝑟𝑎𝑝(𝑥)=𝑤𝑟𝑎𝑝(𝑥)−𝑤𝑟𝑎𝑝𝐶𝑦𝑐𝑙𝑒∗𝑤𝑟𝑎𝑝𝑁𝑢𝑚(𝑥)unwrap(x)=wrap(x)−wrapCycle∗wrapNum(x)
unwrap(x) 与 wrap(x) 的图象如下:

在这里插入图片描述

在这里插入图片描述

计算方法

如公式𝑢𝑛𝑤𝑟𝑎𝑝(𝑥)=𝑤𝑟𝑎𝑝(𝑥)−𝑤𝑟𝑎𝑝𝐶𝑦𝑐𝑙𝑒∗𝑤𝑟𝑎𝑝𝑁𝑢𝑚(𝑥)unwrap(x)=wrap(x)−wrapCycle∗wrapNum(x)所示,要使函数在 wrap 与 unwrap 之间转换,重点在于确定 wrapCycle 与 wrapNum。

wrapCycle 代表了相位函数是递增还是递减,大多数应用场景下,相位都是单调变化的,因此比较好确定,可以直接比对两个相邻 wrap 值的大小关系。若 wrap(1) > wrap(2),则递减,wrapCycle = 360;若 wrap(1) < wrap(2),递增,wrapCycle = -360;若两者相同,则可以换下一个值进行比对,一般来讲这种情况极少。

但这里有一个 bug,就是必须保证比较的两个值,不处于跳变点两端。也就是说,如果比对 -59 和 -92,那自然是正确的,但如果比对的值,类似于 -158 和 166,从而得到了递增的结论,那显然是错误的。因此这里就牵扯到了如何确定跳变点的问题。

同样的,要得到 wrapNum,也需要确认跳变点的个数。因此,核心问题就是如何确认跳变点。

1. wrap 函数的跳变点确认

在这里插入图片描述


如图,确定两个 wrap 值之间是否发生了跳变,是一个比较直观的问题。 一般的策略是,如果相邻两个值的差距大于 180 度,则说明发生了一次跳变。

但这里会产生一个“歧义”问题,也就是说,对于一个完全未知的 wrap 状态下的数据,我们无法确定相邻两个值之间的差距,到底是因为跳变产生的,还是它本身的变化就是如此。再具体点,也就是说,当你看到相邻两个点的 wrap 值突然变化了 180 度以上,就认为它发生了跳变,其实并不算充要条件,只有加上“相邻两点之间的实际相位变化不会超过 180 度”这样的条件,才能认为这两个 wrap 值的差距是跳变产生的,而不是它的相位值本来就这样变化。

另一个是跳变丢失的问题,也就是指,采集原始数据时,由于间隔太大,导致丢失了一些可以被判断为跳变的数据(差距超出 180 度的点没采集到),这样也无法判断两个值之间是否发生过跳变。解决方案就是,增加数据密集程度,保证两点之间至多只有一个跳变点。

因此,如果要准确的将采集到的数据从 wrap 变为 unwrap,需要你对数据的性质有足够的了解,并且采集点的密集程度要合适。

2. unwrap 函数的跳变点确认

在这里插入图片描述


unwrap 函数还原成 wrap 函数相对来讲没有那么多坑。由于 wrap 转 unwrap 的机制,对递增函数而言,其最小值不会小于 -180 度;对递减函数而言,其最大值不会超过 180 度。因此对于不同的情况,以 -180 度或 +180 度为起点,360 度为周期,每经过一个周期,就代表发生了一次跳变,使 wrapNum 加 1,然后乘以 wrapCycle 即可得出需要补偿的角度。

例如,一个递减 unwrap 函数中,如何计算 -600 度对应的 wrap 值?很简单,若设定 180 ~ -180 为第 0 个周期,则第 1 个周期为 -180 ~ -540,第 2 个周期为 -540 ~ -900,-600 处于该周期,wrapNum = 2,wrapCycle = 360,所以相应的 wrap 值为 -600 + 2 * 360 = 120。

编程示例

在这里使用 C 语言做一个示例,只考虑常规情况,即单调且不会出现歧义问题。

/*
    描述:对函数进行解卷绕/卷绕
    参数:
    input:输入函数值
    points:需要进行运算的点数,最大为 input 的数组长度
    toggle:切换选项。输入 1 进行解卷绕 unwrap,输入 2 进行卷绕 wrap
*/
double* wrapToggle(double input[], int points, int toggle) {    
    if (points <= 1 || (toggle != 1 && toggle != 2)) {
        return input;
    }
    int PI = 180,
        cycleNum = 0,
        cycle = 0;

    double* result = (double*)malloc(sizeof(double) * points);

    // 根据曲线的趋势,判断补偿周期的正负
    if (input[0] > input[1]) {
        cycle = 2 * PI;
    }
    else {
        cycle = -2 * PI;
    }

    // 第一个值不变
    result[0] = input[0];

    // 判断是 unwrap 还是 wrap 运算
    // unwrap 解卷绕
    if (toggle == 1) {

        // 若第 2 个点就跳变,说明之前判断的 cycle 是反的。
        if (fabs(input[0] - input[1]) > PI) {
            cycle = -cycle;
        }

        for (int i = 1; i < points; i++) {
            // 相邻点差距大于 180 度,说明发生了跳变,补偿周期 +1
            if (fabs(input[i - 1] - input[i])>PI) {
                cycleNum++;
            }
            // 每个值都补偿一次周期
            result[i] = input[i] - double(cycle) * cycleNum;
        }
    }
    // wrap 卷绕
    else if (toggle == 2) {
        for (int i = 0; i < points; i++) {
            // unwrap 函数绝对值大于 180 的才进行计算
            if (fabs(input[i]) > PI) {
                cycleNum = abs(int((input[i] + cycle / 2) / 360)) + 1;
                result[i] = input[i] + double(cycle) * cycleNum;
            }
            else {
                result[i] = input[i];
            }
        }
    }
    return result;
}

相位 unwrap 与 wrap 算法详解(附代码) - 个人文章 - SegmentFault 思否

### 回答1: 相位解包裹算法是一种信号处理算法,可以在图像和信号处理中应用,主要目的是消除相位混叠和增加相位精度。在Matlab中,可以使用以下代码实现相位解包裹算法: function [unwrapped_phase] = phase_unwrap(phase) [Nx,Ny,Nz] = size(phase); unwrapped_phase = zeros(Nx,Ny,Nz); for i = 1:Nz unwrapped_phase(:,:,i) = unwrap_phase_2D(phase(:,:,i)); end end function [unwrapped_phase] = unwrap_phase_2D(phase) [Nx, Ny] = size(phase); unwrapped_phase = zeros(Nx,Ny); %obtain difference between adjacent pixels (central difference) in x and y dx = diff(phase,1,1); dy = diff(phase,1,2); %for first row/column, use forward difference dx = vertcat(phase(1,:) - phase(2,:), dx); dy = horzcat(phase(:,1) - phase(:,2), dy); %Calculate the interger number of 2*pi jumps along each dimension dx_int = round(dx./(2*pi)); dy_int = round(dy./(2*pi)); %create matrices to add/remove the 2*pi integer jumps dx_mat = vertcat(cumsum(dx_int), dx_int(end,:)); dy_mat = horzcat(cumsum(dy_int,2), dy_int(:,end)); %combine jumps in both dimensions total_jumps = dx_mat + dy_mat; %unwrap the phase by adding the jumps to the original phase unwrapped_phase = phase + total_jumps*2*pi; end 该函数接受一个相位图像作为输入,并返回解包裹后的相位图像。该算法可以分为两个函数。 第一个函数“phase_unwrap”用于处理三维相位图像。它在循环中使用第二个函数“unwrap_phase_2D”进行对每个二维平面进行相位解包裹。 第二个函数“unwrap_phase_2D”将二维相位图像作为输入,并返回解包裹后的相位图像。首先,使用中心差分法计算相邻像素之间的相位差异。然后,计算每个维度上的整数数量2 x pi跳转,并创建矩阵来添加或删除这些跳转。最后,将所有跳转添加到原始相位图像中,以进行解包裹。 因此,通过使用这个Matlab代码,我们可以快速有效地解包裹相位图像,提高相位测量的准确性。 ### 回答2: 相位解包裹算法是数字信号处理中的一种常用算法,用于解决相位跳跃的问题。Matlab是常用的数学软件之一,因此相位解包裹算法也可以用Matlab实现。 具体实现思路如下: 1. 读取原始相位数据,并将其转化为0到2pi的范围内,以便方便后续计算。 2. 对于相位数据序列中相差2pi以上的部分,进行相位解包裹处理。这里可以采用线性插值或者递推法。 3. 最后将解包裹后的相位数据进行反变换,得到相位跳跃后的信号。 以下是相位解包裹算法的Matlab代码示例: % 读取原始相位数据 phase_data = load('phase_data.txt'); % 将相位转换为0到2pi范围内 phase_data = mod(phase_data, 2*pi); % 定义解包裹后的相位数据 unwrapped_phase_data = zeros(size(phase_data)); % 进行相位解包裹 for i = 2:length(phase_data) if phase_data(i) - phase_data(i-1) > pi unwrapped_phase_data(i) = unwrapped_phase_data(i-1) - 2*pi; elseif phase_data(i) - phase_data(i-1) < -pi unwrapped_phase_data(i) = unwrapped_phase_data(i-1) + 2*pi; else unwrapped_phase_data(i) = unwrapped_phase_data(i-1); end end % 将解包裹后的相位数据进行反变换 unwrapped_signal = cos(unwrapped_phase_data) + sin(unwrapped_phase_data)*1j; 以上代码演示了如何使用Matlab实现相位解包裹算法。具体实现细节还需要根据实际数据的特点进行适当调整,以达到更好的效果。 ### 回答3: 相位解包裹算法(matlab代码实现)用于消除相位在2π范围内的“跳变”,使得相位变化连续、平滑,避免计算误差。 算法步骤: 1. 将相位从2π范围内调整到-pi到pi范围 2. 计算相邻两点相位差,若差大于pi,则减去2pi,使相位变化小于pi 3. 每次相位变化超过pi时,当前相位值就加上或减去2pi,以跳过2π范围内的不稳定区域。 下面是基于matlab的算法代码实现: function [ph_unwrap] = unwrap(phase) % phase:相位数据 % ph_unwrap: 相位保护算法输出的解包裹相位 % 将相位移到-pi到pi范围内 phase_adj = phase - round(phase/(2*pi))*2*pi; % 相邻相位误差调整到-pi到pi之间 delta_phase = diff(phase_adj); delta_phase = [delta_phase; delta_phase(end)]; % 保持向量大小一致 delta_phase(delta_phase > pi) = delta_phase(delta_phase > pi) - 2*pi; delta_phase(delta_phase < -pi) = delta_phase(delta_phase < -pi) + 2*pi; % 解包裹相位 ph_unwrap = cumsum(delta_phase); end 该代码实现了相位解包裹过程,并将相位调整至-pi到pi范围内。虽然该算法可适用于许多应用场合,但依据需求和实际情况,有时需要对算法进行调整或改进,以满足实际应用需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值