iOS图像识别

作者: CocoFei | 来源:发表于2017-07-13 19:08 被阅读1550次

iOS通过摄像头动态识别图像

前言:

目前的计算机图像识别,透过现象看本质,主要分为两大类:

  • 基于规则运算的图像识别,例如颜色形状等模板匹配方法
  • 基于统计的图像识别。例如机器学习ML,神经网络等人工智能方法

区别:模板匹配方法适合固定的场景或物体识别,机器学习方法适合大量具有共同特征的场景或物体识别。

对比:无论从识别率,准确度,还是适应多变场景变换来讲,机器学习ML都是优于模板匹配方法的;前提你有大量的数据来训练分类器。如果是仅仅是识别特定场景、物体或者形状,使用模板匹配方法更简单更易于实现。

目标:实现在iOS客户端,通过摄像头发现并标记目标。

实现效果图

效果图

一、方案选择

1.1、iOS客户端快速实现图像识别的两种方案:

集成Google的TensorFlow实现 集成OpenCV开源计算机库来实现

logo
  • AlphaGo战胜世界围棋冠军,人工智能大火,谷歌去年开源了其用来制作AlphaGo的深度学习系统Tensorflow,而且Tensorflow支持了iOS,Android等移动端。
  • OpenCV于1999年由Intel建立的,跨平台的开源计算机视觉库,主要由C和C++代码构成,有Python、Ruby、MATLAB等语言的接口,支持iOS,Android等移动设备。
  • 显而易见,虽然都是开源库,都支持机器学习ML,但从推出时间,代码迭代,资料的丰富度,以及前辈已经给踩平的坑来讲,OpenCV是成熟的,应该首先选择的。

1.2、OpenCV中实现图像识别的方法对比:

  • 模板匹配

    • 适合固定的场景、物体或特定形状的图片识别
    • eg1:某公司的Logo图标,假设图标是不变的,也适用
    • eg2:适用于某个图片是另外一张大图的一部分的场景
    • eg3:例如五角星形状固定,可转换为边框匹配
  • 特征点检测

    • 适合标记两幅图片中相同的特征点
    • eg1:有相同部分的照片拼接,视频运动追踪
    • eg2:例如全景图片的拼接,长图的拼接
    • eg3:监控视频中的目标跟踪
  • 基于机器学习ML的训练分类器用来分类的方法

    • 此方法依赖于训练数据,给机器提供大量包含目标的正确数据和不包含目标的错误背景数据,让机器来总结提取特征,适合识别某类有多种状态的场景或物体识别
    • eg1:人脸识别、人眼识别,身体识别等等
    • eg2:支付宝扫福,福字有成千上万种写法

二、OpenCV集成

2.1、iOS项目集成OpenCV,主要有两种方法

  • 方法1: 从OpenCV官网下载opencv2.framework框架,然后拖入即可,导入依赖的库,具体集成方法见我的另外一篇文章iOS集成OpenCV
  • 方法2: CocoaPods方式集成,Pod文件中配置pod 'OpenCV',而实验证明,用CocoaPods方式配置虽然简单,但自动配置的不正确,存在名称重复等大量的问题。

三、模板匹配法

3.1、获取视频图像

首先要做的肯定是从摄像头中获取视频帧,转为OpenCV能够用的cv::Mat矩阵,OpenCV运算是以矩阵Mat为基础的。从iPhone摄像头获取视频帧,在下方的代理方法中获取:

#pragma mark - 获取视频帧,处理视频
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection;

3.2、将视频帧转换为cv::Mat矩阵

将视频帧对象高效转换为OpenCV能够使用矩阵cv::Mat
摄像头获取的图像是偏转90度,切镜像的,通过矩阵翻转,矩阵倒置操作纠正,而且将彩色图像转换为灰度图像,加快计算速度,减少CPU占有率。

#pragma mark - 将CMSampleBufferRef转为cv::Mat
+(cv::Mat)bufferToMat:(CMSampleBufferRef) sampleBuffer{
    CVImageBufferRef imgBuf = CMSampleBufferGetImageBuffer(sampleBuffer);
   
    //锁定内存
    CVPixelBufferLockBaseAddress(imgBuf, 0);
    // get the address to the image data
    void *imgBufAddr = CVPixelBufferGetBaseAddress(imgBuf);
   
    // get image properties
    int w = (int)CVPixelBufferGetWidth(imgBuf);
    int h = (int)CVPixelBufferGetHeight(imgBuf);
   
    // create the cv mat
    cv::Mat mat(h, w, CV_8UC4, imgBufAddr, 0);

    //转换为灰度图像
    cv::Mat edges;
    cv::cvtColor(mat, edges, CV_BGR2GRAY);
   
    //旋转90度
    cv::Mat transMat;
    cv::transpose(mat, transMat);
   
    //翻转,1是x方向,0是y方向,-1位Both
    cv::Mat flipMat;
    cv::flip(transMat, flipMat, 1);
   
    CVPixelBufferUnlockBaseAddress(imgBuf, 0);
   
    return flipMat;
}

