百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

[OpenCV实战]10 使用Hu矩进行形状匹配

bigegpt 2024-09-12 11:27 8 浏览

使用Hu矩进行形状匹配

在这篇文章中,我们将展示如何使用Hu Moments进行形状匹配。您将学习以下内容

?什么是图像矩??如何计算图像矩??什么是图像矩不变量(或者Hu时刻)??如何使用OpenCV计算图像的Hu图像矩??如何使用Hu图像矩来找到两个形状之间的相似性。

1 什么是图像矩?

图像矩是图像像素强度的加权平均值。让我们选择一个简单的例子来理解。

为简单起见,我们考虑单通道二进制图像 I 。位置处的像素强度(X,Y)为 I (X,Y)。二进制图像的I(X,Y)可以取值0或255。

最简单的图像矩可以这样计算:

添加图片注释,不超过 140 字(可选)

我们在上面的等式中所做的就是计算所有像素值的总和。换句话说,所有图像矩仅基于它们的值加权,而不是基于它们在图像中的位置。

对于二进制图像,可以以几种不同的方式解释上述矩:

1.它是值白色像素的数量(即强度=255)。2.它是代表图像中白色区域的面积。

到目前为止,您可能不会对图像矩留下深刻印象,但这里有一些有趣的东西。图1包含三个二进制图像S(S0.png),旋转S(S5.png)和K(K0.png)

添加图片注释,不超过 140 字(可选)

S图像和旋转S图像的图像矩非常接近,K的矩就大大不同。

对于两个相同的形状,上面的图像矩必然是相同的,但它不是一个充分的条件。我们可以很容易地构建两个图像,其中图像矩相同的,但它们看起来非常不同。

2 如何计算图像矩

2.1 质心获取

让我们看看一些更复杂的矩。

添加图片注释,不超过 140 字(可选)

i和j是整数。这种矩通常被称为图像几何矩,以区别于本文后面提到的中心矩。请注意,上述矩取决于像素的强度及其在图像中的位置。如此直观地说,这些矩正在捕捉一些形状的信息。

我们可以通过图像矩计算质心。使用以下公式计算质心:

添加图片注释,不超过 140 字(可选)

添加图片注释,不超过 140 字(可选)

其他信息查看:

https://blog.csdn.net/LuohenYJ/article/details/88599334

2.2 中心矩

中心矩非常类似于我们之前看到的几何矩,在几何矩的基础上我们需要减去质心坐标。

添加图片注释,不超过 140 字(可选)

请注意,上述中心矩是具有平移不变性的。换句话说,无论图像中的blob在哪里,如果形状相同,则中心矩是不变的。

如果我们还能让这个矩具有不变性,那会不会很酷?那么为此我们需要在中心矩添加标准化,得到中心归一化矩。如下所示。

添加图片注释,不超过 140 字(可选)

中心矩是平移不变的,中心归一化矩是平移和尺度不变的。三种矩总结如下:

添加图片注释,不超过 140 字(可选)

https://images2015.cnblogs.com/blog/934484/201707/934484-20170713101542165-2053888115.png

2.3 Hu矩

中心矩具有很不错的特性,但是不足以用于特征匹配。我们想要计算对平移,缩放和旋转不变的矩,幸运的是,我们实际上可以计算出这样的矩,他们被称为Hu矩的7个不变量。如下图所示。

添加图片注释,不超过 140 字(可选)

Hu矩(或者更确切地说是Hu矩不变量)是使用对图像变换不变的中心矩计算的一组7个变量。事实证明,前6个矩不变量对于平移,缩放,旋转和映射都是不变的。而第7个矩会因为图像映射而改变。

Hu矩的理论你可参考论文:

https://www.researchgate.net/publication/224146066_Analysis_of_Hu's_moment_invariants_on_image_scaling_and_rotation

3 基于Hu矩实现形状匹配

3.1 Hu矩的计算

幸运的是,我们不需要在OpenCV中进行所有计算,因为我们有计算Hu矩的函数。在OpenCV中,我们HuMoments()用来计算输入图像中的Hu矩。

(1)我们先读取原图并将其转换为灰度图像

C++:


// Read image as grayscale image

Mat im = imread(filename,IMREAD_GRAYSCALE);

Python:


# Threshold image

_,im = cv2.threshold(im, 128, 255, cv2.THRESH_BINARY)

(2)使用阈值处理对图像进行二值化:

C++:


// Threshold image 阈值分割

threshold(im, im, 0, 255, THRESH_OTSU);

Python:


# Threshold image

_,im = cv2.threshold(im, 128, 255, cv2.THRESH_BINARY)

(3)基于OpenCV先计算图像中心矩,再计算图像Hu矩

C++:


// Calculate Moments

Moments moments = moments(im, false);

// Calculate Hu Moments

