草庐IT

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

liferecords 2023-03-28 原文
在这篇文章中,我们将展示如何使用Hu Moments进行形状匹配。您将学习以下内容 - 什么是图像矩?- 如何计算图像矩?- 什么是图像矩不变量(或胡时刻)?- 如何使用OpenCV计算图像的Hu图像矩?- 如何使用Hu图像矩来找到两个形状之间的相似性。 # 1 什么是图像矩?

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

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

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

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

对于二进制图像,可以以几种不同的方式解释上述矩: 1. 它是值白色像素的数量(即强度=255)。1. 它是代表图像中白色区域的面积。 到目前为止,您可能不会对图像矩留下深刻印象,但这里有一些有趣的东西。图1包含三个二进制图像S(S0.png),旋转S(S5.png)和K(K0.png)

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

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

2 如何计算图像矩

2.1 质心获取

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

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

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

其他信息查看:

2.2 中心矩

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

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

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

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

2.3 Hu矩

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

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

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

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]不具有可比性。我们可以使用下面给出的对数转换将它们放在相同的范围内

转换后的结果如下:

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矩。

如您所见,我们在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

2 CONTOURS_MATCH_I2

3 CONTOURS_MATCH_I3

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

是图像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 代码

代码地址:

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

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

有关[OpenCV实战]10 使用Hu矩进行形状匹配的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  7. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  8. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

  9. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  10. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

随机推荐