Miguel Arevallilo Herra′ ez, David R. Burton, Michael J. Lalor, and Munther A. Gdeisat
据我们所知,我们描述了一种新的相位展开技术。已经提出了几种基于首先展开最可靠像素的算法。这些仅限于连续路径,并且在定义起始像素时会遇到困难。这里描述的技术使用不同类型的可靠性函数,并且不遵循连续路径来执行展开操作。对该技术进行了详细的解释,并举例说明。
在条纹图案分析中,通常情况下,通过涉及反正切函数的表达式来获得相位。这个数学函数返回在极限之间已知的值+?和 -?。因此,给出的结果是模2以及值接近2出现在相位分布中。展开是解决这些不连续性的过程,将结果转换为所需的连续相位函数。
? 展开问题是十多年来的一个主要研究主题。2确实存在任何不同的算法,但决不能保证得到正确的解决方案,而且执行时间往往很长。乍一看,相位展开似乎是一个相对简单的问题,但在处理真实图像时,必须设计程序来处理许多不同的问题。相位包络中的不连续性、局部区域的欠采样、信噪比的高局部变化以及掩蔽区域是展开器必须克服的一些问题.
过去已经提出了许多相位展开算法来提高展开算法的抗噪声性。这些算法可以分为以下几类:1.全局算法2.区域算法,以及3.路径跟随算法。
全局算法通过最小化一个全局函数来构建解包裹问题。这类算法虽然已知是鲁棒的,但计算上很密集。
区域算法将图像分割成若干区域,每个区域分别处理,然后将处理过的区域与相邻的区域结合起来形成更大的区域,直至整个图像被处理完成。区域算法可以进一步分为两个子类:基于瓦片的算法和基于区域的算法。这些算法在鲁棒性和计算量之间提供了一种折衷。
路径跟踪算法类可以细分为三组:路径依赖方法、残差补偿方法和质量引导路径方法。
在路径依赖方法中,最简单的是由 Schaefer 和 Oppenheim 提出的解包裹器。这组包括螺旋和多扫描方向方法。这些方法侦测图像中的不连续或突变位置,并使用这些信息来计算连续的相位偏移。这些算法通常用于平面偏移,由于固定的数据评估顺序,它们不能处理有噪声的图像。
残差补偿算法在图像中搜索残差,并在正残差和负残差之间生成切割。这些算法一般在计算上高效但不够鲁棒。
质量引导路径算法依赖于按质量排序的像素来解包裹,首先解包裹最高可靠性的像素,最后处理最低可靠性的像素,以防止错误传播。解包裹路径是基于像素的可靠性确定的。尽管有时候一些解包裹错误可能无法检测并且继续传播,这种方法通常在实践中是鲁棒的,并且计算上也相对高效。所提出的算法属于这一类别。
所提出的解包裹算法遵循离散路径,将在第二节中解释,而参考文献中的12到19的解包裹算法通常遵循连续路径。这些质量引导的解包裹算法和传统的方法相比,因为它们依赖于与积分器像素相邻的像素,所以能够更好地处理噪声图像。
在质量引导路径展开算法中,主要有两个问题:可靠性函数的选择和展开路径的设计。从所提出的算法的角度解释了这两个问题。
A.可靠性函数
质量引导的路径展开算法使用不同的标准来确定点的可靠性。该标准通常基于像素与其相邻像素之间的梯度或差异。那些具有最低模2π梯度相对于它们的邻居的点被认为是最好的点,并且因此,是模2π梯度最高的点。
在可靠性函数中使用梯度的绝对值有多个缺点。如果存在一个高的载波值(与条纹图案的调制相比),载波成为主要的调制组件。然而,一个低的载波值会增加或减少图像像素的梯度值,并产生一个不适当的可靠性测量。
二阶差分将提供一个度量,用于相位函数凹凸度的程度。通过使用二阶差分,可以更好地检测相位图中的潜在不一致性。
二阶差分的计算将在第2节中对于图1中的像素进行解释。为了计算图像中一个像素的二阶差分,需要其正交和对角邻居的值(在一个3x3窗口中)。
像素(i,j?1),(i-1,j),(i,j+1)(i,j?1) 而像素(i?1,j?1),(j+1,i+1),(i?1,j+1)被称为对角邻居。
B. 解包裹路径
边缘是两个水平或垂直相连的像素的交点。任何具有左侧、右侧、上方或下方邻近像素的像素都可以构建一个边缘。图2(a)展示了一个图像的一部分,图中显示了像素的可靠性。每两个正交邻近像素都可以产生一个边缘,如图所示。
图2。所提出算法的数值例子。(下页继续。)?
图2(b)中,边缘的可靠性被定义为该边缘所连接的两个像素的可靠性之和。边缘可以被归类为图中以绿色显示的水平边缘和以红色显示的垂直边缘。
解包裹路径不能相对于像素的可靠性来定义。相反,它是通过查看边缘的可靠性值来定义的。解包裹路径的定义相对简单:首先解包裹那些可靠性更高的边缘。边缘按可靠性值存储在数组中,并根据可靠性值进行排序。具有更高可靠性的边缘首先被处理。
图2(c)到2(j)演示了所提出算法的原理。假设我们想要使用所提出的算法解包裹图2(a)中显示的像素的可靠性值。可以构建的边缘及其可靠性值在图2(b)中显示。
最初所有像素都被视为不属于任何组。在图2(c)中,像素f和g首先相对于彼此进行解包裹,因为连接它们的边缘具有最高的可靠性值。这两个像素属于同一组,这通过对它们进行相同颜色着色来示意。然后进行像素a和b的解包裹操作,这两个像素构成了第二组。接着是像素i和j的解包裹操作,它们构成了第三组。
图2(c)和2(d)显示了连接像素a和e的边缘具有第四高的值,
两个构建边缘的像素应该相互解包裹。但像素a已经相对于像素b解包裹,两者都属于同一组。为了解包裹像素e,需要添加/减去的2π倍数是从像素e计算得到的,然后加到或从像素e减去。像素a、b、e现在被视为同一组,并且在图2(d)中用相同的颜色显示。
图2(e)和2(f)说明了两组像素相对于彼此的解包裹。第一组包含像素a、b、e;第二组包含像素i和j。两组通过可靠性值高于或等于其他未处理边缘的垂直边缘连接。这两组应该相互解包裹。第二组包含的像素较少,所以在第一组和第二组之间的差异的2π倍数是计算出来的,然后加到或从第二组的所有像素中减去。包含较少像素的那一组的像素现在被认为属于同一组,并且现在被视为同一组的一部分。图2(f)中以相同的颜色显示。提出的算法按图所示继续进行。解包裹路径是离散的,因此两个构建边缘的像素之间的解包裹是局部的。这与Quiroga等人的算法相比是一个主要区别,可以在图2(d)中看到,像素l和q是相互独立解包裹的,并且与其它已解包裹的像素(a、b、e、f、g、i和j)相断开。
在解包裹过程中,有三种可能的情况:
流程图描述了一个相位解包裹算法的步骤。这里是每个步骤的详细描述:
开始:启动流程。
计算可靠性值:对图像中除边界外的每个像素计算可靠性值。
构建边缘并计算可靠性值:构建图像中的垂直和水平边缘,并为每个边缘计算可靠性值。
排序边缘:按可靠性值对所有边缘(垂直和水平)进行排序,并将它们存储在名为SORTED的数组中。
设置像素组:将每个像素设置为不属于任何组。
检查构建边缘的像素:检查构成边缘的两个像素。
判断像素组归属:
检查未处理的边缘:检查SORTED数组中是否有未处理的边缘。
解包裹边界:如果没有未处理的边缘,对图像边界相对于图像其余部分进行解包裹。
停止:结束流程。
整个过程是一个迭代的解包裹算法,它根据构成边缘的像素的可靠性来决定解包裹的顺序,并根据这些边缘的可靠性值动态地将像素归类到不同的组中。这种方法旨在提高解包裹的准确性,尤其是在处理带有噪声的复杂图像时。
流程图描述了提出算法的操作过程,如图3所示。可靠性函数可能需要修改,低可靠性点可能会在最终结果中引起问题。可能更好的做法是忽略那些非常低可靠性值的交点,并在解包裹完成后对这些进行插值。
提出的算法已通过使用模拟图像进行了测试。所有显示包裹相位的图像在[?π,π]范围内被缩放到黑色和白色之间以供显示。解包裹的相位图像也被缩放到黑白之间以覆盖完整的动态范围。
图4(a)显示了一个非常高质量的包裹相位分布,其中没有噪声和物理不连续性。这幅图像已用于在理想条件下测试提出的算法。图4(b)显示了结果的解包裹相位。
图5(a)显示了一个模拟的包裹相位分布,在相位的主体中没有噪声,但中心有一个小区域包含随机噪声。这组数据的目的是测试解包裹器隔离这种噪声的能力,并防止它破坏好数据的解包裹。图5(b)显示了结果的解包裹相位图。
图6(a)展示了从真实条纹图案分析得到的包裹相位图。该包裹相位图包含由投影条纹的阴影造成的损坏区域。该包裹相位图已使用提出的算法进行了解包裹。图6(b)展示了解包裹后的相位图,表明该算法能够成功解包裹不可靠的区域。
图7(a)展示了一个嘈杂的包裹相位图,它被盐和胡椒噪声所损坏。该图像已使用提出的算法进行了解包裹,解包裹后的相位图显示在图7(b)中。图7(c)展示了解包裹后相位图的三维表示。包裹相位图如图7(a)所示已经被Schaefer和Oppenheim的解包裹算法解包裹,解包裹后的相位图显示在图7(d)中。
解包裹后相位图的动态范围可能比包裹相位图的动态范围大得多。展示解包裹相位图通过在黑色和白色之间缩放它并不总是表明解包裹过程正确进行。因此,遵循Ghiglia和Romero的方法,我们重新包裹解包裹后的相位图以允许与包裹图像直接比较。这种重新包裹在视觉上令人信服地表明解包裹是定性正确的。
图8展示了图7(b)所示解包裹相位图的重新包裹相位图。图7(a)和8展示了包裹和重新包裹相位图之间的良好一致性,证明了提出的算法成功地解包裹了包裹相位图。
提出的算法的执行时间因图像而异,并取决于正在分析的特定相位分布。测试图像的大小为512 x 512像素。提出的算法已在一个PC系统上执行。该PC包含双Pentium III处理器,运行速度为1GHz。这台PC的内存是512Mbyte Rambus RAM。执行时间平均在半秒的数量级。
?
一个快速、可靠的二维解包裹器已经被提出、描述和测试。该算法基于首先沿着非连续路径解包裹具有更高可靠性值的点,当存在不连续性或嘈杂区域时,能够产生一致的结果。
下面是自己编写的 matlab代码:,需要完整版本的可以在评论区留言,留下邮箱!
function unwrappedPhase = unwrapPhase(wrappedPhase)
% 获取图像尺寸
[rows, cols] = size(wrappedPhase);
% 初始化可靠性矩阵
reliability = calculateReliability(wrappedPhase);
% 构建边缘和计算边缘的可靠性
[h_edges, v_edges] = calculateEdges(reliability);
% 排序边缘
edges = sortEdges(h_edges, v_edges);
% 初始化组
pixelGroup = zeros(rows, cols);
% 解包裹过程
unwrappedPhase = wrappedPhase; % 初始化解包裹相位图
for i = 1:length(edges)
edge = edges(i);
[pixel1, pixel2] = getEdgePixels(edge, rows, cols);
% 根据边缘的像素确定解包裹策略
unwrappedPhase = unwrapPixels(unwrappedPhase, pixel1, pixel2, pixelGroup);
end
% 解包裹边界
unwrappedPhase = unwrapBorders(unwrappedPhase, pixelGroup);
end
%在相位解包裹中,一个常见的方法来计算像素点的可靠性是通过它们的一阶或二阶差分,
% 这反映了相位变化的平滑程度。以下是一个可能的实现,它使用了二阶差分来计算可靠性:
function reliability = calculateReliability(wrappedPhase)
[rows, cols] = size(wrappedPhase);
reliability = zeros(rows, cols);
% 计算相位的一阶差分
dPhi_dx = diff(wrappedPhase, 1, 2);
dPhi_dy = diff(wrappedPhase, 1, 1);
% 补齐维度以匹配原始图像尺寸
dPhi_dx = padarray(dPhi_dx, [0, 1], 'post');
dPhi_dy = padarray(dPhi_dy, [1, 0], 'post');
% 计算二阶差分
ddPhi_ddx = diff(dPhi_dx, 1, 2);
ddPhi_ddy = diff(dPhi_dy, 1, 1);
% 补齐维度以匹配原始图像尺寸
ddPhi_ddx = padarray(ddPhi_ddx, [0, 1], 'pre');
ddPhi_ddy = padarray(ddPhi_ddy, [1, 0], 'pre');
% 计算可靠性
for i = 2:(rows-1)
for j = 2:(cols-1)
% 可靠性是基于二阶差分的逆
localReliability = 1 / (1 + abs(ddPhi_ddx(i, j)) + abs(ddPhi_ddy(i, j)));
reliability(i, j) = localReliability;
end
end
% 处理边界情况,边界可靠性可设为内部的最小值或特定策略
reliability(1,:) = reliability(2,:);
reliability(end,:) = reliability(end-1,:);
reliability(:,1) = reliability(:,2);
reliability(:,end) = reliability(:,end-1);
end
%--为了计算图像中每个像素的垂直和水平边缘及其可靠性,你需要考虑每个像素及其直接相邻像素的可靠性。
%--边缘的可靠性通常是由该边缘两侧像素的可靠性决定的。以下是一种计算边缘可靠性的方法:
function [h_edges, v_edges] = calculateEdges(reliability)
% 获取可靠性矩阵的尺寸
[rows, cols] = size(reliability);
% 初始化边缘可靠性矩阵
h_edges = zeros(rows, cols-1);
v_edges = zeros(rows-1, cols);
% 计算水平边缘可靠性
for i = 1:rows
for j = 1:(cols-1)
h_edges(i, j) = min(reliability(i, j), reliability(i, j+1));
end
end
% 计算垂直边缘可靠性
for i = 1:(rows-1)
for j = 1:cols
v_edges(i, j) = min(reliability(i, j), reliability(i+1, j));
end
end
end
%边缘的可靠性值从两个矩阵(水平和垂直)转换成单个向量,
% 并将其与每个边缘的索引配对,然后根据可靠性值对它们进行排序来排序边缘
function sortedEdges = sortEdges(h_edges, v_edges)
% 将边缘矩阵转换为向量
h_edge_vector = h_edges(:);
v_edge_vector = v_edges(:);
% 创建索引向量,用于边缘排序后的映射
h_edge_indices = (1:length(h_edge_vector))';
v_edge_indices = (1:length(v_edge_vector))' + length(h_edge_vector); % 偏移,以确保索引不会重叠
% 合并边缘可靠性值和它们的索引
combined_edges = [h_edge_vector; v_edge_vector];
combined_indices = [h_edge_indices; v_edge_indices];
% 根据边缘的可靠性值对它们进行排序
[sortedValues, sortedIndices] = sort(combined_edges, 'descend');
% 创建一个结构数组来存储排序后的边缘和它们的原始索引
sortedEdges = struct('value', num2cell(sortedValues), 'index', num2cell(combined_indices(sortedIndices)));
% 如果你只需要边缘的索引,你可以只返回sortedIndices。
% 这样你就可以使用sortedIndices来引用原始的h_edges和v_edges矩阵。
end
%-----------------------------------------------------------
%-------------------function : getEdgePixels-------------------
% 根据边缘的索引得到构建这个边缘的两个像素,
% 我们需要知道这个边缘是水平的还是垂直的,以及它在原始矩阵中的位置。
function [pixel1, pixel2] = getEdgePixels(edge, rows, cols)
% edge 应该是一个结构体,包含了边缘的值和索引
edgeIndex = edge.index; % 这里我们取出结构体中的索引
% 确定边缘是水平的还是垂直的
totalHorizontalEdges = (rows * (cols - 1));
if edgeIndex <= totalHorizontalEdges
% 边缘是水平的
row = ceil(edgeIndex / (cols - 1));
col = mod(edgeIndex, cols - 1);
col = col + (col == 0) * (cols - 1); % 如果mod结果为0,则设置为cols-1
pixel1 = sub2ind([rows, cols], row, col);
pixel2 = sub2ind([rows, cols], row, col + 1);
else
% 边缘是垂直的
edgeIndex = edgeIndex - totalHorizontalEdges; % 调整索引值
row = mod(edgeIndex, rows - 1);
row = row + (row == 0) * (rows - 1); % 如果mod结果为0,则设置为rows-1
col = ceil(edgeIndex / (rows - 1));
pixel1 = sub2ind([rows, cols], row, col);
pixel2 = sub2ind([rows, cols], row + 1, col);
end
end
%相位解包裹逻辑涉及到几个步骤。关键在于,如果两个像素属于不同的组,那么我们需要添加或减去适当数量的2π,以确保相位连续性。
function unwrappedPhase = unwrapPixels(unwrappedPhase, pixel1, pixel2, pixelGroup)
% 确定像素是否已经属于某个组
group1 = pixelGroup(pixel1);
group2 = pixelGroup(pixel2);
% 如果两个像素已经在同一个组中,则不需要操作
if group1 == group2 && group1 ~= 0
return;
end
% 两个像素不在同一组,需要进行解包裹
% 这里我们假设pixelGroup的值为0表示未分组,非零表示已分组
% 检查两个像素是否未分组
if group1 == 0 && group2 == 0
% 选择一个新的组ID
newGroup = max(pixelGroup(:)) + 1;
pixelGroup([pixel1, pixel2]) = newGroup;
% 只有在两个像素之间有2π的跳变时才需要解包裹
if abs(unwrappedPhase(pixel1) - unwrappedPhase(pixel2)) > pi
% 解包裹操作
unwrappedPhase(pixel2) = unwrappedPhase(pixel2) + ...
2 * pi * round((unwrappedPhase(pixel1) - unwrappedPhase(pixel2)) / (2 * pi));
end
% 检查其中一个像素是否未分组
elseif group1 == 0 || group2 == 0
% 将未分组的像素分到已分组的像素的组
if group1 == 0
pixelGroup(pixel1) = group2;
else
pixelGroup(pixel2) = group1;
end
end
% 如果两个像素已经分组但不在同一个组,则需要合并组
% 这通常涉及到检查它们的相位差,并确保相位连续
if group1 ~= 0 && group2 ~= 0 && group1 ~= group2
% 合并组
pixelGroup(pixelGroup == group1) = group2;
% 解包裹操作
phaseDifference = unwrappedPhase(pixel1) - unwrappedPhase(pixel2);
if abs(phaseDifference) > pi
% 选择哪个像素需要添加或减去2π
unwrappedPhase(pixelGroup == group2) = unwrappedPhase(pixelGroup == group2) + ...
2 * pi * round(phaseDifference / (2 * pi));
end
end
% 返回更新后的解包裹相位图和像素组
end
%解包裹图像边界通常涉及到与图像中心区域的相位信息相一致的过程。,
% 在图像的边界可能没有足够的邻近像素来确定怎样的解包裹是正确的,
% 所以你通常需要依赖于相邻的已解包裹的区域。
function unwrappedPhase = unwrapBorders(unwrappedPhase, pixelGroup)
[rows, cols] = size(unwrappedPhase);
% 假设边界与最接近的内部相位对齐
for i = 1:rows
if pixelGroup(i, 1) == 0 % 检查左边界
unwrappedPhase(i, 1) = unwrapBorderPixel(unwrappedPhase, i, 2);
end
if pixelGroup(i, cols) == 0 % 检查右边界
unwrappedPhase(i, cols) = unwrapBorderPixel(unwrappedPhase, i, cols-1);
end
end
for j = 1:cols
if pixelGroup(1, j) == 0 % 检查上边界
unwrappedPhase(1, j) = unwrapBorderPixel(unwrappedPhase, 2, j);
end
if pixelGroup(rows, j) == 0 % 检查下边界
unwrappedPhase(rows, j) = unwrapBorderPixel(unwrappedPhase, rows-1, j);
end
end
end
function borderPixelValue = unwrapBorderPixel(unwrappedPhase, refRow, refCol)
% 根据参考相邻像素解包裹边界像素
refPixelValue = unwrappedPhase(refRow, refCol);
% 这里可以添加额外的逻辑来处理解包裹,比如处理2π的倍数
% 下面的代码简化了这一过程
borderPixelValue = refPixelValue;
end
?我们对比了一下枝切法和我上面写的解出来的效果:
原图:
枝切法:解包裹后的相位?
?通过上面质量图导向法解包裹得到相位
我上文用到的算法,写的代码解出来的??