double huMoments[7];

HuMoments(moments, huMoments);

Python:


# Calculate Moments

moments = cv2.moments(im)

# Calculate Hu Moments

huMoments = cv2.HuMoments(moments)

(4)在前一步骤中获得的Hu矩变化过大。例如k图像的Hu矩为:


h[0] = 0.00162663

h[1] = 3.11619e-07

h[2] = 3.61005e-10

h[3] = 1.44485e-10

h[4] = -2.55279e-20

h[5] = -7.57625 e-14

h[6] = 2.09098e-20

请注意,hu [0]的大小与hu [6]不具有可比性。我们可以使用下面给出的对数转换将它们放在相同的范围内

添加图片注释,不超过 140 字(可选)

转换后的结果如下:


H[0] = 2.78871

H[1] = 6.50638

H[2] = 9.44249

H[3] = 9.84018

H[4] = -19.593

H[5] = -13.1205

H[6] = 19.6797

转换代码为:

C++:


// Log scale hu moments

for(int i = 0; i < 7; i++)

{

  huMoments[i] = -1 * copysign(1.0, huMoments[i]) * log10(abs(huMoments[i])); 

}

Python:


# Log scale hu moments

for i in range(0,7):

  huMoments[i] = -1* copysign(1.0, huMoments[i]) * log10(abs(huMoments[i])))

其中copysign函数的意思是将函数第一个变量的符号设置成第二个变量的正负数符号,然后输出第一个变量。例如若第二个变量为负数,则上式1变为负数-1,输出-1。

3.2 基于matchShapes函数计算两个图形之间的距离

如前所述,所有7个Hu矩不变量不管图像缩放和旋转都是不变的。只有映射时比如图像翻转,那么第七个Hu矩正负符号就会变化。那不是很漂亮吗?

我们来看一个例子。在下表中我们有6张图片和他们的Hu矩。

添加图片注释,不超过 140 字(可选)

如您所见,我们在S1.png中移动字母S,并在S2.png中移动+缩放它。我们添加了一些旋转来制作S3.png并进一步翻转图像以制作S4.png。注意,S0,S1,S2,S3和S4的所有Hu矩在值上彼此接近,除了翻转S4的第七个Hu矩的符号。另外,请注意它们与K非常不同。

在本节中,我们将学习如何使用Hu Moments来找到两个形状之间的距离。如果距离小,则两个图形在外观上接近。

OpenCV提供了一个易于使用的名为matchShapes函数,它接收两个图像(或轮廓)并使用Hu矩找到它们之间的距离。所以,你只需将图像二值化并使用matchShapes即可。

用法如下所示

C++:


double d1 = matchShapes(im1, im2, CONTOURS_MATCH_I1, 0);

double d2 = matchShapes(im1, im2, CONTOURS_MATCH_I2, 0);

double d3 = matchShapes(im1, im2, CONTOURS_MATCH_I3, 0);

Python:


d1 = cv2.matchShapes(im1,im2,cv2.CONTOURS_MATCH_I1,0)

d2 = cv2.matchShapes(im1,im2,cv2.CONTOURS_MATCH_I2,0)

d3 = cv2.matchShapes(im1,im2,cv2.CONTOURS_MATCH_I3,0)

请注意,您可以通过第三个参数(CONTOURS_MATCH_I1,CONTOURS_MATCH_I2或CONTOURS_MATCH_I3)使用三种b不同的距离。如果上述距离很小,则两个图像(im1和im2)相似。您可以使用任何距离测量。它们通常产生类似的结果。个人喜欢第二种,因为好计算。

三种距离具体计算如下:

1 CONTOURS_MATCH_I1

添加图片注释,不超过 140 字(可选)

2 CONTOURS_MATCH_I2

添加图片注释,不超过 140 字(可选)

3 CONTOURS_MATCH_I3

添加图片注释,不超过 140 字(可选)

添加图片注释,不超过 140 字(可选)

是图像A和B之间的距离,

添加图片注释,不超过 140 字(可选)

添加图片注释,不超过 140 字(可选)

是图像A和B第i个Hu矩对数转换后的值。

当我们在图像上使用形状匹配时,如S0与S0,K0和S4,我们得到以下输出:


S0和S0:0.0

S0和K0:0.10783054664091285

S0和S4:0.008484870268973932

如果您想在两个形状之间自定义距离。例如,您可能希望使用由给定的Hu Moments之间的欧几里德距离。首先,如前一节所述,计算对数变换的Hu矩,然后自己计算距离,而不是使用matchShapes。

4 代码

代码地址:

https://github.com/luohenyueji/OpenCV-Practical-Exercise

https://download.csdn.net/download/luohenyj/11026231

如果没有积分(系统自动设定资源分数)看看参考链接。我搬运过来的,大修改没有。

