表示和描述(2)

接上篇 表示和描述(1)

边界描述

主要讨论边界的形状数描述、傅里叶描述和其他简单的描述。

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);

接下篇 表示和描述(3)

时间: 2024-09-29 08:39:50

表示和描述(2)的相关文章

查询字段描述sql-postgresql

查询字段描述sql SELECT 'comment on column ' || n.nspname ||'.'|| c.relname || '.' || a.attname ||' is '''|| col_description(a.attrelid,a.attnum) ||''';' FROM pg_class as c join pg_attribute as a on a.attrelid = c.oid join pg_namespace n on c.relnamespace=n

数据结构与算法C++描述学习笔记1、辗转相除——欧几里得算法

前面学了一个星期的C++,以前阅读C++代码有些困难,现在好一些了.做了一些NOI的题目,这也是一个长期的目标中的一环.做到动态规划的相关题目时发现很多问题思考不通透,所以开始系统学习.学习的第一本是<数据结构与算法C++描述>第三版,边学边做一些笔记.所以这些笔记中的代码有很多将会非常简单,甚至可能只有一个记录或者结论. 辗转相除法用来求两个整数的最大公约数,即能同时整除两个数的最大整数.程序如下: int gdc(int m,int n){ int rem; while(n!=0){ //

进程描述符

进程描述 广义上讲,所有进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合. 进程控制块 每个进程在内核中都有一个进程控制块(PCB)来维护进程相关的信息,Linux内核的进程控制块是task_struct结构体. task_struct结构体 task_struct是Linux内核下的一种数据结构,它会被装载到RAM里并且包含着进程的信息,每个进程把它的信息放在task_struct结构体里task_struct包含了这些内容: 标示符:描述本进程的唯一标示符 状态:任务状

获取枚举类型Description特性的描述信息

C#中可以对枚举类型用Description特性描述. 如果需要对Description信息获取,那么可以定义一个扩展方法来实现.代码如下: public static class EnumExtensions { public static string GetDescription(this object value) { if (value==null) return string.Empty; Type type = value.GetType(); var fieldInfo = ty

设计模式-单例模式(Go语言描述)

这篇博客我们继续来看设计模式,今天带来的是一个最简单而且最常用的模式-单例模式.那什么是单例模式呢?相信大家最它最熟悉不过了,那我们就来快速的了解一下它的定义. 保证一个类仅有一个实例,并提供一个访问它的全局访问点. 这个解释足够简单.说白了就是假如我们希望我们在我们的系统中该类仅仅存在1个或0个该类的实例.虽然单例模式很简单,但是熟悉java的同学可能了解,单例模式有很多写法,懒汉式.饿汉式.双重锁... 这么多形式,难道有什么目的?确实,不过他们的目的很明确,就是保证在一种特殊情况下的单例-

OpenCV4Android 提取特征点描述符(Feature Descriptor)

OpenCV4Android 提取特征点描述符(Feature Descriptor) 在得到keypoints之后(参考前面),通过使用相应的FeatureDescriptor就可计算得到关键点处的描述子. Native Code: JNIEXPORT void JNICALL Java_com_example_test_NativeUtil_computeDescripors( JNIEnv *env, jclass thiz, jlong mGrayAddr, jlong mRgbaAdd

USB 描述符

标准的USB设备有5种USB描述符:设备描述符,配置描述符,字符串描述符,接口描述符,端点描述符. 1 // Standard Device Descriptor 2 typedef struct 3 { 4 u8 bLength; 5 u8 bDescriptorType; 6 u16 bcdUSB; 7 u8 bDeviceClass; 8 u8 bDeviceSubClass; 9 u8 bDeviceProtocol; 10 u8 bMaxPacketSize0; 11 u16 idVe

文件描述符与文件指针等文件操作的几个问题

1.二者对比: 文件描述符就是open文件时产生的一个整数,直到一个索引作用,它用于UNIX系统中,用于标识文件.它是系统调用产生的. 文件指针是指向一个FILE的结构体,这个结构体里有一个元素就是文件描述符.它用于ANSI C标准的IO库调用中,用于标识文件.fopen是依赖于open的: 既然FILE中包含文件描述符元素,可以用fopen()直接获取指针fp,然后使用fp获得fp中所包含文件描述符fd的信息. 文件描述符应该是唯一的,但文件指针(值)却不是唯一的,但指向的对象却应该是唯一的.

UNIX环境编程学习笔记(4)——文件I/O之dup复制文件描述符

lienhua342014-08-23 UNIX 提供了两个函数 dup 和 dup2 用于复制一个现存的文件描述符. #include <unistd.h> int dup(int filedes); int dup2(int filedes, int filedes2); 返回值:若成功则返回新的文件描述符,如出错则返回-1. 由 dup 函数返回的文件描述符一定是当前可用文件描述符中的最小描述符.用 dup2 函数则可以通过参数 filedes2 指定目标文件描述符.如果filedes2

线特征LSD and 描述子LBD---LBD算法(二)

上一节主要是介绍LSD算法理论,这节主要是介绍LBD算法. 1.尺度空间中提取线段 为了克服在线检测碎片问题和提高大尺度变化的性能,我们的检测框架采用由对原始图像进行搞死下采样( with a set of 尺度因子and 高斯模糊 )的尺度空间金字塔组成(N层图像).我们首先用Edline算法每层的尺度空间产生一组线.每一条直线都有一个方向,它是由从直线左边到右边的大部分边缘像素的梯度构成的.然后通过在尺度空间中找到对应的直线来组织它们.在尺度空间中所有的线,他们被分配一个唯一的ID,如果图像