博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
5.11 用颜色和关键点对斑点进行分类
阅读量:696 次
发布时间:2019-03-21

本文共 6427 字,大约阅读时间需要 21 分钟。

我们的分类器基于这样的假设:一个斑点包含不同的颜色、不同的关键点,或者两者兼有。为了节省内存并预计算尽可能多的相关信息,我们不存储引用块的图像,而是存储直方图和关键点描述符。

创建一个新文件,BlobClassifier.cpp,用于实现我们的BlobClassifier类。(要查看头文件,请参阅"定义斑点描述符和斑点分类器"这一节。)在BlobClassifier.cpp的顶部,我们将定义几个常量,这些常量与直方图单元的数量、直方图比较方法以及直方图比较与关键点比较的相对重要性有关。以下是相关代码:

#include "BlobClassifier.hpp"#include 
#include "BlobClassifier.hpp"#ifdef WITH_OPENCV_CONTRIB#include
#endifconst int HISTOGRAM_NUM_BINS_PER_CHANNEL = 32;const int HISTOGRAM_COMPARISON_METHOD = cv::HISTCMP_CHISQR_ALT;const float HISTOGRAM_DISTANCE_WEIGHT = 0.98f;const float KEYPOINT_MATCHING_DISTANCE_WEIGHT = 1.0f - HISTOGRAM_DISTANCE_WEIGHT;

[注意,HISTOGRAM_NUM_BINS_PER_CHANNEL常量与BeanCounter的内存使用有立方关系。对于每一个斑点描述符,我们储存三维(BGR)直方图HISTOGRAM_NUM_BINS_PER_CHANNEL ^ 3元素,每个元素是一个32位浮点数。如果常数是32,每个直方图以兆字节为单位的大小可以达到(32 ^ 3)* 32 /(10 ^ 6)= 1.0。对于一小一点的引用描述符来说,这很好。如果常量是256(容器的最大数量为8位颜色通道),柱状图的大小会上升高达(256 ^ 3)* 32 /(10 ^ 6)= 536.9 mb !考虑到iOS应用程序的内存限制,这是不可接受的。

在高端iOS设备中,每个应用程序最多只能使用1gb的RAM。保守地说,如果应用程序的内存使用接近100兆字节,您应该考虑内存问题了。]

请记住,OpenCV的SURF实现位于xfeatures2d模块中,该模块是opencv_contrib的一部分。如果opencv_contrib可用(是由WITH_OPENCV_CONTRIB宏来确定的),我们将导入<opencv/ xfeatures2d.hpp>头文件和SURF。否则,我们使用ORB。此选择还会影响BlobClassifier的构造函数的实现。OpenCV为各种特征检测器、描述符和匹配器提供了工厂方法,因此我们只需使用工厂方法的正确组合来进行SURF与FLANN匹配,或者ORB与基于Hamming distance的brute-force匹配。下面是构造函数的实现:

BlobClassifier::BlobClassifier() : clahe(cv::createCLAHE())#ifdef WITH_OPENCV_CONTRIB    , featureDetectorAndDescriptorExtractor(cv::xfeatures2d::SURF::create()) , descriptorMatcher(cv::DescriptorMatcher::create("FlannBased"))#else    , featureDetectorAndDescriptorExtractor(cv::ORB::create()) , descriptorMatcher(cv::DescriptorMatcher::create("BruteForce-HammingLUT"))#endif{ }

