ios 影象識別技術總結
iOS通過攝像頭動態識別影象
前言:
目前的計算機影象識別,透過現象看本質,主要分為兩大類:
- 基於規則運算的影象識別,例如顏色形狀等模板匹配方法
- 基於統計的影象識別。例如機器學習ML,神經網路等人工智慧方法
區別:模板匹配方法適合固定的場景或物體識別,機器學習方法適合大量具有共同特徵的場景或物體識別。
對比:無論從識別率,準確度,還是適應多變場景變換來講,機器學習ML都是優於模板匹配方法的;前提你有
大量的資料
來訓練分類器。如果是僅僅是識別特定場景、物體或者形狀,使用模板匹配方法更簡單更易於實現。目標:實現在iOS客戶端,通過攝像頭髮現並標記目標。
實現效果圖
效果圖一、方案選擇
1.1、iOS客戶端快速實現影象識別的兩種方案:
整合Google的TensorFlow
實現 整合OpenCV
開源計算機庫來實現
- 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處理視訊影象
- 原影象
- 直方圖均衡化
- 影象二值化
- 攝像頭預覽
- 灰度圖
- 輪廓圖
如果您覺得有所幫助,請在GitHub OpenCVDemo上賞個Star ⭐️,您的鼓勵是我前進的動力
</div>
</div>