【OpenCV 例程200篇】225. 特征提取之傅里叶描述子

『youcans 的 OpenCV 例程200篇 - 总目录』


【youcans 的 OpenCV 例程200篇】225. 特征提取之傅里叶描述子


目标特征的基本概念

通过图像分割获得多个区域,得到区域内的像素集合或区域边界像素集合。我们把感兴趣的人或物称为目标,目标所处的区域就是目标区域。
特征通常是针对于图像中的某个目标而言的。图像分割之后,还要对目标区域进行适当的表示和描述,以便下一步处理。


3.4 傅里叶描述子

“描述”是对目标的抽象表达,在区别不同目标的基础上,尽可能对目标的尺度、平移、旋转变化不敏感。

傅里叶描述子(Fourier shape descriptors)的基本思想是用目标边界曲线的傅里叶变换来描述目标区域的形状,将二维描述问题简化为一维描述问题。傅里叶描述子具有旋转、平移和尺度不变性。

目标的边界曲线由一系列边界点组成。将边界点的坐标对 ( x k , y k ) (x_k, y_k) (xk,yk) 视为复数,记为:
s ( k ) = x ( k ) + j ∗ y ( k ) , k = 0 , . . . K − 1 s(k) = x(k) + j * y(k), \quad k=0,...K-1 s(k)=x(k)+jy(k),k=0,...K1
于是可以将边界点的集合看作一维信号,并具有有限性和周期性,其一维离散傅里叶变换为:
a ( u ) = ∑ k = 0 K − 1 s ( k ) e − j 2 π u k / K , u = 0 , . . . K − 1 a(u) = \sum _{k=0} ^{K-1} s(k) e^{-j 2\pi uk/K}, \quad u=0,...K-1 a(u)=k=0K1s(k)ej2πuk/K,u=0,...K1

式中的复数系数 a ( u ) a(u) a(u) 称为边界的傅里叶描述子。

显然,通过这些系数 a ( u ) a(u) a(u) 的傅里叶反变换,就可以恢复原始信号 s ( k ) s(k) s(k) ,也即恢复目标的边界点集的坐标:
s ( k ) = 1 K ∑ u = 0 P − 1 a ( u ) e j 2 π u k / K , k = 0 , . . . K − 1 s(k) = \frac{1}{K} \sum _{u=0} ^{P-1} a(u) e^{j 2\pi uk/K}, \quad k=0,...K-1 s(k)=K1u=0P1a(u)ej2πuk/K,k=0,...K1
如果使用全部 K 个系数 a ( u ) , u = 0 , . . . K − 1 a(u),u=0,...K-1 a(u),u=0,...K1,则傅里叶反变换等于原输入信号 s ( k ) s(k) s(k) ;如果仅取前 P 个系数 a ( u ) , u = 0 , . . . P − 1 a(u),u=0,...P-1 a(u),u=0,...P1,则傅里叶反变换是原输入信号的近似,

由于傅里叶变换的低频分量决定整体形状,高频分量决定形状的细节。因此,仅取前 P 个系数 a ( u ) a(u) a(u) 相当于保留低频系数、删除高频系数的理想低通滤波器进行滤波。用少量低频的傅里叶描述子就可以描述目标边界曲线形状的基本特征。

边界曲线数组是一维复数数组,可以将其视为列数为 1 的二维复数图像,借用二维图像傅里叶变换算法(DFT)进行处理。

二维傅里叶变换(DFT)的周期性要求,在对傅里叶变换进行滤波之前,要先将傅里叶变换乘以 ( − 1 ) x (-1)^x (1)x 进行中心化,傅里叶逆变换后再乘以 ( − 1 ) x (-1)^x (1)x 进行去中心化。

扫描二维码关注公众号,回复: 14388983 查看本文章

二维傅里叶变换(DFT)的对称性要求,边界曲线及反变换复原曲线的点数必须是偶数。如果边界曲线的点数是奇数,可以在数组的最后添加一个点(如起点),形成首尾相同的偶数点集。另外,在低频滤波,截取低频傅里叶描述子时,保留的项数也要是偶数。

下面的例程表明,使用约 1% 的傅里叶描述子,就可以较好地描述边界曲线的基本形状。