update方法的实现调用了一个辅助方法’createBlobDescriptor`并且将得到的BlobDescriptoer对象添加到一个描述符引用的向量中去:

void BlobClassifier::update(const Blob &referenceBlob) {    referenceBlobDescriptors.push_back(createBlobDescriptor(referenceBlob));}

clear方法的实现是丢弃所有描述符的应用,将Blob分类器恢复到初始,没有训练过的状态:

void BlobClassifier::clear() {    referenceBlobDescriptors.clear();}

classify方法的实现依赖于另一个辅助方法findDistance.对于每一个引用的描述符来说,classify调用findDistance来得到被查询的斑点的描述符和引用的描述符之间的不同度量。我们找到距离最小(相似度最好)的参考描述符,并返回其标签作为分类结果。如果没有引用描述符,则classification返回0,未知的标签。下面是classification的实现:

void BlobClassifier::classify(Blob &detectedBlob) const {    BlobDescriptor detectedBlobDescriptor = createBlobDescriptor(detectedBlob);    float bestDistance = FLT_MAX;    uint32_t bestLabel = 0;    for (const BlobDescriptor &referenceBlobDescriptor :referenceBlobDescriptors) {        float distance = findDistance(detectedBlobDescriptor, referenceBlobDescriptor);        if (distance < bestDistance) {            bestDistance = distance;            bestLabel = referenceBlobDescriptor.getLabel();        }}    detectedBlob.setLabel(bestLabel);}

createBlobDescriptor辅助方法负责计算Blob的规范化直方图和关键点描述符,并使用它们构建新的BlobDescriptor。为了计算(非标准化)直方图,我们使用cv::calcHist函数。在它的参数中,它需要三个数组来指定我们要使用的通道、每个通道的容器数量和每个通道值的范围。为了使得到的直方图规范化,我们要除以blob图像中的像素数。下面的代码与直方图相关,是createBlobDescriptor实现的前半部分:

BlobDescriptor BlobClassifier::createBlobDescriptor( const Blob &blob) const {    const cv::Mat &mat = blob.getMat();    int numChannels = mat.channels();        // Calculate the histogram of the blob's image.    cv::Mat histogram;    int channels[] = { 0, 1, 2 };    int numBins[] = { HISTOGRAM_NUM_BINS_PER_CHANNEL,HISTOGRAM_NUM_BINS_PER_CHANNEL,HISTOGRAM_NUM_BINS_PER_CHANNEL };    float range[] = { 0.0f, 256.0f };    const float *ranges[] = { range, range, range };    cv::calcHist(&mat, 1, channels, cv::Mat(), histogram, 3,numBins, ranges);    // Normalize the histogram.    histogram *= (1.0f / (mat.rows * mat.cols));

[在计算颜色直方图之前,我们可以像在第1章“设置软件和硬件”中所做的那样调整白色平衡。这个额外的步骤可能使描述符在光照条件的变化方面更加健壮。另一方面,iOS相机系统已经对白平衡进行了评估,所以我们最好相信它的结果(而不应用这个附加的步骤)。然而这个请随意实验。]

现在,我们必须将blob图像转换成灰度,并使用cv::Feature2D的检测和计算方法获取关键点和关键点描述符。有了规范化的直方图和关键点描述符,我们就有了构建和返回新BlobDescriptor所需的一切。下面是createBlobDescriptor实现的剩余部分:

// Convert the blob's image to grayscale.    cv::Mat grayMat;    switch (numChannels) {        case 4:    cv::cvtColor(mat, grayMat, cv::COLOR_BGRA2GRAY);            break;        default:    cv::cvtColor(mat, grayMat, cv::COLOR_BGR2GRAY);            break;}// Adaptively equalize the grayscale image to enhance local    // contrast.clahe->apply(grayMat, grayMat);// Detect features in the grayscale image.    std::vector
keypoints; featureDetectorAndDescriptorExtractor->detect(grayMat, keypoints);// Extract descriptors of the features.cv::Mat keypointDescriptors; featureDetectorAndDescriptorExtractor->compute(grayMat, keypoints, keypointDescriptors);return BlobDescriptor(histogram, keypointDescriptors, blob.getLabel());}

findDistance辅助方法使用cv::compareHist函数进行直方图比较,使用cv::DescriptorMatcher匹配方法进行关键点匹配。得到的每个关键点匹配都有一个距离,我们将这些距离相加。然后,作为两个blob描述符之间距离的总体度量,我们返回直方图距离和总关键点匹配距离的加权平均值。以下是相关代码:

float BlobClassifier::findDistance( const BlobDescriptor &detectedBlobDescriptor, const BlobDescriptor &referenceBlobDescriptor) const {        // Calculate the histogram distance.    float histogramDistance = (float)cv::compareHist( detectedBlobDescriptor.getNormalizedHistogram(), referenceBlobDescriptor.getNormalizedHistogram(), HISTOGRAM_COMPARISON_METHOD);        // Calculate the keypoint matching distance.    float keypointMatchingDistance = 0.0f;    std::vector
keypointMatches; descriptorMatcher->match(detectedBlobDescriptor.getKeypointDescriptors(),referenceBlobDescriptor.getKeypointDescriptors(),keypointMatches); for (const cv::DMatch &keypointMatch : keypointMatches) { keypointMatchingDistance += keypointMatch.distance; } return histogramDistance * HISTOGRAM_DISTANCE_WEIGHT + keypointMatchingDistance * KEYPOINT_MATCHING_DISTANCE_WEIGHT;}

这就是斑点分类器代码的结尾。我们再次看到,单个类可以提供有用的通用计算机视觉功能,而无需非常复杂的实现。也许这是一个禅的时刻;我们的工作是一条通往(某种)简单的道路!当然,OpenCV在实现与直方图相关的函数和与关键点相关的类时为我们隐藏了很多复杂性,通过这种方式,这个库为我们提供了一条相对温和的路径。

为了完整起见,请注意BlobDescriptor类有一个简单的实现。创建一个新文件BlobDescriptor.cpp,并用以下代码填充构造函数和getter:

#include "BlobDescriptor.hpp"BlobDescriptor::BlobDescriptor(const cv::Mat &normalizedHistogram, const cv::Mat &keypointDescriptors, uint32_t label) : normalizedHistogram(normalizedHistogram) , keypointDescriptors(keypointDescriptors) , label(label) { }const cv::Mat &BlobDescriptor::getNormalizedHistogram() const {    return normalizedHistogram;}const cv::Mat &BlobDescriptor::getKeypointDescriptors() const {    return keypointDescriptors;}uint32_t BlobDescriptor::getLabel() const {        return label;}

现在,我们已经完成了BeanCounter的所有代码!

转载地址:http://lqxez.baihongyu.com/

你可能感兴趣的文章