iOS黑科技之(AVFoundation)動(dòng)態(tài)人臉識(shí)別(二)

iOS黑科技之(AVFoundation)動(dòng)態(tài)人臉識(shí)別(二)

上一篇介紹了Core Image實(shí)現(xiàn)的靜態(tài)人臉識(shí)別, 這里介紹AVFoundation的強(qiáng)大功能之一的動(dòng)態(tài)人臉識(shí)別

一. 首先介紹一些人臉識(shí)別的方式

1. CoreImage靜態(tài)人臉識(shí)別, 可識(shí)別照片, 圖像等

  • 詳情可查看上一篇博客介紹

3. OpenCV

  • 由一系列 C 函數(shù)和少量 C++ 類構(gòu)成, 實(shí)現(xiàn)了圖像處理和計(jì)算機(jī)視覺方面的很多通用算法, 其他的具體的不是很了解

4. Vision

  • Vision 是 Apple 在 WWDC 2017 伴隨iOS 11推出的基于CoreML的圖像識(shí)別框架
  • 根據(jù)Vision官方文檔看Vision 本身就有Face Detection and Recognition(人臉檢測識(shí)別)、Machine Learning Image Analysis(機(jī)器學(xué)習(xí)圖片分析)、Barcode Detection(條形碼檢測)、Text Detection(文本檢測)薄风。。丐一。卓练。。等等這些功能
  • 感興趣的同學(xué)可以查看相關(guān)文檔學(xué)習(xí)一下, 這里小編就不過多作介紹了

5. AVFoundation

  • 可以用來使用和創(chuàng)建基于時(shí)間的視聽媒體的框架
  • 這里我們使用的人臉識(shí)別方式也是使用AVFoundation框架

二. 對關(guān)鍵類的簡單介紹

1. AVCaptureDevice:代表硬件設(shè)備

  • 我們可以從這個(gè)類中獲取手機(jī)硬件的照相機(jī)晒哄、聲音傳感器等睁宰。
  • 當(dāng)我們在應(yīng)用程序中需要改變一些硬件設(shè)備的屬性(例如:切換攝像頭肪获、閃光模式改變、相機(jī)聚焦改變)的時(shí)候必須要先為設(shè)備加鎖柒傻,修改完成后解鎖孝赫。
  • 示例: 切換攝像頭
//4. 移除舊輸入,添加新輸入
//4.1 設(shè)備加鎖
session.beginConfiguration()
//4.2. 移除舊設(shè)備
session.removeInput(deviceIn)
//4.3 添加新設(shè)備
session.addInput(newVideoInput)
//4.4 設(shè)備解鎖
session.commitConfiguration()

2. AVCaptureDeviceInput:設(shè)備輸入數(shù)據(jù)管理對象

  • 可以根據(jù)AVCaptureDevice創(chuàng)建對應(yīng)的AVCaptureDeviceInput對象红符,
  • 該對象將會(huì)被添加到AVCaptureSession中管理,代表輸入設(shè)備青柄,它配置抽象硬件設(shè)備的ports。通常的輸入設(shè)備有(麥克風(fēng)预侯,相機(jī)等)

3. AVCaptureOutput: 代表輸出數(shù)據(jù)

  • 輸出的可以是圖片(AVCaptureStillImageOutput)或者視頻(AVCaptureMovieFileOutput

4. AVCaptureSession: 媒體(音致开、視頻)捕捉會(huì)話

  • 負(fù)責(zé)把捕捉的音頻視頻數(shù)據(jù)輸出到輸出設(shè)備中。
  • 一個(gè)AVCaptureSession可以有多個(gè)輸入或輸出萎馅。
  • 是連接AVCaptureInputAVCaptureOutput的橋梁喇喉,它協(xié)調(diào)input到output之間傳輸數(shù)據(jù)。
  • 它有startRunning和stopRunning兩種方法來開啟會(huì)話和結(jié)束會(huì)話校坑。
  • 每個(gè)session稱之為一個(gè)會(huì)話,也就是在應(yīng)用運(yùn)行過程中如果你需要改變會(huì)話的一些配置(例如:切換攝像頭),此時(shí)需要先開啟配置千诬,配置完成之后再提交配置耍目。

5. AVCaptureVideoPreviewLayer: 圖片預(yù)覽層

  • 我們的照片以及視頻是如何顯示在手機(jī)上的呢?那就是通過把這個(gè)對象添加到UIViewlayer上的

好了, 上面吧啦吧啦的說了那么多廢話, 那么我們的人臉識(shí)別究竟是怎樣實(shí)現(xiàn)的呢? 下面干貨來了

三. 添加掃描設(shè)備

  • 獲取設(shè)備(攝像頭)
  • 創(chuàng)建輸入設(shè)備
  • 創(chuàng)建掃描輸出
  • 創(chuàng)建捕捉回話

1. 輸出設(shè)備

  • 這里使用AVCaptureMetadataOutput, 可以掃描人臉, 二維碼, 條形碼等信息
  • 必須設(shè)置代理, 否則獲取不到掃描結(jié)果
  • 需要設(shè)置要輸出什么樣的數(shù)據(jù): face(人臉), qr(二維碼)等等
//3.創(chuàng)建原數(shù)據(jù)的輸出對象
let metadataOutput = AVCaptureMetadataOutput()
        
//4.設(shè)置代理監(jiān)聽輸出對象輸出的數(shù)據(jù)徐绑,在主線程中刷新
metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)

