iOS下使用OpenCV进行图像识别(不使用Haar和LBP,不需要大量机器学习)

本文翻译自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);

    }

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容