簡介
引自維基百科計(jì)算機(jī)視覺是一門研究如何使機(jī)器“看”的科學(xué),更進(jìn)一步的說,就是指用攝影機(jī)和計(jì)算機(jī)代替人眼對(duì)目標(biāo)進(jìn)行識(shí)別、跟蹤和測量等機(jī)器視覺,并進(jìn)一步做圖像處理,用計(jì)算機(jī)處理成為更適合人眼觀察或傳送給儀器檢測的圖像。
計(jì)算機(jī)視覺系統(tǒng)的結(jié)構(gòu)形式很大程度上依賴于其具體應(yīng)用方向。計(jì)算機(jī)視覺系統(tǒng)的具體實(shí)現(xiàn)方法同時(shí)也由其功能決定——是預(yù)先固定的抑或是在運(yùn)行過程中自動(dòng)學(xué)習(xí)調(diào)整。盡管如此,計(jì)算機(jī)視覺的都需要具備以下處理步驟:
引自維基百科人臉識(shí)別,特指利用分析比較人臉視覺特征信息進(jìn)行身份鑒別的計(jì)算機(jī)技術(shù)。
廣義的人臉識(shí)別實(shí)際包括構(gòu)建人臉識(shí)別系統(tǒng)的一系列相關(guān)技術(shù),包括人臉圖像采集、人臉定位、人臉識(shí)別預(yù)處理、身份確認(rèn)以及身份查找等;而狹義的人臉識(shí)別特指通過人臉進(jìn)行身份確認(rèn)或者身份查找的技術(shù)或系統(tǒng)。
人臉識(shí)別是計(jì)算機(jī)視覺的一種應(yīng)用,iOS中常用的有四種實(shí)現(xiàn)方式:CoreImage、Vision、OpenCV、AVFoundation,下面一一介紹幾種方式的實(shí)現(xiàn)步驟。
CoreImage
自從 iOS 5(大概在2011年左右)之后,iOS 開始支持人臉識(shí)別,只是用的人不多。人臉識(shí)別 API 讓開發(fā)者不僅可以進(jìn)行人臉檢測,還能識(shí)別微笑、眨眼等表情。
操作部分:
- 濾鏡(CIFliter):CIFilter 產(chǎn)生一個(gè)CIImage。典型的,接受一到多的圖片作為輸入,經(jīng)過一些過濾操作,產(chǎn)生指定輸出的圖片。
- 檢測(CIDetector):CIDetector 檢測處理圖片的特性,如使用來檢測圖片中人臉的眼睛、嘴巴、等等。
- 特征(CIFeature):CIFeature 代表由 detector處理后產(chǎn)生的特征。
圖像部分:
- 畫布(CIContext):畫布類可被用與處理Quartz 2D 或者 OpenGL。可以用它來關(guān)聯(lián)CoreImage類。如濾鏡、顏色等渲染處理。
- 顏色(CIColor): 圖片的關(guān)聯(lián)與畫布、圖片像素顏色的處理。
- 向量(CIVector): 圖片的坐標(biāo)向量等幾何方法處理。
- 圖片(CIImage): 代表一個(gè)圖像,可代表關(guān)聯(lián)后輸出的圖像。
實(shí)現(xiàn)代碼如下:
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)
// 轉(zhuǎn)換坐標(biāo)系
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)")
// 應(yīng)用變換轉(zhuǎn)換坐標(biāo)
var faceViewBounds = face.bounds.applying(transform)
// 在圖像視圖中計(jì)算矩形的實(shí)際位置和大小
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)")
}
}
運(yùn)行APP,效果如下:
Vision
Vision 是 Apple 在 WWDC 2017 推出的圖像識(shí)別框架,它基于 Core ML,所以可以理解成 Apple 的工程師設(shè)計(jì)了一種算法模型,然后利用 Core ML 訓(xùn)練,最后整合成一個(gè)新的框架,相比開源模型然后讓開發(fā)者自己整合起來,這種方式更安全也更方便我們使用。
Vision 支持多種圖片類型,如:
- CIImage
- NSURL
- NSData
- CGImageRef
- CVPixelBufferRef
Vision使用中的角色有:
Request,RequestHandler,results和results中的Observation數(shù)組。
Request類型:
有很多種,比如圖中列出的 人臉識(shí)別、特征識(shí)別、文本識(shí)別、二維碼識(shí)別等。
使用概述:
我們?cè)谑褂眠^程中是給各種功能的 Request 提供給一個(gè) RequestHandler,Handler 持有需要識(shí)別的圖片信息,并將處理結(jié)果分發(fā)給每個(gè) Request 的 completion Block 中。可以從 results 屬性中得到 Observation 數(shù)組。
observations數(shù)組中的內(nèi)容根據(jù)不同的request請(qǐng)求返回了不同的observation,如:VNFaceObservation、VNTextObservation、VNBarcodeObservation、VNHorizonObservation,不同的Observation都繼承于VNDetectedObjectObservation,而VNDetectedObjectObservation則是繼承于VNObservation。每種Observation有boundingBox,landmarks等屬性,存儲(chǔ)的是識(shí)別后物體的坐標(biāo),點(diǎn)位等,我們拿到坐標(biāo)后,就可以進(jìn)行一些UI繪制。
代碼實(shí)現(xiàn)如下:
@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)
//遍歷所有識(shí)別結(jié)果
for item in result {
//標(biāo)注框
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
}
運(yùn)行APP,效果如下:
OpenCV
OpenCV (Open Source Computer Vision Library)是一個(gè)在BSD許可下發(fā)布的開源庫,因此它是免費(fèi)提供給學(xué)術(shù)和商業(yè)用途。有C++、C、Python和Java接口,支持Windows、Linux、MacOS、iOS和Android等系統(tǒng)。OpenCV是為計(jì)算效率而設(shè)計(jì)的,而且密切關(guān)注實(shí)時(shí)應(yīng)用程序的發(fā)展和支持。該庫用優(yōu)化的C/C++編寫,可以應(yīng)用于多核處理。在啟用OpenCL的基礎(chǔ)上,它可以利用底層的異構(gòu)計(jì)算平臺(tái)的硬件加速。
OpenCV 起始于 1999 年 Intel 的一個(gè)內(nèi)部研究項(xiàng)目。從那時(shí)起,它的開發(fā)就一直很活躍。進(jìn)化到現(xiàn)在,它已支持如 OpenCL 和 OpenGL 等現(xiàn)代技術(shù),也支持如 iOS 和 Android 等平臺(tái)。
下面是在官方文檔中列出的最重要的模塊:
-
core:簡潔的核心模塊,定義了基本的數(shù)據(jù)結(jié)構(gòu),包括稠密多維數(shù)組
Mat
和其他模塊需要的基本函數(shù)。 - imgproc:圖像處理模塊,包括線性和非線性圖像濾波、幾何圖像轉(zhuǎn)換 (縮放、仿射與透視變換、一般性基于表的重映射)、顏色空間轉(zhuǎn)換、直方圖等等。
- video:視頻分析模塊,包括運(yùn)動(dòng)估計(jì)、背景消除、物體跟蹤算法。
- calib3d:包括基本的多視角幾何算法、單體和立體相機(jī)的標(biāo)定、對(duì)象姿態(tài)估計(jì)、雙目立體匹配算法和元素的三維重建。
- features2d:包含了顯著特征檢測算法、描述算子和算子匹配算法。
- objdetect:物體檢測和一些預(yù)定義的物體的檢測 (如人臉、眼睛、杯子、人、汽車等)。
- ml:多種機(jī)器學(xué)習(xí)算法,如 K 均值、支持向量機(jī)和神經(jīng)網(wǎng)絡(luò)。
- highgui:一個(gè)簡單易用的接口,提供視頻捕捉、圖像和視頻編碼等功能,還有簡單的 UI 接口 (iOS 上可用的僅是其一個(gè)子集)。
- gpu:OpenCV 中不同模塊的 GPU 加速算法 (iOS 上不可用)。
- ocl:使用 OpenCL 實(shí)現(xiàn)的通用算法 (iOS 上不可用)。
- 一些其它輔助模塊,如 Python 綁定和用戶貢獻(xiàn)的算法。
cv::Mat 是 OpenCV 的核心數(shù)據(jù)結(jié)構(gòu),用來表示任意 N 維矩陣。因?yàn)閳D像只是 2 維矩陣的一個(gè)特殊場景,所以也是使用 cv::Mat 來表示的。也就是說,cv::Mat 將是你在 OpenCV 中用到最多的類。
如前面所說,OpenCV 是一個(gè) C++ 的 API,因此不能直接在 Swift 和 Objective-C 代碼中使用,但能在 Objective-C++ 文件中使用。
Objective-C++ 是 Objective-C 和 C++ 的混合物,讓你可以在 Objective-C 類中使用 C++ 對(duì)象。clang 編譯器會(huì)把所有后綴名為 .mm
的文件都當(dāng)做是 Objective-C++。一般來說,它會(huì)如你所期望的那樣運(yùn)行,但還是有一些使用 Objective-C++ 的注意事項(xiàng)。內(nèi)存管理是你最應(yīng)該格外注意的點(diǎn),因?yàn)?ARC 只對(duì) Objective-C 對(duì)象有效。當(dāng)你使用一個(gè) C++ 對(duì)象作為類屬性的時(shí)候,其唯一有效的屬性就是 assign
。因此,你的 dealloc
函數(shù)應(yīng)確保 C++ 對(duì)象被正確地釋放了。
第二重要的點(diǎn)就是,如果你在 Objective-C++ 頭文件中引入了 C++ 頭文件,當(dāng)你在工程中使用該 Objective-C++ 文件的時(shí)候就泄露了 C++ 的依賴。任何引入你的 Objective-C++ 類的 Objective-C 類也會(huì)引入該 C++ 類,因此該 Objective-C 文件也要被聲明為 Objective-C++ 的文件。這會(huì)像森林大火一樣在工程中迅速蔓延。所以,應(yīng)該把你引入 C++ 文件的地方都用 #ifdef __cplusplus
包起來,并且只要可能,就盡量只在 .mm
實(shí)現(xiàn)文件中引入 C++ 頭文件。
要獲得更多如何混用 C++ 和 Objective-C 的細(xì)節(jié),請(qǐng)查看 Matt Galloway 寫的這篇教程。
基于OpenCV,iOS應(yīng)用程序可以實(shí)現(xiàn)很多有趣的功能,也可以把很多復(fù)雜的工作簡單化。一般可用于:
- 對(duì)圖片進(jìn)行灰度處理
- 人臉識(shí)別,即特征跟蹤
- 訓(xùn)練圖片特征庫(可用于模式識(shí)別)
- 提取特定圖像內(nèi)容(根據(jù)需求還原有用圖像信息)
代碼實(shí)現(xiàn)如下:
- (void)viewDidLoad {
[super viewDidLoad];
//預(yù)設(shè)置face探測的參數(shù)
[self preSetFace];
//image轉(zhuǎn)mat
cv::Mat mat;
UIImageToMat(self.imageView.image, mat);
//執(zhí)行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;
//轉(zhuǎn)換為灰度圖像
cv::cvtColor(inputImage, frame_gray, CV_BGR2GRAY);
//圖像均衡化
cv::equalizeHist(frame_gray, frame_gray);
//分類器識(shí)別
_faceDetector.detectMultiScale(frame_gray, _faceRects,1.1,2,0,cv::Size(30,30));
vector<cv::Rect> faces;
faces = _faceRects;
// 在每個(gè)人臉上畫一個(gè)紅色四方形
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;
}
運(yùn)行APP,效果如下:
AVFoundation
AVFoundation照片和視頻捕捉功能是從框架搭建之初就是它的強(qiáng)項(xiàng)。 從iOS 4.0 我們就可以直接訪問iOS的攝像頭和攝像頭生成的數(shù)據(jù)(照片、視頻)。目前捕捉功能仍然是蘋果公司媒體工程師最關(guān)注的領(lǐng)域。
AVFoundation實(shí)現(xiàn)視頻捕捉的步驟如下:
- 捕捉設(shè)備
AVCaptureDevice為攝像頭、麥克風(fēng)等物理設(shè)備提供接口。大部分我們使用的設(shè)備都是內(nèi)置于MAC或者iPhone、iPad上的。當(dāng)然也可能出現(xiàn)外部設(shè)備。但是AVCaptureDevice 針對(duì)物理設(shè)備提供了大量的控制方法。比如控制攝像頭聚焦、曝光、白平衡、閃光燈等。
- 捕捉設(shè)備的輸入
注意:為捕捉設(shè)備添加輸入,不能添加到AVCaptureSession 中,必須通過將它封裝到一個(gè)AVCaptureDeviceInputs實(shí)例中。這個(gè)對(duì)象在設(shè)備輸出數(shù)據(jù)和捕捉會(huì)話間扮演接線板的作用。
- 捕捉設(shè)備的輸出
AVCaptureOutput 是一個(gè)抽象類。用于為捕捉會(huì)話得到的數(shù)據(jù)尋找輸出的目的地。框架定義了一些抽象類的高級(jí)擴(kuò)展類。例如 AVCaptureStillImageOutput 和 AVCaptureMovieFileOutput類。使用它們來捕捉靜態(tài)照片、視頻。例如 AVCaptureAudioDataOutput 和 AVCaptureVideoDataOutput ,使用它們來直接訪問硬件捕捉到的數(shù)字樣本。
- 捕捉連接
AVCaptureConnection類.捕捉會(huì)話先確定由給定捕捉設(shè)備輸入渲染的媒體類型,并自動(dòng)建立其到能夠接收該媒體類型的捕捉輸出端的連接。
- 捕捉預(yù)覽
如果不能在影像捕捉中看到正在捕捉的場景,那么應(yīng)用程序用戶體驗(yàn)就會(huì)很差。幸運(yùn)的是框架定義了AVCaptureVideoPreviewLayer 類來滿足該需求。這樣就可以對(duì)捕捉的數(shù)據(jù)進(jìn)行實(shí)時(shí)預(yù)覽。
通過一個(gè)特定的AVCaptureOutput類型的AVCaptureMetadataOutput可以實(shí)現(xiàn)人臉檢測功能.支持硬件加速以及同時(shí)對(duì)10個(gè)人臉進(jìn)行實(shí)時(shí)檢測.
當(dāng)使用人臉檢測時(shí),會(huì)輸出一個(gè)具體的子類類型AVMetadataFaceObject,該類型定義了多個(gè)用于描述被檢測到的人臉的屬性,包括人臉的邊界(設(shè)備坐標(biāo)系),以及斜傾角(roll angle,表示人頭部向肩膀方向的側(cè)傾角度)和偏轉(zhuǎn)角(yaw angle,表示人臉繞Y軸旋轉(zhuǎn)的角度).
實(shí)現(xiàn)代碼如下:
- (void)viewDidLoad {
[super viewDidLoad];
_facesViewArr = [NSMutableArray arrayWithCapacity:0];
//1.獲取輸入設(shè)備(攝像頭)
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.根據(jù)輸入設(shè)備創(chuàng)建輸入對(duì)象
AVCaptureDeviceInput*input = [[AVCaptureDeviceInput alloc] initWithDevice:deviceF error:nil];
//3.創(chuàng)建原數(shù)據(jù)的輸出對(duì)象
AVCaptureMetadataOutput *metaout = [[AVCaptureMetadataOutput alloc] init];
//4.設(shè)置代理監(jiān)聽輸出對(duì)象輸出的數(shù)據(jù),在主線程中刷新
[metaout setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
self.session = [[AVCaptureSession alloc] init];
//5.設(shè)置輸出質(zhì)量(高像素輸出)
if ([self.session canSetSessionPreset:AVCaptureSessionPreset640x480]) {
[self.session setSessionPreset:AVCaptureSessionPreset640x480];
}
//6.添加輸入和輸出到會(huì)話
[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.告訴輸出對(duì)象要輸出什么樣的數(shù)據(jù),識(shí)別人臉, 最多可識(shí)別10張人臉
[metaout setMetadataObjectTypes:@[AVMetadataObjectTypeFace]];
AVCaptureSession *session = (AVCaptureSession *)self.session;
//8.創(chuàng)建預(yù)覽圖層
_previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session];
_previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
_previewLayer.frame = self.view.bounds;
[self.view.layer insertSublayer:_previewLayer atIndex:0];
//9.設(shè)置有效掃描區(qū)域(默認(rèn)整個(gè)屏幕區(qū)域)(每個(gè)取值0~1, 以屏幕右上角為坐標(biāo)原點(diǎn))
metaout.rectOfInterest = self.view.bounds;
//前置攝像頭一定要設(shè)置一下 要不然畫面是鏡像
for (AVCaptureVideoDataOutput* output in session.outputs) {
for (AVCaptureConnection * av in output.connections) {
//判斷是否是前置攝像頭狀態(tài)
if (av.supportsVideoMirroring) {
//鏡像設(shè)置
av.videoOrientation = AVCaptureVideoOrientationPortrait;
// av.videoMirrored = YES;
}
}
}
//10. 開始掃描
[self.session startRunning];
}
- (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
//當(dāng)檢測到了人臉會(huì)走這個(gè)回調(diào)
//干掉舊畫框
for (UIView *faceView in self.facesViewArr) {
[faceView removeFromSuperview];
}
[self.facesViewArr removeAllObjects];
//轉(zhuǎn)換
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];
}
}
運(yùn)行APP,效果如下:
本文四種方式實(shí)現(xiàn)的代碼已放到Github,有需要的可以下載查看。