//7.告訴輸出對象要輸出什么樣的數(shù)據(jù),識(shí)別人臉, 最多可識(shí)別10張人臉
metadataOutput.metadataObjectTypes = [.face]

主要代碼如下:

fileprivate func addScaningVideo(){
    //1.獲取輸入設(shè)備(攝像頭)
    guard let device = AVCaptureDevice.default(for: .video) else { return }
    
    //2.根據(jù)輸入設(shè)備創(chuàng)建輸入對象
    guard let deviceIn = try? AVCaptureDeviceInput(device: device) else { return }
    deviceInput = deviceIn
    
    //3.創(chuàng)建原數(shù)據(jù)的輸出對象
    let metadataOutput = AVCaptureMetadataOutput()
    
    //4.設(shè)置代理監(jiān)聽輸出對象輸出的數(shù)據(jù)邪驮,在主線程中刷新
    metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
    //4.2 設(shè)置輸出代理
    faceDelegate = previewView
    
    //5.設(shè)置輸出質(zhì)量(高像素輸出)
    session.sessionPreset = .high
    
    //6.添加輸入和輸出到會(huì)話
    if session.canAddInput(deviceInput!) {
        session.addInput(deviceInput!)
    }
    if session.canAddOutput(metadataOutput) {
        session.addOutput(metadataOutput)
    }
    
    //7.告訴輸出對象要輸出什么樣的數(shù)據(jù),識(shí)別人臉, 最多可識(shí)別10張人臉
    metadataOutput.metadataObjectTypes = [.face]
    
    //8.創(chuàng)建預(yù)覽圖層
    previewLayer = AVCaptureVideoPreviewLayer(session: session)
    previewLayer.videoGravity = .resizeAspectFill
    previewLayer.frame = view.bounds
    previewView.layer.insertSublayer(previewLayer, at: 0)
    
    //9.設(shè)置有效掃描區(qū)域(默認(rèn)整個(gè)屏幕區(qū)域)(每個(gè)取值0~1, 以屏幕右上角為坐標(biāo)原點(diǎn))
    metadataOutput.rectOfInterest = previewView.bounds
    
    //10. 開始掃描
    if !session.isRunning {
        DispatchQueue.global().async {
            self.session.startRunning()
        }
    }
}

2. 切換攝像頭

  • 獲取當(dāng)前攝像頭方向
  • 創(chuàng)建新的輸入input
  • 移除舊輸入capture, 添加新的輸入capture
  • 具體代碼如下:
@IBAction func switchCameraAction(_ sender: Any) {
    //1. 執(zhí)行轉(zhuǎn)場動(dòng)畫
    let anima = CATransition()
    anima.type = "oglFlip"
    anima.subtype = "fromLeft"
    anima.duration = 0.5
    view.layer.add(anima, forKey: nil)
    
    //2. 獲取當(dāng)前攝像頭
    guard let deviceIn = deviceInput else { return }
    let position: AVCaptureDevice.Position = deviceIn.device.position == .back ? .front : .back
    
    //3. 創(chuàng)建新的input
    let deviceSession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: position)
    guard let newDevice = deviceSession.devices.filter({ $0.position == position }).first else { return }
    guard let newVideoInput = try? AVCaptureDeviceInput(device: newDevice) else { return }
    
    //4. 移除舊輸入,添加新輸入
    //4.1 設(shè)備加鎖
    session.beginConfiguration()
    //4.2. 移除舊設(shè)備
    session.removeInput(deviceIn)
    //4.3 添加新設(shè)備
    session.addInput(newVideoInput)
    //4.4 設(shè)備解鎖
    session.commitConfiguration()
    
    //5. 保存最新輸入
    deviceInput = newVideoInput
}

3. 處理掃描結(jié)果

實(shí)現(xiàn)AVCaptureMetadataOutputObjectsDelegate該協(xié)議的協(xié)議方法(只有一個(gè)方法)

//`metadataObjects`就是返回的掃描結(jié)果
optional public func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection)

4. AVMetadataFaceObject介紹

  • faceID: 人臉的唯一標(biāo)識(shí)
    • 掃描出來的每一個(gè)人, 有不同的faceID
    • 同一個(gè)人, 不同的狀態(tài)下(搖頭, 歪頭, 抬頭等), 都會(huì)有不同faceID
  • hasRollAngle: 是否有傾斜角,側(cè)傾角(左右歪頭)(BOOL類型)
  • rollAngle: 傾斜角,側(cè)傾角的角度(CGFloat類型)
  • hasYawAngle: 是否有偏轉(zhuǎn)角(左右搖頭)
  • yawAngle: 偏轉(zhuǎn)角角度

