本文翻譯自iOS? Application? Development? with? OpenCV 3
在OpenCV中我們可以使用大量圖片進行機器學習來識別圖像厌均,但這同樣會花費大量的時間管行。接下來我們來看一下如何只進行少量的機器訓練便可以識別圖像。
我們分為以下5個步驟來進行:
1策添、將圖像分割為前景和背景區(qū)域。前景區(qū)域為黑色逸爵,背景為白色幅疼。
2、使前景區(qū)域邊緣更加平滑
3偷仿、檢測前景的形狀哩簿,并裁剪出前景的矩形圖像。
4酝静、對裁剪出來的前景圖像進行直方圖分析节榜。
5、對圖像進行特征匹配(或叫關鍵點匹配)
1别智、blob detection(將圖像分割為前景區(qū)域和背景區(qū)域)
通常宗苍,一個blob detection需要解決以下幾個問題:
1、Segmentation:區(qū)分前景區(qū)域和背景區(qū)域
2薄榛、Edge? detection:區(qū)分邊緣部分和不是邊緣部分
3讳窟、Contour analysis:簡單的代表邊緣,以便于我們形成一個幾何圖形
我們使用cv::inRang方法來區(qū)分前景區(qū)域和背景區(qū)域敞恋,在使用cv::Canny方法來畫出邊緣部分丽啡,最后使用cv::findContours來找到這些輪廓,并使用cv::boundingRect來畫出矩形框住這些輪廓硬猫。
2补箍、直方圖分析
直方圖是圖像中每種顏色出現(xiàn)次數(shù)的計數(shù)。 通常啸蜜,我們不會分別計算所有可能的顏色; 相反坑雅,我們將相似的顏色組合成一個bin。 對于較少數(shù)量的bin衬横,直方圖占用較少的內(nèi)存并提供較粗略的比較基礎裹粤。 我們選擇每個通道32個bin(或總共32 ^ 3 = 32678(bin))。
直方圖的比較可以告訴我們兩個圖像是否包含相似的顏色蜂林。 僅這種相似性并不一定意味著圖像包含匹配的對象遥诉。 例如,銀叉和銀幣可以具有類似的直方圖噪叙。
OpenCV提供函數(shù)cv :: calcHist和cv :: compareHist來計算直方圖并測量它們的相似性突那。
3、特征匹配
OpenCV提供了SURF detector/extractor配上FLANN matcher,和ORB detector/extractor配上brute-force matcher來實現(xiàn)特征匹配构眯。
SURF有被申請專利愕难,所以請不要用于商業(yè)用途,這個方法在opencv_contrib中通過cv::xfeature2d::SURF實現(xiàn)惫霸。如果我們沒有導入opencv_contrib的話猫缭,我們可以用ORB,這個方法在cv::ORB類中實現(xiàn)。
以下是項目相關代碼
創(chuàng)建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);
? ? ? ? ? ? ? }
? ? }
}
創(chuàng)建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);
? ? }
}