让我们假设背景有一个特别的颜色范围,例如“奶油到雪之间的白色”。我们的斑点检测器将计算图像的主色范围,并搜索颜色与此范围不同的大区域。这些异常区域将构成检测到的斑点。
[对于像豆子或硬币这样的小物件,用户可以很容易地找到一个简单的背景,比如一张白纸、一张简单的桌面、一件简单的衣服,甚至是手掌。斑点检测器可以动态估计背景颜色范围,可以适应各种背景和光照条件;它并不局限于实验室环境。]
创建一个新文件"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
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;
}
###返回到第五章目录###
###返回到书籍目录###