5. 處理掃描結(jié)果

5.1 獲取預(yù)覽圖層的人臉數(shù)組

  • 遍歷掃描的人臉數(shù)組, 轉(zhuǎn)換成在預(yù)覽圖層的人臉數(shù)組
  • 主要是人臉在圖層的左邊的轉(zhuǎn)換
  • 返回轉(zhuǎn)換后的新的數(shù)組
fileprivate func transformedFaces(faceObjs: [AVMetadataObject]) -> [AVMetadataObject] {
    var faceArr = [AVMetadataObject]()
    for face in faceObjs {
        //將掃描的人臉對象轉(zhuǎn)成在預(yù)覽圖層的人臉對象(主要是坐標(biāo)的轉(zhuǎn)換)
        if let transFace = previewLayer.transformedMetadataObject(for: face){
            faceArr.append(transFace)
        }
    }
    return faceArr
}

5.2 根據(jù)人臉位置添加紅框

  • 設(shè)置紅框的frame
faceLayer?.frame = face.bounds
  • 根據(jù)偏轉(zhuǎn)角和傾斜角的角度獲取CATransform3D
    fileprivate func transformDegress(yawAngle: CGFloat) -> CATransform3D {
        let yaw = degreesToRadians(degress: yawAngle)
        //圍繞Y軸旋轉(zhuǎn)
        let yawTran = CATransform3DMakeRotation(yaw, 0, -1, 0)
        //紅框旋轉(zhuǎn)問題
        return CATransform3DConcat(yawTran, CATransform3DIdentity)
    }
    
    //處理偏轉(zhuǎn)角問題
    fileprivate func transformDegress(rollAngle: CGFloat) -> CATransform3D {
        let roll = degreesToRadians(degress: rollAngle)
        //圍繞Z軸旋轉(zhuǎn)
        return CATransform3DMakeRotation(roll, 0, 0, 1)
    }
    
    //角度轉(zhuǎn)換
    fileprivate func degreesToRadians(degress: CGFloat) -> CGFloat{
        return degress * CGFloat(Double.pi) / 180
    }

  • 根據(jù)有無偏轉(zhuǎn)角和傾斜角旋轉(zhuǎn)紅框
//3.4 設(shè)置偏轉(zhuǎn)角(左右搖頭)
if face.hasYawAngle{
    let tranform3D = transformDegress(yawAngle: face.yawAngle)
    
    //矩陣處理
    faceLayer?.transform = CATransform3DConcat(faceLayer!.transform, tranform3D)
}

//3.5 設(shè)置傾斜角,側(cè)傾角(左右歪頭)
if face.hasRollAngle{
    let tranform3D = transformDegress(rollAngle: face.rollAngle)
    
    //矩陣處理
    faceLayer?.transform = CATransform3DConcat(faceLayer!.transform, tranform3D)
}

  • 至此, 動(dòng)態(tài)的人臉識(shí)別就完成了, 會(huì)在人臉位置增加紅框顯示, 并且紅框會(huì)根據(jù)人臉的位置動(dòng)態(tài)的, 實(shí)時(shí)的調(diào)整
  • 下面就快拿起你的相機(jī)測試吧

GitHub--Demo地址

  • 注意:
  • 這里只是列出了主要的核心代碼,具體的代碼邏輯請參考demo
  • 文中相關(guān)介紹有的地方如果有不是很詳細(xì)或者有更好建議的,歡迎聯(lián)系小編

其他相關(guān)文章


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末毅访,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子盘榨,更是在濱河造成了極大的恐慌喻粹,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件草巡,死亡現(xiàn)場離奇詭異守呜,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)山憨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門查乒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人郁竟,你說我怎么就攤上這事玛迄。” “怎么了棚亩?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵蓖议,是天一觀的道長虏杰。 經(jīng)常有香客問我,道長拒担,這世上最難降的妖魔是什么嘹屯? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮从撼,結(jié)果婚禮上州弟,老公的妹妹穿的比我還像新娘。我一直安慰自己低零,他們只是感情好婆翔,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著掏婶,像睡著了一般啃奴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上雄妥,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天最蕾,我揣著相機(jī)與錄音,去河邊找鬼老厌。 笑死瘟则,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的枝秤。 我是一名探鬼主播醋拧,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼淀弹!你這毒婦竟也來了丹壕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤薇溃,失蹤者是張志新(化名)和其女友劉穎菌赖,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體痊焊,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡盏袄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了薄啥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辕羽。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖垄惧,靈堂內(nèi)的尸體忽然破棺而出刁愿,到底是詐尸還是另有隱情,我是刑警寧澤到逊,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布铣口,位于F島的核電站滤钱,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏脑题。R本人自食惡果不足惜件缸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望叔遂。 院中可真熱鬧他炊,春花似錦、人聲如沸已艰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽哩掺。三九已至凿叠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嚼吞,已是汗流浹背盒件。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留舱禽,地道東北人履恩。 一個(gè)月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像呢蔫,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子飒筑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容