3.3、视频帧矩阵转换为灰度矩阵

此时已经获取了摄像头图像矩阵flipMat,下一步只需将模板图像UIImage转换为cv::Mat矩阵,提供OpenCV函数对比即可,如果上一步已经将矩阵转换为灰度图像,则cv::cvtColor(tempMat, tempMat, CV_BGR2GRAY);这一行去掉即可。

//将图片转换为灰度的矩阵
-(cv::Mat)initTemplateImage:(NSString *)imgName{
    UIImage *templateImage = [UIImage imageNamed:imgName];
    cv::Mat tempMat;
    UIImageToMat(templateImage, tempMat);
    //cv::cvtColor(tempMat, tempMat, CV_BGR2GRAY);
    return tempMat;
}

3.4、视频帧矩阵与模板矩阵对比

此时获取了模板UIImage的矩阵templateMat和视频帧的矩阵flipMat,只需要用OpenCV的函数对比即可。

/**
 对比两个图像是否有相同区域
 
 @return 有为Yes
 */
-(BOOL)compareInput:(cv::Mat) inputMat templateMat:(cv::Mat)tmpMat{
    int result_rows = inputMat.rows - tmpMat.rows + 1;
    int result_cols = inputMat.cols - tmpMat.cols + 1;
   
    cv::Mat resultMat = cv::Mat(result_cols,result_rows,CV_32FC1);
    cv::matchTemplate(inputMat, tmpMat, resultMat, cv::TM_CCOEFF_NORMED);
   
    double minVal, maxVal;
    cv::Point minLoc, maxLoc, matchLoc;
    cv::minMaxLoc( resultMat, &minVal, &maxVal, &minLoc, &maxLoc, cv::Mat());
    //    matchLoc = maxLoc;
    //    NSLog(@"min==%f,max==%f",minVal,maxVal);
    dispatch_async(dispatch_get_main_queue(), ^{
        self.similarLevelLabel.text = [NSString stringWithFormat:@"相似度:%.2f",maxVal];
    });
   
    if (maxVal > 0.7) {
        //有相似位置,返回相似位置的第一个点
        currentLoc = maxLoc;
        return YES;
    }else{
        return NO;
    }
}

3.5、模板匹配法优化

此时,我们已经对比两个图像的相似度了,其中maxVal越大表示匹配度越高,1为完全匹配,一般想要匹配准确,需要大于0.7。

但此时我们发现一个问题,我们摄像头离图像太远或者太近,都无法识别,只有在特定的距离才能够识别。

这是因为模板匹配法,只是死板的拿模板图像去和摄像头读取的图像进行比较,放大缩小都不行。

我们做些优化,按照图像金字塔的方法,将模板进行动态的放大缩小,只要能够匹配,说明图像就是一样的,这样摄像头前进后退都能够识别。我们将识别出的位置和大小保存在数组中,用矩形方框来标记位置。至于怎么标记,就不细说了,方法很多。

//图像金字塔分级放大缩小匹配,最大0.8*相机图像,最小0.3*tep图像
-(NSArray *)compareByLevel:(int)level CameraInput:(cv::Mat) inputMat{
    //相机输入尺寸
    int inputRows = inputMat.rows;
    int inputCols = inputMat.cols;
   
    //模板的原始尺寸
    int tRows = self.templateMat.rows;
    int tCols = self.templateMat.cols;
   
    NSMutableArray *marr = [NSMutableArray array];
   
    for (int i = 0; i < level; i++) {
        //取循环次数中间值
        int mid = level*0.5;
        //目标尺寸
        cv::Size dstSize;
        if (i<mid) {
            //如果是前半个循环,先缩小处理
            dstSize = cv::Size(tCols*(1-i*0.2),tRows*(1-i*0.2));
        }else{
            //然后再放大处理比较
            int upCols = tCols*(1+i*0.2);
            int upRows = tRows*(1+i*0.2);
            //如果超限会崩,则做判断处理
            if (upCols>=inputCols || upRows>=inputRows) {
                upCols = tCols;
                upRows = tRows;
            }
            dstSize = cv::Size(upCols,upRows);
        }
        //重置尺寸后的tmp图像
        cv::Mat resizeMat;
        cv::resize(self.templateMat, resizeMat, dstSize);
        //然后比较是否相同
        BOOL cmpBool = [self compareInput:inputMat templateMat:resizeMat];
       
        if (cmpBool) {
            NSLog(@"匹配缩放级别level==%d",i);
            CGRect rectF = CGRectMake(currentLoc.x, currentLoc.y, dstSize.width, dstSize.height);
            NSValue *rValue = [NSValue valueWithCGRect:rectF];
            [marr addObject:rValue];
            break;
        }
    }
    return marr;
}