4.1 Hu矩计算

C++:


#include "pch.h"
#include <iostream>
#include "opencv2/opencv.hpp"

using namespace cv;
using namespace std;

int main()
{
    //是否进行log转换
    bool showLogTransformedHuMoments = true;

    // Obtain filename 图像地址
    string filename("./image/s0.png");

    // Read Image 读图
    Mat im = imread(filename, IMREAD_GRAYSCALE);

    // Threshold image 阈值分割
    threshold(im, im, 0, 255, THRESH_OTSU);

    // Calculate Moments 计算矩
    //第二个参数True表示非零的像素都会按值1对待,也就是说相当于对图像进行了二值化处理,阈值为1
    Moments moment = moments(im, false);

    // Calculate Hu Moments 计算Hu矩
    double huMoments[7];
    HuMoments(moment, huMoments);

    // Print Hu Moments
    cout << filename << ": ";

    for (int i = 0; i < 7; i++)
    {
        if (showLogTransformedHuMoments)
        {
            // Log transform Hu Moments to make squash the range
            cout << -1 * copysign(1.0, huMoments[i]) * log10(abs(huMoments[i])) << " ";
        }
        else
        {
            // Hu Moments without log transform.
            cout << huMoments[i] << " ";
        }
    }
    // One row per file
    cout << endl;
}

Python:


from math import copysign, log10

def main():
    showLogTransformedHuMoments = True

    # Obtain filename from command line argument
    filename = './image/s0.png'

    # Read image
    im = cv2.imread(filename,cv2.IMREAD_GRAYSCALE)

    # Threshold image
    _,im = cv2.threshold(im, 128, 255, cv2.THRESH_BINARY)

    # Calculate Moments
    moment = cv2.moments(im)

    # Calculate Hu Moments
    huMoments = cv2.HuMoments(moment)

    # Print Hu Moments
    print("{}: ".format(filename),end='')

    for i in range(0,7):
        if showLogTransformedHuMoments:
            # Log transform Hu Moments to make
            # squash the range
            print("{:.5f}".format(-1*copysign(1.0,\
                    huMoments[i])*log10(abs(huMoments[i]))),\
                    end=' ')
        else:
            # Hu Moments without log transform
            print("{:.5f}".format(huMoments[i]),end=' ')
    print()

if __name__ == "__main__":
    main()

4.2 形状匹配

C++:


#include "pch.h"
#include "opencv2/opencv.hpp"

using namespace cv;
using namespace std;

int main()
{
  Mat im1 = imread("./image/S0.png",IMREAD_GRAYSCALE);
  Mat im2 = imread("./image/K0.png",IMREAD_GRAYSCALE);
  Mat im3 = imread("./image/S4.png",IMREAD_GRAYSCALE);

  double m1 = matchShapes(im1, im1, CONTOURS_MATCH_I2, 0);
  double m2 = matchShapes(im1, im2, CONTOURS_MATCH_I2, 0);
  double m3 = matchShapes(im1, im3, CONTOURS_MATCH_I2, 0);

  cout << "Shape Distances Between " << endl << "-------------------------" << endl;
  cout << "S0.png and S0.png : " << m1 << endl;
  cout << "S0.png and K0.png : " << m2 << endl;
  cout << "S0.png and S4.png : " << m3 << endl;
}

Python:


import cv2

def main():

    im1 = cv2.imread("./image/S0.png",cv2.IMREAD_GRAYSCALE)
    im2 = cv2.imread("./image/K0.png",cv2.IMREAD_GRAYSCALE)
    im3 = cv2.imread("./images/S4.png",cv2.IMREAD_GRAYSCALE)

    m1 = cv2.matchShapes(im1,im1,cv2.CONTOURS_MATCH_I2,0)
    m2 = cv2.matchShapes(im1,im2,cv2.CONTOURS_MATCH_I2,0)
    m3 = cv2.matchShapes(im1,im3,cv2.CONTOURS_MATCH_I2,0)

    print("Shape Distances Between \n-------------------------")

    print("S0.png and S0.png : {}".format(m1))
    print("S0.png and K0.png : {}".format(m2))
    print("S0.png and S4.png : {}".format(m3))

if __name__ == "__main__":
    main()

5 参考

https://www.learnopencv.com/shape-matching-using-hu-moments-c-python/


引用链接

[1] https://blog.csdn.net/LuohenYJ/article/details/88599334: https://blog.csdn.net/LuohenYJ/article/details/88599334

[2] https://www.researchgate.net/publication/224146066_Analysis_of_Hu's_moment_invariants_on_image_scaling_and_rotation: https://www.researchgate.net/publication/224146066_Analysis_of_Hu's_moment_invariants_on_image_scaling_and_rotation

[3] https://github.com/luohenyueji/OpenCV-Practical-Exercise : https://github.com/luohenyueji/OpenCV-Practical-Exercise

