Learning Opencv 3 —— 十四章 轮廓匹配
关于 Opencv 轮廓的介绍可以参考:https://blog.csdn.net/a40850273/article/details/88063478
Moments
Moments 是一种轮廓、图像和点的高层次特征,计算方式如下
其能够理解为对图像中每个像素点的加权和,如果 ,即
,则每个像素点的权重都是 1。如果对于一幅二值图像(像素值不是 1 就是 0),那么
就是非零像素值的面积。如果一个轮廓,那么
就是轮廓的长度。同理,如果用
和
除以
,则表示图像在 x 和 y 方向的均值。
cv::moments() 用于计算一幅图像的 Moments
cv::Moments cv::moments( // Return structure contains moments
cv::InputArray points, // 2-dimensional points or an "image"
bool binaryImage = false // false='interpret image values as "mass"'
)
参数介绍:
- points:可以是一个二维数组(一幅图像),也可以是一堆点(一个轮廓)
- binaryImage:如果为 True,所有的非零像素值将被视为 1
不过以上求得的 Moments 会因为对轮廓进行放缩和旋转而发生改变。
满足平移不变性的中心 Moments
对于一幅图像或轮廓, 具有平移不变性。但是更高阶的 Moments 将不再具有该特征。中心 Moments 定义如下
,其中
,
通过对每个像素点对于其中心进行解决成功满足了平移不变性。其中,也可以很显然地得到 。
同时满足缩放不变性的归一化中心 Moments
为了进一步引入缩放不变性,引入归一化中心 Moments,定义如下
满足旋转不变性的 Hu invariant moments
Hu invariant moments 通过归一化中心 Moments 进行线性组合,从而实现对于缩放,旋转和反射(反射 并不满足)的不变性。
下面给出一个计算范例
void cv::HuMoments(
const cv::Moments& moments, // Input is result from cv::moments() function
double* hu // Return is C-style array of 7 Hu moments
);
cv::HuMoments() 通过传入一个 cv::Moments 的对象计算出上面给出的 7 个 hu moments。
使用 Hu Moments 进行匹配
cv::matchShapes() 基于提供的两个目标,自动计算他们的 Moments,最后基于用户给出的标准进行比较。
double cv::MatchShapes(
cv::InputArray object1, // First array of 2D points or cv:U8C1 image
cv::InputArray object2, // Second array of 2D points or cv:U8C1 image
int method, // Comparison method (Table 14-4)
double parameter = 0 // Method-specific parameter
);
参数说明:
- object1,object2:输入的两个目标,必须为灰度图像或者轮廓
- method:匹配方法包括一下三种,并且不同的 method 将影响最后返回的匹配度
其中,
- parameter:当前的算法并没有使用,可以简单地使用初值。这个参数主要是为了适配将来新增的 method 可能使用到自定义参数
使用形状上下文比较形状
使用 Moments 进行形状匹配可以追溯到 80 年代。同时最新的算法不断出现,不过由于当前的 Shape 模块还在开发中,因此这里将只简要介绍一下高层接口。
Shape 模块的结构
Shape 的构建基于一个抽象类 cv::ShapeDistanceExtractor。其返回一个非负数,如果两个形状完全相同,将返回 0。
class ShapeContextDistanceExtractor : public ShapeDistanceExtractor {
public:
...
virtual float computeDistance( InputArray contour1, InputArray contour2 ) = 0;
};
而具体的形状距离提取类将派生自这个基类 cv::ShapeDistanceExtractor。这里简要介绍其中的两个 cv::ShapeTransformer 和 cv::HistogramCostExtractor。
class ShapeTransformer : public Algorithm {
public:
virtual void estimateTransformation(
cv::InputArray transformingShape,
cv::InputArray targetShape,
vector<cv::DMatch>& matches
) = 0;
virtual float applyTransformation(
cv::InputArray input,
cv::OutputArray output = noArray()
) = 0;
virtual void warpImage(
cv::InputArray transformingImage,
cv::OutputArray output,
int flags = INTER_LINEAR,
int borderMode = BORDER_CONSTANT,
const cv::Scalar& borderValue = cv::Scalar()
) const = 0;
};
class HistogramCostExtractor : public Algorithm {
public:
virtual void buildCostMatrix(
cv::InputArray descriptors1,
cv::InputArray descriptors2,
cv::OutputArray costMatrix
)
= 0;
virtual void setNDummies( int nDummies ) = 0;
virtual int getNDummies() const = 0;
virtual void setDefaultCost( float defaultCost ) = 0;
virtual float getDefaultCost() const = 0;
};
shape transformer 表示了从一堆点到另一堆点再映射算法的类,其中仿射变换和透视变换都可以用形状变换实现(在 Opencv 中 cv::ThinPlateSplineShapeTransformer)。
histogram cost extractor 映射直方图中一个格到另一个格的 shoveling dirt 为一个代价。常用的派生类如下
对于每一个 extractors 和 transformers 都存在一个工厂方法(createX()),比如 cv::createChiHistogramCostExtractor()。
shape context distance extractor
namespace cv {
class ShapeContextDistanceExtractor : public ShapeDistanceExtractor {
public:
...
virtual float computeDistance(
InputArray contour1,
InputArray contour2
) = 0;
};
Ptr<ShapeContextDistanceExtractor> createShapeContextDistanceExtractor(
int nAngularBins = 12,
int nRadialBins = 4,
float innerRadius = 0.2f,
float outerRadius = 2,
int iterations = 3,
const Ptr<HistogramCostExtractor> &comparer
= createChiHistogramCostExtractor(),
const Ptr<ShapeTransformer> &transformer
= createThinPlateSplineShapeTransformer()
);
}
本质上 Shape Context algorithm 计算两个或更多的待比较物体的表征。每一个表征基于形状边缘的一系列子级点,同时对于每一个采样点,它构造一个确定的直方图来反映从该点角度在极坐标系下的形状。所有的直方图具有相同的大小 nAngularBins * nRadialBins。两个待匹配物体上的点基于 chi-squared 计算距离。然后算法计算两个待匹配物体最优的 1:1 点匹配从而得到最小的 chi-squared 距离总和。这个算法不算快,不过能够提供一个相对不错的结果。
#include "opencv2/opencv.hpp"
#include <algorithm>
#include <iostream>
#include <string>
using namespace std;
using namespace cv;
static vector<Point> sampleContour( const Mat& image, int n=300 ) {
vector<vector<Point> > _contours;
vector<Point> all_points;
findContours(image, _contours, RETR_LIST, CHAIN_APPROX_NONE);
for (size_t i=0; i <_contours.size(); i++) {
for (size_t j=0; j <_contours[i].size(); j++)
all_points.push_back( _contours[i][j] );
// If too little points, replicate them
//
int dummy=0;
for (int add=(int)all_points.size(); add<n; add++)
all_points.push_back(all_points[dummy++]);
// Sample uniformly
random_shuffle(all_points.begin(), all_points.end());
vector<Point> sampled;
for (int i=0; i<n; i++)
sampled.push_back(all_points[i]);
return sampled;
}
int main(int argc, char** argv) {
string path = "../data/shape_sample/";
int indexQuery = 1;
Ptr<ShapeContextDistanceExtractor> mysc = createShapeContextDistanceExtractor();
Size sz2Sh(300,300);
Mat img1=imread(argv[1], IMREAD_GRAYSCALE);
Mat img2=imread(argv[2], IMREAD_GRAYSCALE);
vector<Point> c1 = sampleContour(img1);
vector<Point> c2 = sampleContour(img2);
float dis = mysc->computeDistance( c1, c2 );
cout << "shape context distance between " <<
argv[1] << " and " << argv[2] << " is: " << dis << endl;
return 0;
}
Hausdorff distance extractor
类似于 Shape Context distance,Hausdorff distance 基于 cv::ShapeDistanceExtractor 接口给出了另一种形状不相似度的度量。
Hausdorff distance 首先对于一张图像中的每个点找出另一幅图像上的最近点,而其中最大距离就是 Hausdorff distance。不过 Hausdorff distance 不是对称的(不过可以通过某些操作实现对称)。
,其中
Hausdorff distance extractor 可以通过工厂方法 cv::createHausdorffDistanceExtractor() 产生。
cv::Ptr<cv::HausdorffDistanceExtractor> cv::createHausdorffDistanceExtractor(
int distanceFlag = cv::NORM_L2,
float rankProp = 0.6
);
同时 cv::HausdorffDistanceExtractor 和 Shape Context distance extractor 具有相同的接口,因此同样可以使用 cv::computeDistance() 计算对比目标的距离。