3.5、模板匹配法实现效果图

logo

四、机器学习ML训练分类器方法识别图像

4.1、训练分类器

机器学习方法适合批量提取大量图片的特征,训练分类器,具体训练方法在网上查找。关键点在于训练数据比较难弄,例如人脸分类器需要的正样本人脸图像几千张,负样本需要为正样本的3倍左右。我的解决思路为从摄像头录制待识别物体,从视频帧中生成PNG格式的正样本,再拍摄不包含待识别物体的背景,仍旧从视频中自动生成PNG格式的负样本。训练级联分类器方法说明

4.2、加载训练好的分类器

训练完成后会生成一个XML格式的文件,我们加载这个XML文件,就可以用其来识别物体了,这里我们使用OpenCV官方库中人眼识别库haarcascade_eye_tree_eyeglasses.xml,我们从GitHub上面下载开源库OpenCV的源代码,最新版本为3.2.0,分类器路径为/opencv-3.2.0/data目录下的文件夹中,XML格式文件就是。

加载训练好的分类器文件需要用到加载器,我们定义一个加载器属性对象

cv::CascadeClassifier icon_cascade;//分类器

加载器加载XML文件,加载成功返回YES

    //加载训练文件
    NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"haarcascade_eye_tree_eyeglasses.xml" ofType:nil];
    cv::String fileName = [bundlePath cStringUsingEncoding:NSUTF8StringEncoding];
   
    BOOL isSuccessLoadFile = icon_cascade.load(fileName);
    isSuccessLoadXml = isSuccessLoadFile;
    if (isSuccessLoadFile) {
        NSLog(@"Load success.......");
    }else{
        NSLog(@"Load failed......");
    }

4.3、使用分类器识别图像

我们是从摄像头获取图像,仍需把视频帧转换为OpenCV能够使用的cv::Mat矩阵格式,按照上面3.2相同的方法转换,假设我们已经获取了视频帧转换好的灰度图像矩阵cv::Mat imgMat,那我们用OpenCV的API接口来识别视频帧,并把识别出的位置转换为Frame存在数组中返回,我们可以随意使用这些Frame来标记识别出的位置

//获取计算出的标记的位置,保存在数组中
-(NSArray *)getTagRectInLayer:(cv::Mat) inputMat{
    if (inputMat.empty()) {
        return nil;
    }
    //图像均衡化
    cv::equalizeHist(inputMat, inputMat);
    //定义向量,存储识别出的位置
    std::vector<cv::Rect> glassess;
    //分类器识别
    icon_cascade.detectMultiScale(inputMat, glassess, 1.1, 3, 0);
    //转换为Frame,保存在数组中
    NSMutableArray *marr = [NSMutableArray arrayWithCapacity:glassess.size()];
    for (NSInteger i = 0; i < glassess.size(); i++) {
        CGRect rect = CGRectMake(glassess[i].x, glassess[i].y, glassess[i].width,glassess[i].height);
        NSValue *value = [NSValue valueWithCGRect:rect];
        [marr addObject:value];
    }
    return marr.copy;
}

4.4、分类器识别图像效果图

logo

五、Demo中的图像处理

5.1、使用系统CIFilter高斯模糊视频

CIFilter高斯模糊

5.2、使用OpenCV处理视频图像

  • 原图像
  • 直方图均衡化
  • 图像二值化
  • 摄像头预览
  • 灰度图
  • 轮廓图
CIFilter高斯模糊

如果您觉得有所帮助,请在GitHub OpenCVDemo上赏个Star ⭐️,您的鼓励是我前进的动力