例程 14.4:傅里叶描述子

    #  14.4 特征提取之傅里叶描述子
    def truncFFT(fftCnt, pLowF=64):  # 截短傅里叶描述子
        fftShift = np.fft.fftshift(fftCnt)  # 中心化,将低频分量移动到频域中心
        center = int(len(fftShift)/2)
        low, high = center - int(pLowF/2), center + int(pLowF/2)
        fftshiftLow = fftShift[low:high]
        fftLow = np.fft.ifftshift(fftshiftLow)  # 逆中心化
        return fftLow

    def reconstruct(img, fftCnt, scale, ratio=1.0):  # 由傅里叶描述子重建轮廓图
        pLowF = int(fftCnt.shape[0] * ratio)  # 截短长度 P<=K
        fftLow = truncFFT(fftCnt, pLowF)  # 截短傅里叶描述子,删除高频系数
        ifft = np.fft.ifft(fftLow)  # 傅里叶逆变换 (P,)
        # cntRebuild = np.array([ifft.real, ifft.imag])  # 复数转为数组 (2, P)
        # cntRebuild = np.transpose(cntRebuild)  # (P, 2)
        cntRebuild = np.stack((ifft.real, ifft.imag), axis=-1)  # 复数转为数组 (P, 2)
        if cntRebuild.min() < 0:
            cntRebuild -= cntRebuild.min()
        cntRebuild *= scale / cntRebuild.max()
        cntRebuild = cntRebuild.astype(np.int32)
        print("ratio={}, fftCNT:{}, fftLow:{}".format(ratio, fftCnt.shape, fftLow.shape))

        rebuild = np.ones(img.shape, np.uint8)*255  # 创建空白图像
        cv2.rectangle(rebuild, (2,3), (568,725), (0,0,0), )  # 绘制边框
        cv2.polylines(rebuild, [cntRebuild], True, 0, thickness=2)  # 绘制多边形,闭合曲线
        return rebuild

    # 特征提取之傅里叶描述子
    img = cv2.imread("../images/chromosome.tif", flags=1)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 灰度图像
    _, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY)
    print(gray.shape)  # (727, 570)

    # 寻找二值化图中的轮廓,method=cv2.CHAIN_APPROX_NONE 输出轮廓的每个像素点
    contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)  # OpenCV4~
    cnts = sorted(contours, key=cv2.contourArea, reverse=True)  # 所有轮廓按面积排序
    cnt = cnts[0]  # 第 0 个轮廓,面积最大的轮廓,(664, 1, 2)
    cntPoints = np.squeeze(cnt)  # 删除维度为 1 的数组维度,(2867, 1, 2)->(2867,2)
    lenCnt = cnt.shape[0]  # 轮廓点的数量
    print("length of max contour:", lenCnt)
    imgCnts = np.zeros(gray.shape[:2], np.uint8)  # 创建空白图像
    cv2.drawContours(imgCnts, cnt, -1, (255, 255, 255), 2)  # 绘制轮廓

    # 离散傅里叶变换,生成傅里叶描述子 fftCnt
    cntComplex = np.empty(cntPoints.shape[0], dtype=complex)  # 声明复数数组 (2867,)
    cntComplex = cntPoints[:,0] + 1j * cntPoints[:,1]  # (xk,yk)->xk+j*yk
    # print("cntComplex", cntComplex.shape)
    fftCnt = np.fft.fft(cntComplex)  # 离散傅里叶变换,生成傅里叶描述子

    # 由全部傅里叶描述子重建轮廓曲线
    scale = cntPoints.max()  # 尺度系数
    rebuild = reconstruct(img, fftCnt, scale)  # 傅里叶逆变换重建轮廓曲线,傅里叶描述子 (2866,)
    # 由截短傅里叶系数重建轮廓曲线
    rebuild1 = reconstruct(img, fftCnt, scale, ratio=0.2)  # 截短比例 20%,傅里叶描述子 (572,)
    rebuild2 = reconstruct(img, fftCnt, scale, ratio=0.05)  # 截短比例 5%,傅里叶描述子 (142,)
    rebuild3 = reconstruct(img, fftCnt, scale, ratio=0.02)  # 截短比例 2%,傅里叶描述子 (56,)

    plt.figure(figsize=(9, 6))
    plt.subplot(231), plt.axis('off'), plt.title("Origin")
    plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
    plt.subplot(232), plt.axis('off'), plt.title("Contour")
    plt.imshow(cv2.cvtColor(imgCnts, cv2.COLOR_BGR2RGB))
    plt.subplot(233), plt.axis('off'), plt.title("rebuild (100%)")
    plt.imshow(cv2.cvtColor(rebuild, cv2.COLOR_BGR2RGB))
    plt.subplot(234), plt.axis('off'), plt.title("rebuild1 (20%)")
    plt.imshow(cv2.cvtColor(rebuild1, cv2.COLOR_BGR2RGB))
    plt.subplot(235), plt.axis('off'), plt.title("rebuild2 (5%)")
    plt.imshow(cv2.cvtColor(rebuild2, cv2.COLOR_BGR2RGB))
    plt.subplot(236), plt.axis('off'), plt.title("rebuild3 (2%)")
    plt.imshow(cv2.cvtColor(rebuild3, cv2.COLOR_BGR2RGB))
    plt.tight_layout()
    plt.show()

运行结果:

在这里插入图片描述

length of max contour: 2867
ratio=1.0, fftCNT:(2867,), fftLow:(2866,)
ratio=0.2, fftCNT:(2867,), fftLow:(572,)
ratio=0.05, fftCNT:(2867,), fftLow:(142,)
ratio=0.02, fftCNT:(2867,), fftLow:(56,)


【本节完】

版权声明:
youcans@xupt 原创作品,转载必须标注原文链接:(https://blog.csdn.net/youcans/article/details/125634022)
Copyright 2022 youcans, XUPT
Crated:2022-7-6

222. 特征提取之弗里曼链码
225. 特征提取之傅里叶描述子

猜你喜欢

转载自blog.csdn.net/youcans/article/details/125634022
今日推荐