主要讨论边界的形状数描述、傅里叶描述和其他简单的描述。
1)基础描述
边界长度:边界的像素数粗略近似
边界直径:相隔最远的两个点的距离(链码一节介绍的长短轴)
边界偏心率:长轴/短轴
其他规范化近似:外接圆、外接矩形、外接凸包
2)形状数描述
参考表示和描述(1)的Freeman编码,图1-2形状编号的长度即为形状数的阶。
这个概念的意义在于,你可以指定一个边界的阶(以4-方向为例,对于闭合边界,其阶为大于等于4的偶数),然后对边界做重采样。
如图1-2中指定形状数阶数为18,首先获得其基本矩形(左2),然后计算其最接近18的划分(6*3,左3)。
在轮廓匹配中,可以用形状数作为参考:以4-方向为例,依次取阶数K=4,6,8...2n(显然随着K的增大,边界描述越精确),对待匹配的两组边界生成形状数,比较两组形状数,取d为使得两者相同的最大阶。显然对于相同的两条边界,d=无穷大。
3)傅里叶描述
使用一个复数来描述一个点:s(k) = x(k) + jy(k),然后对其做离散傅里叶变换:
这里介绍频域的4种边界操作(图2-1):一种是通过忽略部分高频傅里叶系数获得边界的近似描述(低频决定边界形状,高频决定边界细节);另三种是旋转、平移、缩放。
a. 边界近似
取前P个傅里叶系数做逆变换:。方便起见,直接将后K-P个系数赋0做逆变换就可以了。
b. 边界旋转
需要注意的是这里的旋转是以序列中(0,0)为原点的,将序列减去质心就可以实现以质心为原点旋转了。
c. 边界平移
,其中,乘以冲击序列的傅里叶变换
d. 边界缩放
,其中a为缩放系数
图2-1. 边界傅里叶描述
如图2-1所示,a为原始图像,b~e为使用Moore边界追踪算法后在频域做平移、旋转、缩放、近似。其中旋转部分做了中心平移,近似部分取前2/3的傅里叶系数做逆变换。
此外,边界还可以用统计矩来描述,但一般对区域的不变矩描述更广泛。
主要讨论区域的纹理描述、不变矩描述和其他简单描述
1)基础描述
区域面积、周长都是用相应像素数来粗略估计
区域致密性:(周长)^2 / 面积。显然圆形具有最小的致密性。
区域圆度率:区域面积 / 具有相同周长的圆的面积。
2)纹理描述
纹理描述主要有三种方法:统计方法、结构方法、频谱方法。
统计方法考察纹理的平滑、粗糙、粒状等特征;结构方法考察纹理的排列描述;频谱方法考察纹理的周期性。
以下主要讨论统计方法的几种指标
a. 统计矩
对一个区域,统计其归一直方图,计算其均值的z的第n阶矩为,其中m是平均灰度。
n=2:二阶矩(方差),描述区域的灰度对比变化。由其构成的归一化R度量对粗糙的区域有较大响应
n=3、4:三阶矩度量直方图倾斜性(负数表示总体小于均值),四阶矩度量直方图平坦度。
b. 灰度共生矩阵(GLCM)
GLCM不仅考察灰度分布,还考虑了像素的相对位置。
令Q为两个像素相对位置的一个定义(比如当前像素的右邻像素),G为针对区域f由Q生成的共生矩阵(对于8bit图像,大小为256*256)。按当前Q定义,g_ij表示区域f中灰度值为i且其右邻像素灰度值为j的像素对数目(图3-1)。
图3-1. 灰度共生矩阵(《数字图像处理》)
令,由GLCM可得到以下统计量
对比度:,纹理越深,该值越大
3)不变矩描述
令图像f(x,y)尺寸M*N,其二维(p+q)阶为,相应的(p+q)阶中心矩为,其中。
根据归一化中心矩可获得7个不变矩(hu不变矩),其对同一区域的平移、缩放、旋转、镜像能保持一致描述
OpenCV有hu不变矩的实现:
// 处理图像 int i, j; Mat src = imread("../DIP_CODE+IMAGE/IMAGE/dipum_images_ch11/Fig1123(a)(Original_Padded_to_568_by_568).tif",0); Mat shift_(src.size(), CV_8UC1, Scalar::all(0)); // 平移 for (i = 0; i < src.rows-50; i++) { uchar* ptrSrc = src.ptr<uchar>(i); uchar* ptrShift = shift_.ptr<uchar>(i + 50); for (j = 0; j < src.cols-50; j++) { uchar value = ptrSrc[j]; if (value > 0) ptrShift[j+50] = value; } } Mat resize_(src.size(), CV_8UC1, Scalar::all(0)); // 缩放 Mat resize_tmp; resize(src, resize_tmp, Size(src.cols/2, src.rows/2)); Mat roi_(resize_, Rect(src.cols / 4, src.rows / 4, src.cols / 2, src.rows / 2)); resize_tmp.copyTo(roi_); Mat flip_; // 镜像 flip(src, flip_, 1); Mat rotate_; // 旋转 Point2f center_(src.cols/2, src.rows/2); Mat r = getRotationMatrix2D(center_, 45, 1); warpAffine(src, rotate_, r, src.size()); namedWindow("src"); namedWindow("shift"); namedWindow("resize"); namedWindow("flip"); namedWindow("rotate"); imshow("src", src); imshow("shift", shift_); imshow("resize", resize_); imshow("flip", flip_); imshow("rotate", rotate_); //////////////////////////////////////////////////////////////////////////////// // 统计不变矩 Moments mom = moments(src); double hu[7]; HuMoments(mom, hu); for (i = 0; i < 7; i++) hu[i] = log(abs(hu[i])) * (-1*hu[i]/abs(hu[i])); cout << "************** src *****************" << endl; for (i = 0; i < 7; i++) cout << "hu[" << i << "]=" << hu[i] << "; "; cout << endl; mom = moments(shift_); HuMoments(mom, hu); for (i = 0; i < 7; i++) hu[i] = log(abs(hu[i])) * (-1*hu[i] / abs(hu[i])); cout << "************** shift *****************" << endl; for (i = 0; i < 7; i++) cout << "hu[" << i << "]=" << hu[i] << "; "; cout << endl; mom = moments(resize_); HuMoments(mom, hu); for (i = 0; i < 7; i++) hu[i] = log(abs(hu[i])) * (-1*hu[i] / abs(hu[i])); cout << "************** resize *****************" << endl; for (i = 0; i < 7; i++) cout << "hu[" << i << "]=" << hu[i] << "; "; cout << endl; mom = moments(flip_); HuMoments(mom, hu); for (i = 0; i < 7; i++) hu[i] = log(abs(hu[i])) * (-1*hu[i] / abs(hu[i])); cout << "************** flip *****************" << endl; for (i = 0; i < 7; i++) cout << "hu[" << i << "]=" << hu[i] << "; "; cout << endl; mom = moments(rotate_); HuMoments(mom, hu); for (i = 0; i < 7; i++) hu[i] = log(abs(hu[i])) * (-1*hu[i] / abs(hu[i])); cout << "************** rotate *****************" << endl; for (i = 0; i < 7; i++) cout << "hu[" << i << "]=" << hu[i] << "; "; cout << endl; waitKey(0);