[4] https://download.csdn.net/download/luohenyj/11026231 : https://download.csdn.net/download/luohenyj/11026231

[5] https://www.learnopencv.com/shape-matching-using-hu-moments-c-python/: https://www.learnopencv.com/shape-matching-using-hu-moments-c-python/

相关推荐

当Frida来“敲”门(frida是什么)

0x1渗透测试瓶颈目前,碰到越来越多的大客户都会将核心资产业务集中在统一的APP上,或者对自己比较重要的APP,如自己的主业务,办公APP进行加壳,流量加密,投入了很多精力在移动端的防护上。而现在挖...

服务端性能测试实战3-性能测试脚本开发

前言在前面的两篇文章中,我们分别介绍了性能测试的理论知识以及性能测试计划制定,本篇文章将重点介绍性能测试脚本开发。脚本开发将分为两个阶段:阶段一:了解各个接口的入参、出参,使用Python代码模拟前端...

Springboot整合Apache Ftpserver拓展功能及业务讲解(三)

今日分享每天分享技术实战干货,技术在于积累和收藏,希望可以帮助到您,同时也希望获得您的支持和关注。架构开源地址:https://gitee.com/msxyspringboot整合Ftpserver参...

Linux和Windows下:Python Crypto模块安装方式区别

一、Linux环境下:fromCrypto.SignatureimportPKCS1_v1_5如果导包报错:ImportError:Nomodulenamed'Crypt...

Python 3 加密简介(python des加密解密)

Python3的标准库中是没多少用来解决加密的,不过却有用于处理哈希的库。在这里我们会对其进行一个简单的介绍,但重点会放在两个第三方的软件包:PyCrypto和cryptography上,我...

怎样从零开始编译一个魔兽世界开源服务端Windows

第二章:编译和安装我是艾西,上期我们讲述到编译一个魔兽世界开源服务端环境准备,那么今天跟大家聊聊怎么编译和安装我们直接进入正题(上一章没有看到的小伙伴可以点我主页查看)编译服务端:在D盘新建一个文件夹...

附1-Conda部署安装及基本使用(conda安装教程)

Windows环境安装安装介质下载下载地址:https://www.anaconda.com/products/individual安装Anaconda安装时,选择自定义安装,选择自定义安装路径:配置...

如何配置全世界最小的 MySQL 服务器

配置全世界最小的MySQL服务器——如何在一块IntelEdison为控制板上安装一个MySQL服务器。介绍在我最近的一篇博文中,物联网,消息以及MySQL,我展示了如果Partic...

如何使用Github Action来自动化编译PolarDB-PG数据库

随着PolarDB在国产数据库领域荣膺桂冠并持续获得广泛认可,越来越多的学生和技术爱好者开始关注并涉足这款由阿里巴巴集团倾力打造且性能卓越的关系型云原生数据库。有很多同学想要上手尝试,却卡在了编译数据...

面向NDK开发者的Android 7.0变更(ndk android.mk)

订阅Google官方微信公众号:谷歌开发者。与谷歌一起创造未来!受Android平台其他改进的影响,为了方便加载本机代码,AndroidM和N中的动态链接器对编写整洁且跨平台兼容的本机...

信创改造--人大金仓(Kingbase)数据库安装、备份恢复的问题纪要

问题一:在安装KingbaseES时,安装用户对于安装路径需有“读”、“写”、“执行”的权限。在Linux系统中,需要以非root用户执行安装程序,且该用户要有标准的home目录,您可...

OpenSSH 安全漏洞,修补操作一手掌握

1.漏洞概述近日,国家信息安全漏洞库(CNNVD)收到关于OpenSSH安全漏洞(CNNVD-202407-017、CVE-2024-6387)情况的报送。攻击者可以利用该漏洞在无需认证的情况下,通...

Linux:lsof命令详解(linux lsof命令详解)

介绍欢迎来到这篇博客。在这篇博客中,我们将学习Unix/Linux系统上的lsof命令行工具。命令行工具是您使用CLI(命令行界面)而不是GUI(图形用户界面)运行的程序或工具。lsoflsof代表&...

幻隐说固态第一期:固态硬盘接口类别

前排声明所有信息来源于网络收集,如有错误请评论区指出更正。废话不多说,目前固态硬盘接口按速度由慢到快分有这几类:SATA、mSATA、SATAExpress、PCI-E、m.2、u.2。下面我们来...

新品轰炸 影驰SSD多款产品登Computex

分享泡泡网SSD固态硬盘频道6月6日台北电脑展作为全球第二、亚洲最大的3C/IT产业链专业展,吸引了众多IT厂商和全球各地媒体的热烈关注,全球存储新势力—影驰,也积极参与其中,为广大玩家朋友带来了...