相关文章

  • tensorflow入门-iOS篇

    谷歌图像识别提供了iOS和Android的例子 此处主要介绍iOS的例子使用 地址是https://github....

  • iOS图像识别

    iOS通过摄像头动态识别图像 前言: 目前的计算机图像识别,透过现象看本质,主要分为两大类:基于规则运算的图像识别...

  • Airtest 笔记1:快速入门

    1、介绍 Airtest 框架跨平台:支持window、Android、ios基于图像识别UI自动化测试框架 Po...

  • iOS8、9、10、11特性demo集

    点此获得更好阅读体验 iOS11部分 Core ML 基于CoreML的图像识别demo,使用了Inception...

  • 学习资料

    1.沉浸式线性代数 英文版本 2.iOS开发 - Vision 图像识别框架的使用 3.Create ML Tu...

  • [ WWDC2018 ] - 计算机视觉和物体追踪 Vision

    一、WWDC2018 Vision 去年IOS11出了Vision框架给开发者提供了使用简单的图像识别方式,本来期...

  • 深度学习中的图片对象

    自2012年,AlexNet在ImageNet图像识别一战成名后,基于深度学习的图像识别快速发展。 图像识别也是深...

  • 图像识别基础

    图像识别基础 我们对图像进行一些列的处理,将其有用的信息提取出来,进行划分归类,这就是图像识别。 图像识别目的: ...

  • Xamarin.iOS中的CoreML简介

    CoreML为iOS带来了机器学习 - 应用程序可以利用训练有素的机器学习模型来执行从问题解决到图像识别的各种任务...

  • IOS-Vision-In-Video-Streams

    一、引言 去年IOS11出了Vison框架给开发者提供了使用简单的图像识别方式,本来期待在今年能够拥有更多的图像处...

网友评论

  • 陪伴最长情:你源码里面训练分类器识别图像中加载训练文件,这个文件是哪里来的呢,怎么生成的呢,我想做识别人体不同部位的效果,望告知,谢谢!
    CocoFei:@陪伴最长情 这个是opencv库里自带的,下载opencv后里面就可以找到。如果仅仅是识别身体不同部位,我记得苹果自带的类库就可以实现,不需要集成opencv
  • Ignacio焕:我看原因应该改rows为负数导致的,但是我不知道为什么_rows会为负数呢
    CocoFei:@E毛 输出视频尺寸和待识别图像尺寸小的话,CPU计算快,所以一般都是设置视频分辨率低一点,图片小一些。如果真有需求,那就自己百度设置输出视频尺寸,最高是和照片尺寸一样,但视频会很卡,注意:待识别图片尺寸还是需要小于视频输出尺寸,这是模板匹配法特性决定的。
    Ignacio焕:@CocoFei 恩,我也发现这个问题了,只能添加下雨480*360的图片,不知道这个尺寸可以修改吗
    CocoFei:@E毛 你是不是添加的待识别图片尺寸很大,而输出视频图像尺寸,我是根据设置预览窗口自动适配的,如果“待识别图片”的尺寸 > 输入图像的尺寸,会报错,换一张小图片就可以了。
  • Ignacio焕:我重新添加了一个图片,但是在识别的过程中,出现OpenCV Error: Assertion failed (s >= 0) in setSize, file /Volumes/build-storage/build/master_iOS-mac/opencv/modules/core/src/matrix.cpp, line 310, 您知道怎么处理吗
  • Hunter琼:我想问问你这个识别相似度 哪有88%以上 我在网上随便找个福 形似度只有30%
    CocoFei:@Hunter琼 识别型号为啥不用文字识别/条形码识别,或者OCR文字识别,如果仅仅是识别文字,用开源库Tesseract OCR iOS就行
    Hunter琼:@CocoFei 我们现在有个识别设备的需求, 比如识别路由器型号之类的 能用这个框架吗 ??
    CocoFei:@Hunter琼 模板匹配法,只能识别相同的东西,比如广告标识等固定的东西,你想识别成千上万种福字,训练模型,提取特征,网上有教程,自己搜
  • Hunter琼:厉害了
  • ray_1942:加油
    CocoFei:@ray_1942 谢谢
  • 五锅锅:有点厉害
    CocoFei:@五锅锅 这个没啥技术含量,和算法级的ML比起来,我这只能算是一个简单应用

本文标题:iOS图像识别

本文链接:https://www.haomeiwen.com/subject/sfdwhxtx.html