像素是图像的基本元素,像素与像素之间存在着某些联系,理解像素间的基本关系是数字图像处理的基础。常见的像素间的基本关系包括:邻域、邻接、通路、连通、距离。
1. 邻域
邻域表示了像素之间的连接关系。
像素(x,y)的邻域 ,是指与像素(x,y)对应的点的集合{(x+p,y+q)} ,其中 (p,q) 为一对有意义的整数。邻域是像素(x,y)附近像素形成的区域,像素 (x,y) 也被称为中心像素。
最常用的邻域有以下几种:
-
4 邻域 :对于像素(x,y),上下左右4个像素被称为 4 邻域,使用 <math xmlns="http://www.w3.org/1998/Math/MathML"> N 4 ( p ) N_4(p) </math>N4(p)表示。4 邻域的四个像素分别是:(x,y-1)、(x,y+1)、(x-1,y)、(x+1,y)。
-
D 邻域 :对于像素(x, y), 其左上、右上、左下、右下的四个对角上的像素组成了 D 邻域,使用 <math xmlns="http://www.w3.org/1998/Math/MathML"> N d ( p ) N_d(p) </math>Nd(p)表示。D 邻域四个像素分别是:(x + 1, y + 1)、( x + 1, y - 1)、(x - 1, y + 1)、(x - 1, y - 1)。
-
8 邻域 :对于像素(x,y),它的 4 邻域的点和 D 邻域的点组成了 8 邻域,使用 <math xmlns="http://www.w3.org/1998/Math/MathML"> N 8 ( p ) N_8(p) </math>N8(p)表示。那么, <math xmlns="http://www.w3.org/1998/Math/MathML"> N 8 ( p ) = N 4 ( p ) + N d ( p ) N_8(p)=N_4(p)+N_d(p) </math>N8(p)=N4(p)+Nd(p)
邻域是一个很基础的概念。后续我们对图像进行卷积 操作的时候,通常是对当前像素的邻域像素进行操作的。
以一个最简单的均值滤波为例,均值滤波是对于每一个像素点, 将其设定为取其邻域窗口内的所有像素的平均值。
算术均值滤波器的公式:
<math xmlns="http://www.w3.org/1998/Math/MathML"> g ( x , y ) = 1 m ∗ n ∑ i , j ∈ S x y f ( i , j ) g(x,y) = \frac{1}{m*n}\sum_{i,j\in S_{xy}}f(i,j) </math>g(x,y) = m∗n1∑i,j∈ Sxyf(i,j)
其中, <math xmlns="http://www.w3.org/1998/Math/MathML"> S x y S_{xy} </math>Sxy表示以像素(x,y)为中心的区域,m*n 是 模板 的大小。f(x,y) 表示原图像,g(x,y) 表示使用 <math xmlns="http://www.w3.org/1998/Math/MathML"> S x y S_{xy} </math>Sxy 定义的邻域中的像素所计算出的算术平均值。
这里的模板,也可以被称为核(kernels)、窗口(windows)、掩模(mask)。
下图以 3*3 的模板为例,均值滤波会对原图像的每一个像素点,计算它的邻域像素和模版矩阵的对应元素的乘积,然后加起来,作为该像素位置的值。窗口的移动是从左到右,然后从上到下依次移动。
下面,实现一个简单的均值滤波函数
cpp
Mat meanFilter(Mat &src, int ksize = 3)
{
cv::Mat dst = src.clone();
int k0 = ksize/2;
int sum[3] = {0,0,0};
for(int i=k0;i<dst.rows-k0-1;i++)
{
for(int j=k0;j<dst.cols-k0-1;j++)
{
memset(sum,0, sizeof(sum));
for(int channel = 0; channel<3; channel++)
{
for(int m = 0;m<ksize;m++)
{
for (int n=0;n<ksize;n++)
{
sum[channel] += src.at<cv::Vec3b>(i-k0+m,j-k0+n)[channel];
}
}
dst.at<Vec3b>(i,j)[channel] = saturate_cast<uchar>((float)sum[channel] /(ksize*ksize));
}
}
}
return dst;
}
当然这个代码只是粗略地实现均值滤波,存在着很多优化的空间,例如使用积分图、卷积核分离等。OpenCV 也提供了均值滤波函数 blur() 函数。
cpp
int main(int argc,char *argv[])
{
Mat src = imread(".../flower.jpg");
imshow("src",src);
Mat dst;
dst = meanFilter(src, 15);
imshow("meanFilter",dst);
blur(src,dst,Size(15,15));
imshow("blur",dst);
waitKey(0);
return 0;
}
上面只是简单例举了领域的使用场景,后续会有专门的文章来详细介绍卷积和滤波。
2. 邻接
邻接 是指两个像素,在位置上相邻 并且取值相同或相近。
我们用 V 表示定义邻接的灰度值集合。在二值图像中,V={1} 表示值为1的像素邻接。在灰度图像中,V 包含更多的元素。
-
**4 邻接:**对于灰度值在 V 集合中的像素 p 和 q,如果 q 在 <math xmlns="http://www.w3.org/1998/Math/MathML"> N 4 ( p ) N_4(p) </math>N4(p) 中,那么像素 p 和 q 是 4 邻接的。
-
**8 邻接:**对于灰度值在 V 集合中的像素 p 和 q,如果 q 在 <math xmlns="http://www.w3.org/1998/Math/MathML"> N 8 ( p ) N_8(p) </math>N8(p) 中,那么像素 p 和 q 是 8 邻接的。
-
**m 邻接(混合邻接):**m 邻接是 8 邻接的改进。只要满足以下任何一个条件即可:
-
q 在 <math xmlns="http://www.w3.org/1998/Math/MathML"> N 4 ( p ) N_4(p) </math>N4(p) 中
-
q 在 <math xmlns="http://www.w3.org/1998/Math/MathML"> N d ( p ) N_d(p) </math>Nd(p) 中,且集合在 <math xmlns="http://www.w3.org/1998/Math/MathML"> N 4 ( p ) ∩ N 4 ( q ) N_4(p)\cap N_4(q) </math>N4(p)∩N4(q) 中没有来自 V 中的像素。
-
像素 p 和 q 是 4 邻接,那么它们一定是 8 邻接的。反之,不一定成立。
下图反应了 8 邻接会带来二义性。
从图中可以看到,p 是中心像素。
-
q1、q2 和 p 是 8 邻接的。
-
q1 和 p 非 m 邻接的。
-
q2 和 p 是 m 邻接的。
某条通路经过像素 q2、p、q1,那会有几种走法呢?
如果从 p、q1、q2 是 8 邻接的角度看,p 到 q1 可以有2种走法,所以 q2 到 q1 的通路有2条。
同理,从 m 邻接角度看,p 和 q1 只有1种走法,所以 q2 到 q1 的通路只有1条。
所以,m 邻接的引入是为了消除 8 邻接常常带来二义性。
从集合的角度看: <math xmlns="http://www.w3.org/1998/Math/MathML"> 4 邻接 ⊂ m 邻接 ⊂ 8 邻接 4 邻接 \subset m 邻接 \subset 8邻接 </math>4邻接 ⊂ m邻接 ⊂ 8邻接
3. 通路
通路 :从像素 p <math xmlns="http://www.w3.org/1998/Math/MathML"> ( x 0 , y 0 ) (x_0,y_0) </math>(x0,y0) 到像素 q <math xmlns="http://www.w3.org/1998/Math/MathML"> ( x n , y n ) (x_n,y_n) </math>(xn,yn) 的通路是特定的像素序列,其坐标为:
<math xmlns="http://www.w3.org/1998/Math/MathML"> ( x 0 , y 0 ) , ( x 1 , y 1 ) , ( x 2 , y 2 ) , . . . ( x n , y n ) (x_0,y_0),(x_1,y_1),(x_2,y_2),...(x_n,y_n) </math>(x0,y0),(x1,y1),(x2,y2),...(xn,yn)
并且满足, <math xmlns="http://www.w3.org/1998/Math/MathML"> ( x i , y i ) (x_i,y_i) </math>(xi,yi) 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( x i − 1 , y i − 1 ) (x_{i-1},y_{i-1}) </math>(xi−1,yi−1) 对于 <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 ≤ i ≤ n 1 \leq i \leq n </math>1 ≤ i ≤ n 是邻接的。
**闭合通路:**如果满足 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( x 0 , y 0 ) = ( x n , y n ) (x_0,y_0)=(x_n,y_n) </math>(x0,y0)=(xn,yn),则通路是闭合通路。
由不同的邻接定义,可以得到不同的通路:4 邻接 => 4 通路,8 邻接 => 8 通路,m 邻接 => m 通路
所以,从中间的图可以看到 q2 和 q1 之间存在 8 通路,从最右的图可以看到 q2 和 q1 之间存在 m 通路。
从集合的角度看: <math xmlns="http://www.w3.org/1998/Math/MathML"> 4 通路 ⊂ m 通路 ⊂ 8 通路 4通路 \subset m 通路 \subset 8通路 </math>4通路 ⊂ m通路 ⊂ 8通路
下图中,p-q 通路对应的是不同的通路。
4. 连通
连通 :若 S 是图像中的一个像素子集,对于任意的 <math xmlns="http://www.w3.org/1998/Math/MathML"> p 、 q ∈ S p、q \in S </math>p、q ∈ S。如果存在一条由 S 中像素组成的从 p 到 q 的通路,则称 p 在图像集 S 中与 q 连通。
邻接 是连通的一种特例,连通是由一系列依次邻接的像素组成的。
连通分为 4 连通 和 8 连通。
连通分量:对于 S 中任意像素 p,所有与 p 相连通且又在 S 中的像素集合。
连通集:如果 S 中仅有一个连通分量,则 S 称为连通集。
在之前基本图形的绘制那篇文章里, 曾介绍过绘图函数所使用的 lineType 参数。
下面对这个参数做一些补充说明:
-
LINE_4 :基于 4 连通 Bresenham 算法处理的直线。
-
LINE_8 :基于 8 连通 Bresenham 算法处理的直线。
-
LINE_AA :基于高斯滤波平滑处理的直线。
下面的例子,展示了使用不同的 lineType 参数的效果
cpp
int main(int argc,char *argv[])
{
Mat image = Mat::zeros(Size(80, 80), CV_8UC3);
image.setTo(255);// 设置屏幕为白色
Point p1(20, 0);
Point p2(80, 60);
Point p3(0, 0);
Point p4(80, 80);
Point p5(0, 20);
Point p6(60, 80);
line(image, p1, p2, Scalar(0, 0, 255), 1, LINE_4);
line(image, p3, p4, Scalar(255, 0, 0), 1, LINE_8);
line(image, p5, p6, Scalar(0, 255, 0), 1, LINE_AA);
imshow("src", image);
waitKey(0);
return 0;
}
将生成的图片放大,可以看到使用 LINE_4、LINE_8、LINE_AA 画出来的线段效果是不同的。使用 LINE_AA 效果看上去是最好的,其次是 LINE_8。
通过邻接可以引申很多概念,邻接 -> 通路 -> 连通 -> 连通集 -> 区域/邻接区域 -> 前景和背景 -> 边界
5. 距离
对于像素 p(x,y)、q(s,t) 和 z(u,v),如果满足:
-
非负性:D(p,q) ≥ 0
-
同一性:D(p,q)=0,当且仅当p=q时
-
对称性:D(p,q) = D(q,p)
-
直递性:D(p,z) ≤ D(p,q) + D(q,z)
则称 D 是距离的度量函数。
在欧几里得空间中,点 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( x 1 , x 2 , . . . , x n ) (x_1,x_2,...,x_n) </math>(x1,x2,...,xn)和点 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( y 1 , y 2 , . . . , y n ) (y_1,y_2,...,y_n) </math>(y1,y2,...,yn)之间的闵可夫斯基距离:
<math xmlns="http://www.w3.org/1998/Math/MathML"> D ( x , y ) = ( ∑ i = 1 n ∣ x i − y i ∣ p ) 1 / p D(x,y)=(\sum_{i=1}^{n}|x_i - y_i|^p)^{1/p} </math>D(x,y)=(∑i=1n∣xi−yi∣p)1/p
- 曼哈顿距离
当 p = 1 时,即为曼哈顿距离或城市距离、街区距离,是指两个向量之间的距离,在计算距离时不涉及对角线移动。像素 p(x,y) 和 q(s,t) 之间的距离公式:
<math xmlns="http://www.w3.org/1998/Math/MathML"> D 4 ( p , q ) = ∣ x − s ∣ + ∣ y − t ∣ D_4(p,q)=|x-s|+|y-t| </math>D4(p,q)=∣x−s∣+∣y−t∣
表示从像素 p 向像素 q 出发,每次能走的点必须是在当前像素点的 4 邻域中。一步一步走到 q 点后,一共经过的像素点数就是曼哈顿距离。
- 欧式距离
当 p = 2 时,即为欧式距离,就是直角坐标系的距离。像素 p(x,y) 和 q(s,t) 之间的距离公式:
<math xmlns="http://www.w3.org/1998/Math/MathML"> D e ( p , q ) = ( x − s ) 2 + ( y − t ) 2 D_e(p,q) = \sqrt{(x-s)^2+(y-t)^2} </math>De(p,q) = (x−s)2+(y−t)2
- 切比雪夫距离
当 p = <math xmlns="http://www.w3.org/1998/Math/MathML"> ∞ \infty </math>∞ 时,即为切比雪夫距离或棋盘距离,像素 p(x,y) 和 q(s,t) 之间的距离公式:
<math xmlns="http://www.w3.org/1998/Math/MathML"> D 8 ( x , y ) = m a x ( ∣ x − s ∣ , ∣ y − t ∣ ) D_8(x,y)=max(|x-s|,|y-t|) </math>D8(x,y)=max(∣x−s∣,∣y−t∣)
表示从像素 p 向像素 q 出发,每次能走的点必须是在当前像素点的 8 邻域中。一步一步走到 q 点后,一共经过的像素点数就是切比雪夫距离。
6. 总结
本文涉及到很多概念,这些概念代表着像素间的基本关系。像邻域、连通在后续文章中很多都会涉及到,像距离又跟相似度有关,所以它们是数字图像的基础。