本文翻译自iOS Application Development with OpenCV 3
在OpenCV中我们可以使用大量图片进行机器学习来识别图像,但这同样会花费大量的时间。接下来我们来看一下如何只进行少量的机器训练便可以识别图像。
我们分为以下5个步骤来进行:
1、将图像分割为前景和背景区域。前景区域为黑色,背景为白色。
2、使前景区域边缘更加平滑
3、检测前景的形状,并裁剪出前景的矩形图像。
4、对裁剪出来的前景图像进行直方图分析。
5、对图像进行特征匹配(或叫关键点匹配)
1、blob detection(将图像分割为前景区域和背景区域)
通常,一个blob detection需要解决以下几个问题:
1、Segmentation:区分前景区域和背景区域
2、Edge detection:区分边缘部分和不是边缘部分
3、Contour analysis:简单的代表边缘,以便于我们形成一个几何图形
我们使用cv::inRang方法来区分前景区域和背景区域,在使用cv::Canny方法来画出边缘部分,最后使用cv::findContours来找到这些轮廓,并使用cv::boundingRect来画出矩形框住这些轮廓。
2、直方图分析
直方图是图像中每种颜色出现次数的计数。 通常,我们不会分别计算所有可能的颜色; 相反,我们将相似的颜色组合成一个bin。 对于较少数量的bin,直方图占用较少的内存并提供较粗略的比较基础。 我们选择每个通道32个bin(或总共32 ^ 3 = 32678(bin))。
直方图的比较可以告诉我们两个图像是否包含相似的颜色。 仅这种相似性并不一定意味着图像包含匹配的对象。 例如,银叉和银币可以具有类似的直方图。
OpenCV提供函数cv :: calcHist和cv :: compareHist来计算直方图并测量它们的相似性。
3、特征匹配
OpenCV提供了SURF detector/extractor配上FLANN matcher,和ORB detector/extractor配上brute-force matcher来实现特征匹配。
SURF有被申请专利,所以请不要用于商业用途,这个方法在opencv_contrib中通过cv::xfeature2d::SURF实现。如果我们没有导入opencv_contrib的话,我们可以用ORB,这个方法在cv::ORB类中实现。
以下是项目相关代码
创建Blob描述
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));
// 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<cv::KeyPoint> keypointDescriptors;
featureDetectorAndDescriptorExtractor->compute(grayMat, keypoints, keypointDescriptors);
return BlobDescriptor(histogram, keypointDescriptors, blob.getLabel());
}
计算两个Blob之间的Distance
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<cv::DMatch> 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;
}
检测Blob
void BlobDetector::detect(cv::Mat &image, std::vector<Blob> &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 <cv::Rect> rects;
int blobMinSize = (int)(MIN(image.rows, image.cols) * BLOB_RELATIVE_MIN_SIZE_IN_IMAGE);
for (std::vector<cv::Point> 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);
}
}
}
创建mask
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);
}
}