当我们对一个图像分割为区域后,一般会对分割好的区域进行表示与描述,以便使“自然状态的”像素适合计算机处理。而对于区域有一个基本的划分,为:外部特征(区域的边界)
或内部特征(组成区域的像素)
两种方式来表示区域。然后下一个任务则是在选择表示的方案的基础上描述区域。比如用图像的边界来表示区域,而边界可以用边界长度、凹面形状数目、凸包等特征来描述。
不过无论选择什么特征,被选做描绘子的特征因该尽量对区域大小
、平移
、旋转
这些变化不敏感。这章描绘子满足一个或多个这样的属性。
区域是一个连接的分量,而区域的边界则是区域像素的集合,这些像素有一个或多个不存在区域内的相邻像素。不在边界或区域上的点用0来表示,称为背景点。最初使用的是二值图像,通常用1
表示边界点,0
表示背景。本章后面则会将其扩充到灰度级值或多谱值。
根据上面的定义,可以在得出区域的一些结论:边界是一组相连的点。若边界上的点形成一个顺时针或逆时针序列,则称边界上的点为有序的。若边界上的每个店恰好有两个值均为1的相邻像素点,而且这些像素点不是4邻接的,则称边界是最低限度连接的。内部点定义为区域内擦除边界外的任意位置上的点。
后面的内容会对不同类型的数据进行处理,所以会重新介绍一些基本概念与函数。
单元数组可以将各种类型的对象组合在同一个变量名下的方法。比如将512×512的uint8图像f
、188×2的二维坐标序列b
、包含两个字符名称的单元数组char_arry = {‘area’, ‘centroid’}
组合在同一个变量C中的语法为:
C = {f, b, char_array}
若要显示其元素内容,可以:C{3}
或celldisp(C{3})
。
若使用圆括号,则可以获得变量的描述:
f = zeros(512, 512);
b = ones(188, 2);
char_array = {
'area', 'cemtroid'};
C = {
f, b, char_array};
C{
3}
%%
ans =
1×2 cell 数组
{
'area'} {
'cemtroid'}
%%
celldisp(C{
3})
%%
ans{
1} =
area
ans{
2} =
cemtroid
%%
C(3)
%%
ans =
1×1 cell 数组
{
1×2 cell}
%%
结构就是C语言的结构体。结构元素是通过域的名称进行访问的。
假设定义函数:
function s = image_stats(f)
s.dim = size(f);
s.AI = mean2(f);
s.AIrows = mean(f, 2);
s.AIcols = mean(f, 1);
然后我们调用该函数并显示返回的值:
f = imread("./example.tif");
a = image_stats(f);
display(a);
%%
a =
包含以下字段的 struct:
dim: [486 486]
AI: 0.2805
AIrows: [486×1 double]
AIcols: [1×486 double]
%%
size(a)
%%
ans =
1 1
%%
可以看出结构本身是一个标量。而且使用结构并不会对代码造成更复杂的逻辑,相反可以让输出的数据组织更加清晰,所以这就是面向对象的好处吧。
当然结构也是可以构成数组,且被引用的。这里就不做演示。
函数B = boundaries(f, conn, dir)
可以跟踪f中的对象的外部边界,得到边界的序列。f
是默认的二值图像,背景像素为0. conn
是输出边界的期望连接方式;其值为4或8连接。参数dir是边界被跟踪的方向,其值为cw
和ccw
分别代表顺逆时针。这两个参数默认为:8
和cw
。
假设我们要找对象的唯一最长边界,那么可以:
f = imread('abcd.tif');
gray_f = mat2gray(f);
f = im2bw(gray_f, 0.6);
B = boundaries(f);
d = cellfun('length', B);
[max_d, k] = max(d);
v = B{
k(1)}
%%
v =
152 38
151 39
150 39
149 39
148 39
…………
152 38
%%
这样可以得到边界的坐标。
而B刚好包含了四个字母的边界:
%%
B =
4×1 cell 数组
{
124×2 double}
{
136×2 double}
{
115×2 double}
{
115×2 double}
%%
b8 = bpimd2eoght(b)
和b4 = bound2four(b)
输入的b是多行二列的矩阵,每行包含一个边界的像素坐标,且要求这些坐标是闭合的。两个函数会从b
中分别保留8连接和4连接的边界。
g = bound2im(b, M, N, x0, y0)
生成-幅二值图像g,该图像的大小为M ×N,边界点为1,背景值为0。参数x0和y0决定图像中b的最小x和y坐标位置。边界b必须是坐标的一个大小为np×2(或2×np)的数组,其中,像前面提到的那样,np是点的数目。若x0和y0被省略,则在M×N数组中边界会被近似中心化。若M和N被省略,则图像的水平和垂直尺度就等于边界b的长度和宽度。若函数boundaries发现多个边界,则可以使用函数bound2im,通过连接单元数组B的元素,获得对应的所有坐标:b = cat(1, B{ : })
比如这样:
f = imread('abcd.tif');
subplot(1,2,1);
imshow(f)
[m, n] = size(f);
gray_f = mat2gray(f);
f = im2bw(gray_f, 0.6);
B = boundaries(f);
b = cat(1, B{
:});
g = bound2im(b);
subplot(1,2,2);
imshow(g);
链码通过一个指定长度与方向的直线段的连接序列来表示一条边界。每条线段的方向通过编号方案加以编码,得到Freeman编码
。就是规定方向,方便搜索。
一条边界的链码取决于起点。然而,代码可以通过将起点处理为方向数的循环序列和重新定义起点的方法进行归一化,因此,产生的数字序列形成一个最小幅值的整数。可以通过使用链码的一阶差分来代替链码本身的方法对旋转进行归一化。这种差分可通过计算分隔链码的两个相邻像素的方向变化(图11.1中的逆时针方向)次数来获得。例如,4方向链码10103322的一阶差分为3133030。若我们将链码当做一个循环序列对待,则差分的第一元素可以通过使用链码的第一个和最后一个元素的转换加以计算。这里,结果是33133030。关于任意旋转角度的归一化,可以通过确定带有某些主要特性的边界获得。
用函数c = fchcode(b, conn, dir)
其中:
参数conn
是代码连接方式。dir
是方向,same
或reverse
是分别与b
同向与反向。默认为8连接同向。
例如:
f = imread('circular.tif');
B = boundaries(f);
d = cellfun('length', B);
[max_d, k] = max(d);
b = B{
1};
[m n] = size(f);
g = bound2im(b, m, n, min(b(:, 1)), min(b(:, 2)));
imshow(g2);
[s, su] = bsubsamp(b, 40);
g2 = bound2im(s, m, n, min(s(:, 1)), min(s(:, 2)));
cn = connectpoly(s(:, 1), s(:, 2));
g3 = bound2im(cn, m, n, min(cn(:, 1)), min(cn(:, 2)));
subplot(2,2,1);
imshow(f);
subplot(2,2,2);
imshow(g);
subplot(2,2,3);
imshow(g2);
subplot(2,2,4);
imshow(g3);
g为二值图像边界,g2为二次取样的边界,g3为连接g2的效果。
一条数字边界可由一个多边形以任意精度近似。对于一条闭合曲线,当多边形线段数目与边界点数目相同时,近似是精确的。因此,每一对相邻点定义了多边形的一条边。实践操作中,多边形近似的目的是用尽可能少的顶点表示边界的形状。
一种用于多边形近似的特殊方法是寻找一个区域或者一条边界的最小周长多边形(MPP)。
假设将图形看做“细胞联合体”即这个样子:(图中方块)
其最小周长多边形可以表示成(黑线部分):
进一步可以表示为:
有以下基础结论:
那么具体实现算法的过程可以按以下方式:
boundaries
以四联接顺时针方式获取上一步中的区域边界fchcode
获得该序列的Freeman链码
那么具体算法实现:
function [x, y] = minperpoly(B, cellsize)
%MINPERPOLY Computes the minimum perimeter polygon.
% [X, Y] = MINPERPOLY(F, CELLSIZE) computes the vertices in [X, Y]
% of the minimum perimeter polygon of a single binary region or
% boundary in image B. The procedure is based on Slansky's
% shrinking rubber band approach. Parameter CELLSIZE determines the
% size of the square cells that enclose the boundary of the region
% in B. CELLSIZE must be a nonzero integer greater than 1.
%
% The algorithm is applicable only to boundaries that are not
% self-intersecting and that do not have one-pixel-thick
% protrusions.
% Copyright 2002-2004 R. C. Gonzalez, R. E. Woods, & S. L. Eddins
% Digital Image Processing Using MATLAB, Prentice-Hall, 2004
% $Revision: 1.6 $ $Date: 2003/11/21 14:41:39 $
if cellsize <= 1
error('CELLSIZE must be an integer > 1.');
end
% Fill B in case the input was provided as a boundary. Later
% the boundary will be extracted with 4-connectivity, which
% is required by the algorithm. The use of bwperim assures
% that 4-connectivity is preserved at this point.
B = imfill(B, 'holes');
B = bwperim(B);
[M, N] = size(B);
% Increase image size so that the image is of size K-by-K
% with (a) K >= max(M,N) and (b) K/cellsize = a power of 2.
K = nextpow2(max(M, N)/cellsize);
K = (2^K)*cellsize;
% Increase image size to nearest integer power of 2, by
% appending zeros to the end of the image. This will allow
% quadtree decompositions as small as cells of size 2-by-2,
% which is the smallest allowed value of cellsize.
M = K - M;
N = K - N;
B = padarray(B, [M N], 'post'); % f is now of size K-by-K
% Quadtree decomposition.
Q = qtdecomp(B, 0, cellsize);
% Get all the subimages of size cellsize-by-cellsize.
[vals, r, c] = qtgetblk(B, Q, cellsize);
% Get all the subimages that contain at least one black
% pixel. These are the cells of the wall enclosing the boundary.
I = find(sum(sum(vals(:, :, :)) >= 1));
x = r(I);
y = c(I);
% [x', y'] is a length(I)-by-2 array. Each member of this array is
% the left, top corner of a black cell of size cellsize-by-cellsize.
% Fill the cells with black to form a closed border of black cells
% around interior points. These cells are the cellular complex.
for k = 1:length(I)
B(x(k):x(k) + cellsize-1, y(k):y(k) + cellsize-1) = 1;
end
BF = imfill(B, 'holes');
% Extract the points interior to the black border. This is the region
% of interest around which the MPP will be found.
B = BF & (~B);
% Extract the 4-connected boundary.
B = boundaries(B, 4, 'cw');
% Find the largest one in case of parasitic regions.
J = cellfun('length', B);
I = find(J == max(J));
B = B{
I(1)};
% Function boundaries outputs the last coordinate pair equal to the
% first. Delete it.
B = B(1:end-1,:);
% Obtain the xy coordinates of the boundary.
x = B(:, 1);
y = B(:, 2);
% Find the smallest x-coordinate and corresponding
% smallest y-coordinate.
cx = find(x == min(x));
cy = find(y == min(y(cx)));
% The cell with top leftmost corner at (x1, y1) below is the first
% point considered by the algorithm. The remaining points are
% visited in the clockwise direction starting at (x1, y1).
x1 = x(cx(1));
y1 = y(cy(1));
% Scroll data so that the first point is (x1, y1).
I = find(x == x1 & y == y1);
x = circshift(x, [-(I - 1), 0]);
y = circshift(y, [-(I - 1), 0]);
% The same shift applies to B.
B = circshift(B, [-(I - 1), 0]);
% Get the Freeman chain code. The first row of B is the required
% starting point. The first element of the code is the transition
% between the 1st and 2nd element of B, the second element of
% the code is the transition between the 2nd and 3rd elements of B,
% and so on. The last element of the code is the transition between
% the last and 1st elements of B. The elements of B form a cw
% sequence (see above), so we use 'same' for the direction in
% function fchcode.
code = fchcode(B, 4, 'same');
code = code.fcc;
% Follow the code sequence to extract the Black Dots, BD, (convex
% corners) and White Dots, WD, (concave corners). The transitions are
% as follows: 0-to-1=WD; 0-to-3=BD; 1-to-0=BD; 1-to-2=WD; 2-to-1=BD;
% 2-to-3=WD; 3-to-0=WD; 3-to-2=dot. The formula t = 2*first - second
% gives the following unique values for these transitions: -1, -3, 2,
% 0, 3, 1, 6, 4. These are applicable to travel in the cw direction.
% The WD's are displaced one-half a diagonal from the BD's to form
% the half-cell expansion required in the algorithm.
% Vertices will be computed as array "vertices" of dimension nv-by-3,
% where nv is the number of vertices. The first two elements of any
% row of array vertices are the (x,y) coordinates of the vertex
% corresponding to that row, and the third element is 1 if the
% vertex is convex (BD) or 2 if it is concave (WD). The first vertex
% is known to be convex, so it is black.
vertices = [x1, y1, 1];
n = 1;
k = 1;
for k = 2:length(code)
if code(k - 1) ~= code(k)
n = n + 1;
t = 2*code(k-1) - code(k); % t = value of formula.
if t == -3 | t == 2 | t == 3 | t == 4 % Convex: Black Dots.
vertices(n, 1:3) = [x(k), y(k), 1];
elseif t == -1 | t == 0 | t == 1 | t == 6 % Concave: White Dots.
if t == -1
vertices(n, 1:3) = [x(k) - cellsize, y(k) - cellsize,2];
elseif t==0
vertices(n, 1:3) = [x(k) + cellsize, y(k) - cellsize,2];
elseif t==1
vertices(n, 1:3) = [x(k) + cellsize, y(k) + cellsize,2];
else
vertices(n, 1:3) = [x(k) - cellsize, y(k) + cellsize,2];
end
else
% Nothing to do here.
end
end
end
% The rest of minperpoly.m processes the vertices to
% arrive at the MPP.
flag = 1;
while flag
% Determine which vertices lie on or inside the
% polygon whose vertices are the Black Dots. Delete all
% other points.
I = find(vertices(:, 3) == 1);
xv = vertices(I, 1); % Coordinates of the Black Dots.
yv = vertices(I, 2);
X = vertices(:, 1); % Coordinates of all vertices.
Y = vertices(:, 2);
IN = inpolygon(X, Y, xv, yv);
I = find(IN ~= 0);
vertices = vertices(I, :);
% Now check for any Black Dots that may have been turned into
% concave vertices after the previous deletion step. Delete
% any such Black Dots and recompute the polygon as in the
% previous section of code. When no more changes occur, set
% flag to 0, which causes the loop to terminate.
x = vertices(:, 1);
y = vertices(:, 2);
angles = polyangles(x, y); % Find all the interior angles.
I = find(angles > 180 & vertices(:, 3) == 1);
if isempty(I)
flag = 0;
else
J = 1:length(vertices);
for k = 1:length(I)
K = find(J ~= I(k));
J = J(K);
end
vertices = vertices(J, :);
end
end
% Final pass to delete the vertices with angles of 180 degrees.
x = vertices(:, 1);
y = vertices(:, 2);
angles = polyangles(x, y);
I = find(angles ~= 180);
% Vertices of the MPP:
x = vertices(I, 1);
y = vertices(I, 2);
然后查看效果:
还是用上例中,最终二次采样结果连线像个“凸”的图:
f = imread('circular.tif');
B = boundaries(f);
d = cellfun('length', B);
[max_d, k] = max(d);
b = B{
1};
[m n] = size(f);
g = bound2im(b, m, n, min(b(:, 1)), min(b(:, 2)));
[x, y] = minperpoly(f, 2);
b = connectpoly(x, y);
g2 = bound2im(b, m, n);
[x, y] = minperpoly(f, 4);
b = connectpoly(x, y);
g3 = bound2im(b, m, n);
[x, y] = minperpoly(f, 8);
b = connectpoly(x, y);
g4 = bound2im(b, m, n);
[x, y] = minperpoly(f, 12);
b = connectpoly(x, y);
g5 = bound2im(b, m, n);
[x, y] = minperpoly(f, 16);
b = connectpoly(x, y);
g6 = bound2im(b, m, n);
[x, y] = minperpoly(f, 20);
b = connectpoly(x, y);
g7 = bound2im(b, m, n);
subplot(2,4,1);
imshow(f);
subplot(2,4,2);
imshow(g);
subplot(2,4,3);
imshow(g2);
subplot(2,4,4);
imshow(g3);
subplot(2,4,5);
imshow(g4);
subplot(2,4,6);
imshow(g5);
subplot(2,4,7);
imshow(g6);
subplot(2,4,8);
imshow(g7);
上图中第一个为原图,第二个是边界图,从第三个开始为构造细胞联合体的边界分别为2、4、8、12、16、20的MPP
标记
是边界的一维函数的表示,它可以通过多种方法生成。比如:用在极坐标系的条件下画出一个内部点(比如质心)到边界的距离。如下图所示:
通过这样的操作,能将边界简化为一个一维函数,从而实现降维,使得边界更加容易描述。但仅在保证从原点至边界的延伸向量与边界相交一次并产生一个角度不断增加的单值函数时,参考标记才有意义
这能够排除本身相交的边界,也会排除有深窄凹陷或细长突出的边界。
虽然由刚才描述的方法生成的标记具有平移不变性,但是它们依赖于旋转和伸缩。关于旋转的归一化,可以通过选择相同起点而不考虑形状的方向来产生标记的方法获得。这样做的一种方法是,若对每一个感兴趣的形状该点恰好惟一且与旋转误差无关,则将起始点选择为远离向量的原点的点。
另一种方法是在主特征轴上选择一个点。此方法需要更多的计算,但是更粗糙,因为特征轴的方向是通过所有的轮廓点确定的。还有一种方法是先获得边界的链码,然后用前面讨论过的方法,假设旋转可以通过前面定义的代码方向中的离散角度加以近似。
通过[st, angle, x0, y0 ] = signature(b, x0 , y0)
函数查找给定边界的标记
一条
顺时针或逆时针方向排列边界的xy坐标。中心坐标
,若省略,则默认为边界的质心
st
、angle
的大小为360×1,表示一度的分辨率[THETA, RHO] = cart2pol(X, Y)
则是将笛卡尔坐标转换成极坐标。X和Y都是列向量,且该XY与我们IPT中的XY是相反定义的,即x=y, y=-x。f = imread('circular.tif');
B = boundaries(f);
d = cellfun('length', B);
[max_d, k] = max(d);
b = B{
1};
[m n] = size(f);
g = bound2im(b, m, n, min(b(:, 1)), min(b(:, 2)));
subplot(1,2,1);
imshow(g)
subplot(1,2,2);
[st, angle, x0, y0] = signature(b);
plot(angle, st);
讲道理,规则的圆得到的标记因该是一条直线。这是书中给出的方形和三角形的标记曲线:
将一条边界分解为片断通常是很有用的。分解降低了边界的复杂度,从而简化了描述过程。当边界包含一个或者多个携带形状信息的重要凹面时,这种方法尤其具有吸引力。在这种情况下,使用由边界包围的区域凸壳对边界的鲁棒分解是一种有力的工具。
任意集合S的凸壳H是包含S的最小凸集。集合的差H-S称为S的凸缺D。为了解如何使用这些概念将边界分成有意义的片断,考虑下图,其显示了一个对象(集合S)及其凸缺(阴影区域)。区域边界可以通过沿S轮廓线标出进入或者离开凸缺组成部分时引起变化的点来加以分割。下图右显示了这种情况下的结果。原理上,该方案独立于区域大小和方向。在实际中,这种处理要先于减少“非重要”凹面数目的图像平滑处理完成。在刚刚讨论的方法中,执行边界分解的必要MATLAB工具包含在函数regionprops
中,后面讨论。
骨骼可以理解为究极腐蚀
,腐蚀到只剩一个线来表示图形,就类似于连通整个人体的骨骼一样,故名为骨骼
。
区域骨骼可以通过中轴变化MAT
加以定义。一个边框为b的区域R的MAT描述如下:对于R中的每个点p
,寻找b中的最邻近点。若p比这样的邻近点大,则我们称p
属于R的中轴线。
这只是一个比较清晰的定义,具体实现的话已经有更多的改进算法,毕竟这种算法太复杂,效率低下。
IPT中通过函数bwmorph
使用如下语法来生成二值图像B中所有区域的骨骼:S = bwmorph(B, 'skel', Inf)
通过修改函数中Inf
的值,可以应用去刺算法后的骨骼,这图不明显,故未对比。
在本节中,我们将讨论若干用于处理区域边界的描绘子。如后面所述,这些描绘子中的大多数可用于边界或区域。在IPT中,这些描绘子没有就其适用范围加以分类。
边界长度描绘子:4连接边界的长度则是边界中的像素数-1.八连接则是水平垂直边界记1,对角线上的则是 2 \sqrt{2} 2 可以通过该函数提取4或8连接的边界g = bwperim(f, conn)
。
其中conn
可以通过自定义的矩阵来定义连通性。比如通过conn=ones(3)
可以定义一个8连通性。
边界直径:边界上最远点的欧氏距离。但最远点并不是唯一的,如圆或方形上的点。但是我们通常做如下假设:若直径是一个有用的描绘子,则最好应用到具有单个最远点对的边界。连接这些点的线段称为边界的长轴。边界短轴定义与边界长轴相垂直的线段,长轴和短轴所形成的矩形完全包含了边界。该矩形称为基本矩形,其长轴和短轴的比率为边界的偏心率。
通过s = diameter(L)
计算边界的直径,长轴,短轴和边界区域的基本矩形。
L是个标记矩阵,s是个结构体:
一般基于4方向的Freeman
链码的边界的形状数,定义为最小幅值的一阶差分。形状数的阶定义为表示它的数字的个数,因此,一个边界的形状数,可由fchcode
的参数c.diffmm
给出。
正如前面中所述的那样,4方向Freeman
链码可通过使用最小幅度的整数,使其对起始点不敏感,并可通过使用码的一阶差分,使其对于90°的倍数的旋转不敏感。这样,形状数就对起点以及90°倍数的旋转均不敏感。频繁用于归一化任意旋转的一种方法是使坐标轴这一与长轴对齐,然后基于旋转后图形提取4方向链码。下图可以所示该过程:
上图显示了xy平面中的K点数字边界,在任意点(x0, y0)开始,沿边界逆时针方向进行会出现坐标对(x0, y0),(x1, y1),(x2, y2),……,(xk-1, yk-1)。
这样坐标可以表示为:x(k)=xk,y(k)=yk。边界本身可以表示为坐标序列s(k)=[x(k),y(k)],k=0,1,2,…,K-1 进而每个坐标可以当做一个复数来处理,从而得到:s(k)=x(k)+jy(k)
而s(k)的离散傅里叶变换为:
a ( u ) = ∑ k = 0 K − 1 s ( k ) e − j 2 π u k / K a(u)=\sum_{k=0}^{K-1}s(k)e^{-j2\pi uk/K} a(u)=k=0∑K−1s(k)e−j2πuk/K
其中,u=0,1,2,…,K-1。复数系a(u)称为边界的傅里叶描绘子。这些系数的傅里叶逆变换可以恢复s(k)即
s ( k ) = 1 K ∑ u = 0 K − 1 a ( u ) e j 2 π u k / K s(k)=\frac{1}{K}\sum_{u=0}^{K-1}a(u)e^{j2\pi uk/K} s(k)=K1u=0∑K−1a(u)ej2πuk/K
我们知道,高频分量决定细节部分,低频分量决定总体形状
,那么我们可以我们可以通过控制值计算前P个傅里叶系数得到K的近似值,且P越大细节越多,P减少则边界的细节损失会增加:
s ˆ ( k ) = 1 P ∑ u = 0 P − 1 a ( u ) e j 2 π u k / K \text{\^{s}}(k)=\frac{1}{P}\sum_{u=0}^{P-1}a(u)e^{j2\pi uk/K} sˆ(k)=P1u=0∑P−1a(u)ej2πuk/K
下面的函数frdescp
可以用于计算边界的傅里叶描绘子s,或ifrdescp
通过给定数量的描绘子计算其逆变换,产生一条封闭的空间曲线。
f = imread('circular.tif');
B = boundaries(f);
d = cellfun('length', B);
[max_d, k] = max(d);
b = B{
1};
[m n] = size(f);
g = bound2im(b, m, n, min(b(:, 1)), min(b(:, 2)));
z = frdescp(b);
num = size(z, 1);
display(num);
subplot(2,3,1);
imshow(f);
subplot(2,3,2);
z50 = ifrdescp(z, 0.5*num);
z50im = bound2im(z50, m, n);
imshow(z50im);
subplot(2,3,3);
z10 = ifrdescp(z, 0.1*num);
z10im = bound2im(z10, m, n);
imshow(z10im);
subplot(2,3,4);
z5 = ifrdescp(z, 0.05*num);
z5im = bound2im(z5, m, n);
imshow(z5im)
subplot(2,3,5);
z2 = ifrdescp(z, 0.02*num);
z2im = bound2im(z2, m, n);
imshow(z2im)a
subplot(2,3,6);
z1 = ifrdescp(z, 0.01*num);
z1im = bound2im(z1, m, n);
imshow(z1im)
其中一共有506个描绘子,图23456分别是50%、10%、5%、2%、1%描绘子逆变换出来的图像。
书上这个例子的更加直观
一维边界表示的形状(如边界线段和标记波形)可以使用统计矩(如均值、方差和高阶矩)定量地描述。考虑下图,左侧显示了一个边界线段图,右侧显示了一个以任意变量r的一维函数g®描绘的线段。该函数的获得方式如下:连接该线段的两个端点以形成一个主轴,然后使用11.3.2节中讨论的函数x×2majoraxis来使长轴与x轴对齐。
描述g®的形状的一种方法是将g®归一化到单位面积内,并把它当做一个直方图来处理。换言之,g(ri)可以看成是值ri发生的概率。在这种情况下,r可看做是一个随机变量,其矩为:
μ n = ∑ i = 0 K − 1 ( r i − m ) n g ( r i ) μ_n = \sum_{i=0}^{K-1}(r_i-m)^ng(r_i) μn=i=0∑K−1(ri−m)ng(ri)
其中
m = ∑ i = 0 K − 1 r i g ( r i ) m = \sum_{i=0}^{K-1}r_ig(r_i) m=i=0∑K−1rig(ri)
在这种表示中,K是边界上的点的数目,而μn(r )与g(r )的形状直接相关。例如,二阶矩μ2(r )用于度量边界曲线偏离r的平均值的程度,三阶矩μ3(r )则用于度量边界曲线均值的对称性。统计矩可用函数statmoments
计算。
我们已实现的是把描述任务约简为一维函数。虽然矩是普遍采用的方法,但它并不是用于该目的的惟一描绘子。例如,其他方法包括计算一维离散傅里叶变换,获得它的频谱,然后使用频谱的前q个分量来描述g®。矩较之其他方法的优点在于矩的实现是直接的,同时矩还携带有边界形状的“物理”解释。上图也可以看出,这种方法对于旋转不敏感。若我们希望,则可以通过缩放g和r的值的范围来对尺寸归一化。
该函数是用于计算区域描绘子的主要函数,语法为:D = regionprops(L, properties)
其中,L
是一个标记矩阵,D
是长度为max((L:))
的结构数组。该结构的域表示每个区域的不同度量,由properties
指定的那样,各参数含义为:
描述区域的一种重要方法是量化区域的纹理内容。本节用基于统计的方法
和谱度量法
来计算纹理
一般是用亮度直方图的统计属性作为基础,即用统计矩去度量。那么均值的n阶矩表示为:
μ n = ∑ i = 0 L − 1 ( z i − m ) n p ( z i ) \mu_n = \sum_{i=0}^{L-1}(z_i-m)^np(z_i) μn=i=0∑L−1(zi−m)np(zi)
其中zi表示亮度的一个随机变量,p(z)是一个区域中的灰度级的直方图,L是可能的灰度级数。而
m = ∑ i = 0 L − 1 z i p ( z i ) m = \sum_{i=0}^{L-1}z_ip(z_i) m=i=0∑L−1zip(zi)
是均值亮度。那么这里有一些常见的描绘子:
这三幅图从左到右分别表示平滑的、粗糙的、周期的纹理。
他们的直方图为:
而他们的纹理度量为:
纹理的频谱度量是基于傅里叶频谱的,适用于描述图像中的周期或近似周期二维模式的方向性。这些在频域中易于识别的全局纹理模式,在空间域中则很难检测到。因此,纹理的频谱对于判别周期纹理模式和非周期纹理模式非常有用,对于量化两个周期模式间的差也非常有用。
用极坐标表示频谱所得到的函数S(r,θ)可以简化频谱特性的解释,其中S是频谱函数,r和θ是极坐标系中的变量。对于每一个方向θ,我们可以将S(r,θ)看成是一个一维函数Sθ®。同样,对于每一个频率r,我们也可以将Sr(θ)看成是一个一维函数。θ为定值时,分析Sθ®可以得到从原点出发沿半径方向的频谱特性;同理, r为定值时,分析Sr(θ)可以得到以原点为中心的一个圆的频谱特性。
通过求这些函数的积分(离散变量求和),就可以得到全局描述:
S ( r ) = ∑ θ = 0 π S θ ( r ) , S ( θ ) = ∑ r = 1 R 0 S r ( θ ) S(r) = \sum_{\theta=0}^\pi S_{\theta}(r) \ \ ,\ \ S(\theta)=\sum_{r=1}^{R_0}S_r(\theta) S(r)=θ=0∑πSθ(r) , S(θ)=r=1∑R0Sr(θ)
其中R0是半径在原点的圆的半径
通过函数[srad, sang, S] = spexture(f)
srad
是S(r)
,sang
是S(θ)
,S是频谱。
a = imread('rand.tif');
b = imread('order.tif');
[ra, ta, Sa] = specxture(a);
[rb, tb, Sb] = specxture(b);
subplot(2,3,1);
imshow(a);
subplot(2,3,2);
plot(ra);
subplot(2,3,3)
plot(ta);
subplot(2,3,4);
imshow(b);
subplot(2,3,5);
plot(rb);
subplot(2,3,6)
plot(tb);
一幅图像的f(x,y)的二维(p+q)阶矩定义为:
m p q = ∑ x ∑ y x p y q f ( x , y ) m_{pq}=\sum_x \sum_yx^py^qf(x,y) mpq=x∑y∑xpyqf(x,y)
其中p,q = 0,1,2,3,4……。求和在跨越图像的所有空间坐标x,y的值上进行。相应的中心矩定义为:
μ p q = ∑ x ∑ y ( x − x ˉ ) p ( y − y ˉ ) q f ( x , y ) 其 中 x ˉ = m 10 m 00 , y ˉ = m 01 m 00 \mu_{pq}=\sum_x\sum_y(x-\bar{x})^p(y-\bar{y})^qf(x,y)\, 其中\bar{x}=\frac{m_{10}}{m_{00}},\bar{y}=\frac{m_{01}}{m_{00}} μpq=x∑y∑(x−xˉ)p(y−yˉ)qf(x,y)其中xˉ=m00m10,yˉ=m00m01
归一化(p+q)阶中心矩定义为:
η p q = μ p q μ 00 γ ( p , q = 0 , 1 , 2 , . . . , ) , γ = p + q 2 + 1 , ( p + q = 2 , 3 , . . . ) \eta_{pq}=\frac{\mu_{pq}}{\mu^{\gamma}_{00}} \ (p,q=0,1,2,...,), \ \ \ \gamma=\frac{p+q}{2}+1,(p+q=2,3,...) ηpq=μ00γμpq (p,q=0,1,2,...,), γ=2p+q+1,(p+q=2,3,...)
对于平移、缩放、镜像和旋转都不敏感的7个二维不变矩的集合都可以由这些公式推导出来:
其函数phi = invmoments(f)
可以实现这7个等式,phi是一个包含这七个不变矩的7元素行向量。
假设有n幅已配准的图像,它们的“堆叠”方式如下图所示,对于任意坐标(i,j)都有n个像素,每一幅图像在该位置都有一个像素,这些像素以列向量的形式排列:x=[x1 x2 x3 ··· xn]T。
若这些图像大小有M×N个,则n幅图像中,包含所有像素的n维向量总共有MN个。
一个向量群的平均向量mx可以通过样本的平均值来估计:
m x = 1 K = ∑ k = 1 K X k , K = M N m_x = \frac{1}{K} = \sum_{k=1}^{K}X_k , \ \ \ \ \ \ K=MN mx=K1=k=1∑KXk, K=MN
那么n×n的协方差矩阵Cx则可以一下近似计算得出
C x = 1 K − 1 ∑ K = 1 K ( x k − m x ) ( x k − m x ) T C_x = \frac{1}{K-1}\sum_{K=1}^{K}(x_k-m_x)(x_k-m_x)^T Cx=K−11K=1∑K(xk−mx)(xk−mx)T
因为Cx是一个实对称矩阵,所以总可以找到n个正交特征向量。
那么主分量变换可以由下式给出:
y = A ( x − m x ) y= A(x-m_x) y=A(x−mx)
不难看出,向量y的元素是不相关的。因此,协方差矩阵Cy是对角阵。矩阵A的行是Cx的归一化特征向量。因为Cx是个实对称矩阵,这些向量构成了一个正交集,所以这些沿着Cy主对角线方向的元素就是Cx的特征值。Cy的第i行主对角线元素是向量元素yi的变量。
矩阵A的行向量是正交的,所以它的逆矩阵等于其转置,所以可以通过求A的逆变换来获得x;
x = A T y + m x x = A^Ty+m_x x=ATy+mx
通过S=cat(3,f1,f2,...,fn)
叠加,然后[X, R] = imstack2vectors(S, MASK)
其中S是一个图像堆栈,X是使用叠加的方法从S中提取出来的向量数组。输入MASK是一幅大小为M×N的逻辑图像或数字图像。该图像在用S的元素来生成X的位置有非零元素,而在将被忽略的位置元素为零。
例如,若我们仅想在堆叠的这些图像的右上方象限使用向量,则在该象限中MASK的值为1,而在其余位置的值为0。若参数中未包含MASK,则所有的图像位置将用于形成X。最后,参数R是个数组,该数组的行对应于用于形成X的向量的位置的二维坐标。
通过函数covmatrix
来计算平均向量和x中的向量的协方差矩阵。
function [C, m] = covmatrix(X)
%COVMATRIX Computes the covariance matrix of a vector population.
% [C, M] = COVMATRIX(X) computes the covariance matrix C and the
% mean vector M of a vector population organized as the rows of
% matrix X. C is of size N-by-N and M is of size N-by-1, where N is
% the dimension of the vectors (the number of columns of X).
% Copyright 2002-2004 R. C. Gonzalez, R. E. Woods, & S. L. Eddins
% Digital Image Processing Using MATLAB, Prentice-Hall, 2004
% $Revision: 1.4 $ $Date: 2003/05/19 12:09:06 $
[K, n] = size(X);
X = double(X);
if n == 1 % Handle special case.
C = 0;
m = X;
else
% Compute an unbiased estimate of m.
m = sum(X, 1)/K;
% Subtract the mean from each row of X.
X = X - m(ones(K, 1), :);
% Compute an unbiased estimate of C. Note that the product is
% X'*X because the vectors are rows of X.
C = (X'*X)/(K - 1);
m = m'; % Convert to a column vector.
end