iOS基于CoreImage实现人脸检测(附上demo)

本文介绍了iOS基于CoreImage的CIDetector进行人脸检测, 并附上相关demo。

  • demo 展示效果:


    image.png

demo地址-->swfit完整项目2020持续更新完善

  • 注意本章的难点在于绘制人脸框, CIDetector对Image检测出来的frame坐标系是y轴向上的, 而绘制的人脸框需要的坐标系是y轴向下的.😝 网上很多博客在转换坐标系这块没有处理好, 详情请参考demo.

CoreImage是一种图像处理和分析技术,为静态和视频图像提供近实时处理。它使用GPU或CPU渲染路径,对Core Graphics,Core Video和Image I / O框架中的图像数据类型进行操作。
CoreImage封装了低级图形处理的细节(OpenGL,Op​​enGL ES或Metal, GCD), 直接提供API, 为开发者提高了开发效率.

image.png

详细流程可以查看demo, 而且代码注释已经十分清晰:

1. 展示Controller

import UIKit
import SnapKit

class FaceDetectionViewController: BaseViewController {
    
    lazy var imageView: UIImageView = {
        let imageview = UIImageView.init()
        imageview.backgroundColor = .lightGray
        imageview.contentMode = .scaleAspectFit
        return imageview
    }()
    lazy var faceCountLabel: UILabel = {
        let label = UILabel.init()
        label.backgroundColor = .green
        label.text = "识别到0个人脸"
        return label
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.view.backgroundColor = .white

        setUI()
        
    }
    
    private func setUI() {
        
        let chosePicBtn = UIButton(type: .custom)
        chosePicBtn.setTitle("选择照片", for: .normal)
        chosePicBtn.backgroundColor = .orange
        chosePicBtn.addTarget(self, action: #selector(choosePic), for: .touchUpInside)
        
        let detectBtn = UIButton(type: .custom)
        detectBtn.setTitle("开始检测", for: .normal)
        detectBtn.backgroundColor = .orange
        detectBtn.addTarget(self, action: #selector(startDetect), for: .touchUpInside)
        
        view.addSubviews([imageView, chosePicBtn, detectBtn, faceCountLabel])
        
        imageView.snp.makeConstraints { (make) in
            make.left.equalToSuperview().offset(autoWidth(30))
            make.top.equalToSuperview().offset(autoWidth(30) + LayoutTool.topSafeInset + 44)
            make.right.equalToSuperview().offset(autoWidth(-30))
            make.height.equalTo(autoHeihgt(300))
        }
        
        faceCountLabel.snp.makeConstraints { (make) in
            make.top.equalTo(imageView.snp.bottom).offset(5)
            make.left.right.equalTo(imageView)
            make.height.equalTo(autoHeihgt(20))
        }
        
        chosePicBtn.snp.makeConstraints { (make) in
            make.top.equalTo(faceCountLabel.snp.bottom).offset(30)
            make.left.right.equalTo(imageView)
            make.height.equalTo(autoHeihgt(45))
        }
        
        detectBtn.snp.makeConstraints { (make) in
            make.top.equalTo(chosePicBtn.snp.bottom).offset(20)
            make.left.right.equalTo(imageView)
            make.height.equalTo(autoHeihgt(45))
        }
        

        
        
    }
    
    // MARK: - 开始检测人脸
    @objc private func startDetect() {
        
        FaceRecognition.startRecognize(imageView: imageView) { (count) in
            print("**********************************************************检测到\(count)个人脸")
            self.faceCountLabel.text = "检测到\(count)个人脸"
        }
        
    }
    
    // MARK: - 选择相册图片
    @objc private func choosePic() {
        
        //1. 判断是否允许该操作
        if !UIImagePickerController.isSourceTypeAvailable(.photoLibrary) {
            print("用户没开访问相册权限, 请到设置里面开启")
            return
        }
        
        //创建照片选择器
        let imagePC = UIImagePickerController()
        //设置数据源
        imagePC.sourceType = .photoLibrary

        imagePC.delegate = self
        present(imagePC, animated: true, completion: nil)
    }

}

//MARK: 实现相册代理
extension FaceDetectionViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        //获取选中的图片
        guard let selectorImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage else { return }
        imageView.image = selectorImage
        picker.dismiss(animated: true, completion: nil)
    }
    
