引自維基百科計算機視覺是一門研究如何使機器「看」的科學,更進一步的說,就是指用攝影機和計算機代替人眼對目標進行識別、跟蹤和測量等機器視覺,並進一步作圖像處理,用計算機處理成爲更適合人眼觀察或傳送給儀器檢測的圖像。html
計算機視覺系統的結構形式很大程度上依賴於其具體應用方向。計算機視覺系統的具體實現方法同時也由其功能決定——是預先固定的抑或是在運行過程當中自動學習調整。儘管如此,計算機視覺的都須要具有如下處理步驟:ios
引自維基百科人臉識別,特指利用分析比較人臉視覺特徵信息進行身份鑑別的計算機技術。 廣義的人臉識別實際包括構建人臉識別系統的一系列相關技術,包括人臉圖像採集、人臉定位、人臉識別預處理、身份確認以及身份查找等;而狹義的人臉識別特指經過人臉進行身份確認或者身份查找的技術或系統。git
人臉識別是計算機視覺的一種應用,iOS中經常使用的有四種實現方式:CoreImage、Vision、OpenCV、AVFoundation,下面一一介紹幾種方式的實現步驟。github
自從 iOS 5(大概在2011年左右)以後,iOS 開始支持人臉識別,只是用的人很少。人臉識別 API 讓開發者不只能夠進行人臉檢測,還能識別微笑、眨眼等表情。算法
操做部分:數組
圖像部分:安全
實現代碼以下:bash
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 是 Apple 在 WWDC 2017 推出的圖像識別框架,它基於 Core ML,因此能夠理解成 Apple 的工程師設計了一種算法模型,而後利用 Core ML 訓練,最後整合成一個新的框架,相比開源模型而後讓開發者本身整合起來,這種方式更安全也更方便咱們使用。網絡
Vision 支持多種圖片類型,如:
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 (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 等平臺。
下面是在官方文檔中列出的最重要的模塊:
Mat
和其餘模塊須要的基本函數。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照片和視頻捕捉功能是從框架搭建之初就是它的強項。 從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,技術乾貨首發微信,第一時間推送。