簡介
引自維基百科計算機視覺是一門研究如何使機器“看”的科學,更進一步的說,就是指用攝影機和計算機代替人眼對目標進行識別、跟蹤和測量等機器視覺,並進一步做影象處理,用計算機處理成為更適合人眼觀察或傳送給儀器檢測的影象。
計算機視覺系統的結構形式很大程度上依賴於其具體應用方向。計算機視覺系統的具體實現方法同時也由其功能決定——是預先固定的抑或是在執行過程中自動學習調整。儘管如此,計算機視覺的都需要具備以下處理步驟:
引自維基百科人臉識別,特指利用分析比較人臉視覺特徵資訊進行身份鑑別的計算機技術。 廣義的人臉識別實際包括構建人臉識別系統的一系列相關技術,包括人臉影象採集、人臉定位、人臉識別預處理、身份確認以及身份查詢等;而狹義的人臉識別特指通過人臉進行身份確認或者身份查詢的技術或系統。
人臉識別是計算機視覺的一種應用,iOS中常用的有四種實現方式:CoreImage、Vision、OpenCV、AVFoundation,下面一一介紹幾種方式的實現步驟。
CoreImage
自從 iOS 5(大概在2011年左右)之後,iOS 開始支援人臉識別,只是用的人不多。人臉識別 API 讓開發者不僅可以進行人臉檢測,還能識別微笑、眨眼等表情。
操作部分:
- 濾鏡(CIFliter):CIFilter 產生一個CIImage。典型的,接受一到多的圖片作為輸入,經過一些過濾操作,產生指定輸出的圖片。
- 檢測(CIDetector):CIDetector 檢測處理圖片的特性,如使用來檢測圖片中人臉的眼睛、嘴巴、等等。
- 特徵(CIFeature):CIFeature 代表由 detector處理後產生的特徵。
影象部分:
- 畫布(CIContext):畫布類可被用與處理Quartz 2D 或者 OpenGL。可以用它來關聯CoreImage類。如濾鏡、顏色等渲染處理。
- 顏色(CIColor): 圖片的關聯與畫布、圖片畫素顏色的處理。
- 向量(CIVector): 圖片的座標向量等幾何方法處理。
- 圖片(CIImage): 代表一個影象,可代表關聯後輸出的影象。
實現程式碼如下:
guard let personciImage = CIImage(image: personPic.image!) else { return }
let accuracy = [CIDetectorAccuracy: CIDetectorAccuracyHigh]
let faceDetector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options: accuracy)
let faces = faceDetector?.features(in: personciImage)
// 轉換座標系
let ciImageSize = personciImage.extent.size
var transform = CGAffineTransform(scaleX: 1, y: -1)
transform = transform.translatedBy(x: 0, y: -ciImageSize.height)
for face in faces as! [CIFaceFeature] {
print("Found bounds are \(face.bounds)")
// 應用變換轉換座標
var faceViewBounds = face.bounds.applying(transform)
// 在影象檢視中計算矩形的實際位置和大小
let viewSize = personPic.bounds.size
let scale = min(viewSize.width / ciImageSize.width, viewSize.height / ciImageSize.height)
let offsetX = (viewSize.width - ciImageSize.width * scale) / 2
let offsetY = (viewSize.height - ciImageSize.height * scale) / 2
faceViewBounds = faceViewBounds.applying(CGAffineTransform(scaleX: scale, y: scale))
faceViewBounds.origin.x += offsetX
faceViewBounds.origin.y += offsetY
let faceBox = UIView(frame: faceViewBounds)
faceBox.layer.borderWidth = 3
faceBox.layer.borderColor = UIColor.red.cgColor
faceBox.backgroundColor = UIColor.clear
personPic.addSubview(faceBox)
if face.hasLeftEyePosition {
print("Left eye bounds are \(face.leftEyePosition)")
}
if face.hasRightEyePosition {
print("Right eye bounds are \(face.rightEyePosition)")
}
}
複製程式碼
執行APP,效果如下:
Vision
Vision 是 Apple 在 WWDC 2017 推出的影象識別框架,它基於 Core ML,所以可以理解成 Apple 的工程師設計了一種演算法模型,然後利用 Core ML 訓練,最後整合成一個新的框架,相比開源模型然後讓開發者自己整合起來,這種方式更安全也更方便我們使用。
Vision 支援多種圖片型別,如:
- CIImage
- NSURL
- NSData
- CGImageRef
- CVPixelBufferRef
Vision使用中的角色有: Request,RequestHandler,results和results中的Observation陣列。
Request型別: 有很多種,比如圖中列出的 人臉識別、特徵識別、文字識別、二維碼識別等。
使用概述:
我們在使用過程中是給各種功能的 Request 提供給一個 RequestHandler,Handler 持有需要識別的圖片資訊,並將處理結果分發給每個 Request 的 completion Block 中。可以從 results 屬性中得到 Observation 陣列。
observations陣列中的內容根據不同的request請求返回了不同的observation,如:VNFaceObservation、VNTextObservation、VNBarcodeObservation、VNHorizonObservation,不同的Observation都繼承於VNDetectedObjectObservation,而VNDetectedObjectObservation則是繼承於VNObservation。每種Observation有boundingBox,landmarks等屬性,儲存的是識別後物體的座標,點位等,我們拿到座標後,就可以進行一些UI繪製。
程式碼實現如下:
@IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let handler = VNImageRequestHandler.init(cgImage: (imageView.image?.cgImage!)!, orientation: CGImagePropertyOrientation.up)
let request = reqReq()
DispatchQueue.global(qos: .userInteractive).async {
do {
try handler.perform([request])
}
catch {
print("e")
}
}
}
func reqReq() -> VNDetectFaceRectanglesRequest {
let request = VNDetectFaceRectanglesRequest(completionHandler: { (request, error) in
DispatchQueue.main.async {
if let result = request.results {
let transform = CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: -self.imageView!.frame.size.height)
let translate = CGAffineTransform.identity.scaledBy(x: self.imageView!.frame.size.width, y: self.imageView!.frame.size.height)
//遍歷所有識別結果
for item in result {
//標註框
let faceRect = UIView(frame: CGRect.zero)
faceRect.layer.borderWidth = 3
faceRect.layer.borderColor = UIColor.red.cgColor
faceRect.backgroundColor = UIColor.clear
self.imageView!.addSubview(faceRect)
if let faceObservation = item as? VNFaceObservation {
let finalRect = faceObservation.boundingBox.applying(translate).applying(transform)
faceRect.frame = finalRect
}
}
}
}
})
return request
}
複製程式碼
執行APP,效果如下:
OpenCV
OpenCV (Open Source Computer Vision Library)是一個在BSD許可下發布的開源庫,因此它是免費提供給學術和商業用途。有C++、C、Python和Java介面,支援Windows、Linux、MacOS、iOS和Android等系統。OpenCV是為計算效率而設計的,而且密切關注實時應用程式的發展和支援。該庫用優化的C/C++編寫,可以應用於多核處理。在啟用OpenCL的基礎上,它可以利用底層的異構計算平臺的硬體加速。
OpenCV 起始於 1999 年 Intel 的一個內部研究專案。從那時起,它的開發就一直很活躍。進化到現在,它已支援如 OpenCL 和 OpenGL 等現代技術,也支援如 iOS 和 Android 等平臺。
下面是在官方文件中列出的最重要的模組:
- core:簡潔的核心模組,定義了基本的資料結構,包括稠密多維陣列
Mat
和其他模組需要的基本函式。 - imgproc:影象處理模組,包括線性和非線性影象濾波、幾何影象轉換 (縮放、仿射與透視變換、一般性基於表的重對映)、顏色空間轉換、直方圖等等。
- video:視訊分析模組,包括運動估計、背景消除、物體跟蹤演算法。
- calib3d:包括基本的多視角幾何演算法、單體和立體相機的標定、物件姿態估計、雙目立體匹配演算法和元素的三維重建。
- features2d:包含了顯著特徵檢測演算法、描述運算元和運算元匹配演算法。
- objdetect:物體檢測和一些預定義的物體的檢測 (如人臉、眼睛、杯子、人、汽車等)。
- ml:多種機器學習演算法,如 K 均值、支援向量機和神經網路。
- highgui:一個簡單易用的介面,提供視訊捕捉、影象和視訊編碼等功能,還有簡單的 UI 介面 (iOS 上可用的僅是其一個子集)。
- gpu:OpenCV 中不同模組的 GPU 加速演算法 (iOS 上不可用)。
- ocl:使用 OpenCL 實現的通用演算法 (iOS 上不可用)。
- 一些其它輔助模組,如 Python 繫結和使用者貢獻的演算法。
cv::Mat 是 OpenCV 的核心資料結構,用來表示任意 N 維矩陣。因為影象只是 2 維矩陣的一個特殊場景,所以也是使用 cv::Mat 來表示的。也就是說,cv::Mat 將是你在 OpenCV 中用到最多的類。
如前面所說,OpenCV 是一個 C++ 的 API,因此不能直接在 Swift 和 Objective-C 程式碼中使用,但能在 Objective-C++ 檔案中使用。
Objective-C++ 是 Objective-C 和 C++ 的混合物,讓你可以在 Objective-C 類中使用 C++ 物件。clang 編譯器會把所有字尾名為 .mm
的檔案都當做是 Objective-C++。一般來說,它會如你所期望的那樣執行,但還是有一些使用 Objective-C++ 的注意事項。記憶體管理是你最應該格外注意的點,因為 ARC 只對 Objective-C 物件有效。當你使用一個 C++ 物件作為類屬性的時候,其唯一有效的屬性就是 assign
。因此,你的 dealloc
函式應確保 C++ 物件被正確地釋放了。
第二重要的點就是,如果你在 Objective-C++ 標頭檔案中引入了 C++ 標頭檔案,當你在工程中使用該 Objective-C++ 檔案的時候就洩露了 C++ 的依賴。任何引入你的 Objective-C++ 類的 Objective-C 類也會引入該 C++ 類,因此該 Objective-C 檔案也要被宣告為 Objective-C++ 的檔案。這會像森林大火一樣在工程中迅速蔓延。所以,應該把你引入 C++ 檔案的地方都用 #ifdef __cplusplus
包起來,並且只要可能,就儘量只在 .mm
實現檔案中引入 C++ 標頭檔案。
要獲得更多如何混用 C++ 和 Objective-C 的細節,請檢視 Matt Galloway 寫的這篇教程。
基於OpenCV,iOS應用程式可以實現很多有趣的功能,也可以把很多複雜的工作簡單化。一般可用於:
- 對圖片進行灰度處理
- 人臉識別,即特徵跟蹤
- 訓練圖片特徵庫(可用於模式識別)
- 提取特定影象內容(根據需求還原有用影象資訊)
程式碼實現如下:
- (void)viewDidLoad {
[super viewDidLoad];
//預設定face探測的引數
[self preSetFace];
//image轉mat
cv::Mat mat;
UIImageToMat(self.imageView.image, mat);
//執行face
[self processImage:mat];
}
- (void)preSetFace {
NSString *faceCascadePath = [[NSBundle mainBundle] pathForResource:@"haarcascade_frontalface_alt2"
ofType:@"xml"];
const CFIndex CASCADE_NAME_LEN = 2048;
char *CASCADE_NAME = (char *) malloc(CASCADE_NAME_LEN);
CFStringGetFileSystemRepresentation( (CFStringRef)faceCascadePath, CASCADE_NAME, CASCADE_NAME_LEN);
_faceDetector.load(CASCADE_NAME);
free(CASCADE_NAME);
}
- (void)processImage:(cv::Mat&)inputImage {
// Do some OpenCV stuff with the image
cv::Mat frame_gray;
//轉換為灰度影象
cv::cvtColor(inputImage, frame_gray, CV_BGR2GRAY);
//影象均衡化
cv::equalizeHist(frame_gray, frame_gray);
//分類器識別
_faceDetector.detectMultiScale(frame_gray, _faceRects,1.1,2,0,cv::Size(30,30));
vector<cv::Rect> faces;
faces = _faceRects;
// 在每個人臉上畫一個紅色四方形
for(unsigned int i= 0;i < faces.size();i++)
{
const cv::Rect& face = faces[i];
cv::Point tl(face.x,face.y);
cv::Point br = tl + cv::Point(face.width,face.height);
// 四方形的畫法
cv::Scalar magenta = cv::Scalar(255, 0, 0, 255);
cv::rectangle(inputImage, tl, br, magenta, 3, 8, 0);
}
UIImage *outputImage = MatToUIImage(inputImage);
self.imageView.image = outputImage;
}
複製程式碼
執行APP,效果如下:
AVFoundation
AVFoundation照片和視訊捕捉功能是從框架搭建之初就是它的強項。 從iOS 4.0 我們就可以直接訪問iOS的攝像頭和攝像頭生成的資料(照片、視訊)。目前捕捉功能仍然是蘋果公司媒體工程師最關注的領域。
AVFoundation實現視訊捕捉的步驟如下:
- 捕捉裝置
AVCaptureDevice為攝像頭、麥克風等物理裝置提供介面。大部分我們使用的裝置都是內建於MAC或者iPhone、iPad上的。當然也可能出現外部裝置。但是AVCaptureDevice 針對物理裝置提供了大量的控制方法。比如控制攝像頭聚焦、曝光、白平衡、閃光燈等。
- 捕捉裝置的輸入
注意:為捕捉裝置新增輸入,不能新增到AVCaptureSession 中,必須通過將它封裝到一個AVCaptureDeviceInputs例項中。這個物件在裝置輸出資料和捕捉會話間扮演接線板的作用。
- 捕捉裝置的輸出
AVCaptureOutput 是一個抽象類。用於為捕捉會話得到的資料尋找輸出的目的地。框架定義了一些抽象類的高階擴充套件類。例如 AVCaptureStillImageOutput 和 AVCaptureMovieFileOutput類。使用它們來捕捉靜態照片、視訊。例如 AVCaptureAudioDataOutput 和 AVCaptureVideoDataOutput ,使用它們來直接訪問硬體捕捉到的數字樣本。
- 捕捉連線
AVCaptureConnection類.捕捉會話先確定由給定捕捉裝置輸入渲染的媒體型別,並自動建立其到能夠接收該媒體型別的捕捉輸出端的連線。
- 捕捉預覽
如果不能在影像捕捉中看到正在捕捉的場景,那麼應用程式使用者體驗就會很差。幸運的是框架定義了AVCaptureVideoPreviewLayer 類來滿足該需求。這樣就可以對捕捉的資料進行實時預覽。
通過一個特定的AVCaptureOutput型別的AVCaptureMetadataOutput可以實現人臉檢測功能.支援硬體加速以及同時對10個人臉進行實時檢測.
當使用人臉檢測時,會輸出一個具體的子類型別AVMetadataFaceObject,該型別定義了多個用於描述被檢測到的人臉的屬性,包括人臉的邊界(裝置座標系),以及斜傾角(roll angle,表示人頭部向肩膀方向的側傾角度)和偏轉角(yaw angle,表示人臉繞Y軸旋轉的角度).
實現程式碼如下:
- (void)viewDidLoad {
[super viewDidLoad];
_facesViewArr = [NSMutableArray arrayWithCapacity:0];
//1.獲取輸入裝置(攝像頭)
NSArray *devices = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInWideAngleCamera] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionBack].devices;
AVCaptureDevice *deviceF = devices[0];
// NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
// AVCaptureDevice *deviceF;
// for (AVCaptureDevice *device in devices )
// {
// if ( device.position == AVCaptureDevicePositionFront )
// {
// deviceF = device;
// break;
// }
// }
//2.根據輸入裝置建立輸入物件
AVCaptureDeviceInput*input = [[AVCaptureDeviceInput alloc] initWithDevice:deviceF error:nil];
//3.建立原資料的輸出物件
AVCaptureMetadataOutput *metaout = [[AVCaptureMetadataOutput alloc] init];
//4.設定代理監聽輸出物件輸出的資料,在主執行緒中重新整理
[metaout setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
self.session = [[AVCaptureSession alloc] init];
//5.設定輸出質量(高畫素輸出)
if ([self.session canSetSessionPreset:AVCaptureSessionPreset640x480]) {
[self.session setSessionPreset:AVCaptureSessionPreset640x480];
}
//6.新增輸入和輸出到會話
[self.session beginConfiguration];
if ([self.session canAddInput:input]) {
[self.session addInput:input];
}
if ([self.session canAddOutput:metaout]) {
[self.session addOutput:metaout];
}
[self.session commitConfiguration];
//7.告訴輸出物件要輸出什麼樣的資料,識別人臉, 最多可識別10張人臉
[metaout setMetadataObjectTypes:@[AVMetadataObjectTypeFace]];
AVCaptureSession *session = (AVCaptureSession *)self.session;
//8.建立預覽圖層
_previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session];
_previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
_previewLayer.frame = self.view.bounds;
[self.view.layer insertSublayer:_previewLayer atIndex:0];
//9.設定有效掃描區域(預設整個螢幕區域)(每個取值0~1, 以螢幕右上角為座標原點)
metaout.rectOfInterest = self.view.bounds;
//前置攝像頭一定要設定一下 要不然畫面是映象
for (AVCaptureVideoDataOutput* output in session.outputs) {
for (AVCaptureConnection * av in output.connections) {
//判斷是否是前置攝像頭狀態
if (av.supportsVideoMirroring) {
//映象設定
av.videoOrientation = AVCaptureVideoOrientationPortrait;
// av.videoMirrored = YES;
}
}
}
//10. 開始掃描
[self.session startRunning];
}
- (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
//當檢測到了人臉會走這個回撥
//幹掉舊畫框
for (UIView *faceView in self.facesViewArr) {
[faceView removeFromSuperview];
}
[self.facesViewArr removeAllObjects];
//轉換
for (AVMetadataFaceObject *faceobject in metadataObjects) {
AVMetadataObject *face = [self.previewLayer transformedMetadataObjectForMetadataObject:faceobject];
CGRect r = face.bounds;
//畫框
UIView *faceBox = [[UIView alloc] initWithFrame:r];
faceBox.layer.borderWidth = 3;
faceBox.layer.borderColor = [UIColor redColor].CGColor;
faceBox.backgroundColor = [UIColor clearColor];
[self.view addSubview:faceBox];
[self.facesViewArr addObject:faceBox];
}
}
複製程式碼
執行APP,效果如下:
本文四種方式實現的程式碼已放到Github,有需要的可以下載檢視。
關注我
歡迎關注公眾號:jackyshan,技術乾貨首發微信,第一時間推送。