1. 程式人生 > >ios 影象識別技術總結

ios 影象識別技術總結

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 ⭐️,您的鼓勵是我前進的動力

      </div>
    </div>