    //选取完成后调用
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        picker.dismiss(animated: true, completion: nil)
    }
}

2. 检测类

import UIKit

class FaceRecognition: NSObject {

    
    static func startRecognize(imageView: UIImageView, result: @escaping (_ faceCount: Int) -> ()) {
        //删除子控件
        imageView.removeSubviews()
        
       
        guard let image = imageView.image else { return }
        guard let ciImage = CIImage(image: image) else {return}
        
        //检测质量参数
        let options = [CIDetectorAccuracy: CIDetectorAccuracyHigh]
        //检测执行者
        let detector = CIDetector(ofType: CIDetectorTypeFace, context: CIContext(), options: options)
        //检测结果
        guard let faceResultArr = detector?.features(in: ciImage) else {return}
        //返回检测结果
        result(faceResultArr.count)
        //添加人脸框框到imageview
        addBorderView(imageView: imageView, image: image, faceResultArr: faceResultArr)
        
    }
    
    static func addBorderView(imageView: UIImageView, image: UIImage, faceResultArr: [CIFeature]) {

        // 添加识别的红框
        let borderView = UIView(frame: CGRect(x: 0, y: 0, width: imageView.frame.width, height: imageView.frame.height))
        imageView.addSubview(borderView)
        
        //遍历扫描结果
        for faceFeature in faceResultArr {
            
            print("************************************************************image 的size: \(image.size)")
            print("************************************************************imageview 的frame: \(imageView.frame)")
            print("************************************************************faceFeature 的bounds: \(faceFeature.bounds)")
            
            //拿到缩放比例 //imageView和image的尺寸比例 按照imageview.contentMode = .scaleAspectFit
             let scale = getScale(imageView: imageView, image: image)

             //人脸相对于缩放后的image的位置
             let faceOriginX: CGFloat = faceFeature.bounds.minX / scale
             let faceOriginY: CGFloat = faceFeature.bounds.minY / scale
             let faceOriginW: CGFloat =   faceFeature.bounds.width / scale
             let faceOriginH: CGFloat =   faceFeature.bounds.height / scale
             
             //缩放后的image在imageView的frame
             let imageY = (imageView.frame.height - (image.size.height / scale)) * 0.5
             let imageX = (imageView.frame.width - (image.size.width / scale)) * 0.5
             //let imageW = image.size.width / scale
             let imageH = image.size.height / scale
             
             //人脸相对新的image的frame
             let newy = imageH - faceOriginH - faceOriginY
             let newx = faceOriginX
             let neww = faceOriginW
             let newh = faceOriginH
             
             //人脸相对于imageview的frame
             let y0 = newy + imageY
             let x0 = newx + imageX
             let w0 = neww
             let h0 = newh
             

             let newrect = CGRect(x: x0, y: y0, width: w0, height: h0)
             let view = addRedrectangleView(rect: newrect)
            borderView.addSubview(view)
            
            //如果识别到眼睛
            //guard let feature = faceFeature as? CIFaceFeature else { return }
            //左眼 : feature.hasLeftEyePosition  右眼:feature.hasRightEyePosition 嘴巴: feature.hasMouthPosition
        }
    }
    
    // MARK: - 框框的绘制
    static func addRedrectangleView(rect: CGRect) -> UIView{
        let redView = UIView(frame: rect)
        redView.layer.borderColor = UIColor.red.cgColor
        redView.layer.borderWidth = 1
        return redView
    }

}


//imageView和image的尺寸比例 按照imageview.contentMode = .scaleAspectFit
extension FaceRecognition {
    
    //imageView.contentMode = .scaleAspectFit
    static func getScale(imageView: UIImageView, image: UIImage) -> CGFloat{
        let viewSize = imageView.frame.size
        let imageSize = image.size
        
        let widthScale = imageSize.width / viewSize.width
        let heightScale = imageSize.height / viewSize.height
        
        return widthScale > heightScale ? widthScale : heightScale
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342