5.10 在简单的背景下检测斑点

文章目录

  • 5.10 在简单的背景下检测斑点

5.10 在简单的背景下检测斑点

让我们假设背景有一个特别的颜色范围,例如“奶油到雪之间的白色”。我们的斑点检测器将计算图像的主色范围,并搜索颜色与此范围不同的大区域。这些异常区域将构成检测到的斑点。

[对于像豆子或硬币这样的小物件,用户可以很容易地找到一个简单的背景,比如一张白纸、一张简单的桌面、一件简单的衣服,甚至是手掌。斑点检测器可以动态估计背景颜色范围,可以适应各种背景和光照条件;它并不局限于实验室环境。]

创建一个新文件"BlobDetector.cpp",用于实现我们的BlobDetector类。(要查看头文件,请参阅之前"定义斑点描述符和斑点分类器"这一节。)在BlobDetector.cpp开头,我们将定义几个常量,这些常量与背景颜色范围的宽度、斑点的大小和平滑程度以及预览图像中斑点矩形的颜色有关。以下是相关代码:


#include "BlobDetector.hpp"
#include 


const double MASK_STD_DEVS_FROM_MEAN = 1.0;
const double MASK_EROSION_KERNEL_RELATIVE_SIZE_IN_IMAGE = 0.005;
const int MASK_NUM_EROSION_ITERATIONS = 8;

const double BLOB_RELATIVE_MIN_SIZE_IN_IMAGE = 0.05;

const cv::Scalar DRAW_RECT_COLOR(0, 255, 0); // Green

当然,BlobDetector的核心是它的detect方法。可选地,该方法创建图像的缩小版本,以便更快地处理。然后,我们调用一个辅助方法createMask,对(调整过大小的)图像执行阈值化和侵蚀。我们将得到的掩码图传递给cv::Canny函数进行Canny边缘检测。我们将边缘掩码图传递给cv::findContours函数,该函数以vector的格式填充一个轮廓向量.也就是说,每条轮廓都是点的向量。对于每一个轮廓,我们找到这些点的边界矩形。如果我们处理的是一个经过调整大小的图像,我们将边界矩形恢复到原始的比例。我们拒绝非常小的矩形。最后,对于每个接受的矩形,我们在输出向量中放入一个新的Blob对象,并可选地在原始图像中绘制矩形。下面是detect方法的实现:

void BlobDetector::detect(cv::Mat &image, std::vector &blobs, double resizeFactor, bool draw) {
    blobs.clear();
    if (resizeFactor == 1.0) {
        createMask(image);
        
    } else {
        cv::resize(image, resizedImage, cv::Size(), resizeFactor, resizeFactor, cv::INTER_AREA); createMask(resizedImage);
    }
    
    // Find the edges in the mask.
    cv::Canny(mask, edges, 191, 255);
    
    // Find the contours of the edges.
    cv::findContours(edges, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
    
    std::vector rects;
    int blobMinSize = (int)(MIN(image.rows, image.cols) * BLOB_RELATIVE_MIN_SIZE_IN_IMAGE);
    for (std::vector contour : contours) {
        
        // Find the contour's bounding rectangle.
        cv::Rect rect = cv::boundingRect(contour);
        
        // Restore the bounding rectangle to the original scale.
        rect.x /= resizeFactor;
        rect.y /= resizeFactor;
        rect.width /= resizeFactor;
        rect.height /= resizeFactor;
        
        if (rect.width < blobMinSize || rect.height < blobMinSize) {
            continue;
        }
        
        // Create the blob from the sub-image inside the bounding // rectangle.
        
        blobs.push_back(Blob(cv::Mat(image, rect)));
        
        // Remember the bounding rectangle in order to draw it later.
        rects.push_back(rect);
    }
    if (draw) {
        // Draw the bounding rectangles.
        
        for (const cv::Rect &rect : rects) {
            cv::rectangle(image, rect.tl(), rect.br(), DRAW_RECT_COLOR);
        }
    }
}

getMask函数简单地返回detect方法中计算的mask数据.

const cv::Mat &BlobDetector::getMask() const {
    return mask;
}

createMask辅助方法首先使用cv::meanStdDev函数查找图像的平均颜色和标准差。根据BlobDetector.cpp顶部附近的MASK_STD_DEVS_FROM_MEAN常量定义的距离均值的标准差,我们计算背景颜色的范围。我们认为在这个范围之外的值是前景色。使用cv::inRange函数,我们将背景色(图像中)映射为白色(蒙版中),前景色(图像中)映射为黑色(蒙版中)。然后,我们使用cv::getStructuringElement函数创建一个方形内核。最后,利用cv::erode函数中的核新算法对掩码图进行腐蚀形态学运算。这有平滑黑色(前景)区域的效果,因此它们会吸收可能只是噪声的小间隙。以下是相关代码:


void BlobDetector::createMask(const cv::Mat &image) {
    
    // Find the image's mean color.
    // Presumably, this is the background color.
    // Also find the standard deviation.
    cv::Scalar meanColor;
    cv::Scalar stdDevColor;
    cv::meanStdDev(image, meanColor, stdDevColor);
    
    // Create a mask based on a range around the mean color.
    cv::Scalar halfRange = MASK_STD_DEVS_FROM_MEAN * stdDevColor;
    cv::Scalar lowerBound = meanColor - halfRange;
    cv::Scalar upperBound = meanColor + halfRange;
    cv::inRange(image, lowerBound, upperBound, mask);
    
    // Erode the mask to merge neighboring blobs.
    
    int kernelWidth = (int)(MIN(image.cols, image.rows) * MASK_EROSION_KERNEL_RELATIVE_SIZE_IN_IMAGE); if (kernelWidth > 0) {
        cv::Size kernelSize(kernelWidth, kernelWidth);
        cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, kernelSize);
        cv::erode(mask, mask, kernel, cv::Point(-1, -1), MASK_NUM_EROSION_ITERATIONS);
    }
}

这就是斑点检测器代码的结尾。如您所见,它使用的是一种通用且相当线性的方法,这与第4章“检测和合并哺乳动物的脸部”中的人脸检测器不同,后者在搜索人眼、猫眼等时依赖于许多特殊情况。虽然人脸检测器还执行了人脸与猫脸的分类,但我们现在使用的是单独的斑点检测器和斑点分类器,这种职责的分离使我们能够保持每个类的实现相对简单。

为了完整性,请注意Blob类的构造函数具有复制参数的简单实现。对于blob的图像,我们进行深拷贝,因为原始图像可能会更改。(记住,原始图像可能是视频帧中的一个子图像,检测后,我们在视频帧上绘制矩形。)类似地,Blob的getter和setter方法也是自解释的。创建一个新文件Blob.cpp并填写以下实现:


#include "Blob.hpp"

Blob::Blob(const cv::Mat &mat, uint32_t label) : label(label) {
    mat.copyTo(this->mat);
}

Blob::Blob() { }

Blob::Blob(const Blob &other) : label(other.label) {
    other.mat.copyTo(mat);
}

bool Blob::isEmpty() const {
    return mat.empty();
}

uint32_t Blob::getLabel() const {
    return label;
}

void Blob::setLabel(uint32_t value) {
    label = value;
}

const cv::Mat &Blob::getMat() const {
    return mat;
}

int Blob::getWidth() const {
    return mat.cols;
}

int Blob::getHeight() const {
    return mat.rows;
}

###返回到第五章目录###
###返回到书籍目录###

你可能感兴趣的:(iOS,Application,Develpment,with,Ope)