一、打开CreatML,选择新建一个Image Classification

350046b5ade14b30af76a1482f74d22c.png

二、将数据分类好存放在文件夹,然后扔到把打包好的文件夹扔到training data里面

f89683b086ae496b9874344b666e007e.png

 三、等待训练完成后,可以在training界面看模型的准确率,也可以把模型没见过的图片丢到Evaluation里面测试。

四、点击Output -> get 导出模型为mlmodel格式,再把模型文件添加到Xcode工程里。

五、所有用到的变量:

  • captureSession:用于控制摄像头会话的AVCaptureSession实例。
  • videoOutput:用于输出视频数据的AVCaptureVideoDataOutput实例。
  • videoDataOutputQueue:一个串行队列,用于在接收视频数据时执行回调。
  • previewLayer:用于在预览中显示摄像头视频的AVCaptureVideoPreviewLayer实例。
  • resultLabel:用于在界面上显示识别结果的UILabel实例。
  • model:CoreML模型,定义为Megumi。
  • lastIdentifier:用于存储上次识别结果的标识符。
  • consecutiveCont:用于跟踪连续识别相同结果的次数。
  • requiredConsecutiveCount:设置连续识别相同结果所需的次数阈值。

六、setupCamera方法,用于设置和启动摄像头:

 1. 初始化摄像头会话、视频输出、数据输出队列和预览图层

 

        captureSession = AVCaptureSession()
        videoOutput = AVCaptureVideoDataOutput()
        videoDataOutputQueue = DispatchQueue(label: "videoDataOutputQueue")
        previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)

2.设置预览图层的框架和视频重力,并将预览图层添加到视图的层中。

        previewLayer.frame = view.bounds
        previewLayer.videoGravity = .resizeAspectFill
        view.layer.addSublayer(previewLayer)

3.尝试获取默认视图设备,否则返回

        guard let camera = AVCaptureDevice.default(for: .video) else { return }

4.创建一个AVCaptureDeviceInput实例,将它添加到摄像头会话中

        do {
            let input = try AVCaptureDeviceInput(device: camera)
            if captureSession.canAddInput(input) {
                captureSession.addInput(input)
            }
        } catch {
            print("Error creating video device input: \(error)")
        }

5.如果摄像头会话可以添加视频输出,则设置样本缓冲区代理并添加视频输出。

DispatchQueue.global(qos: .userInitiated).async {
            self.captureSession.startRunning()
        }

七、设置结果标签

1.初始化标签并且设置他的位置文本等属性,然后添加到视图中

八、captureOutput方法:在摄像头输出新的样本缓冲区时调用:

1.尝试从样本缓冲区获得图像缓冲区,否则返回

        guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }

2.尝试将CoreML模型转化成VNCoreMLModel,失败则报错并返回

        guard let vnModel = try? VNCoreMLModel(for: model.model) else {
            print("Failed to load the model")
            return
        }

3.创建一个VNCoreMLRequest实例,用于执行模型推理,并设置一个完成处理程序。

        let request = VNCoreMLRequest(model: vnModel, completionHandler: { [weak self] (request, error) in
            guard let strongSelf = self else { return }
            if let error = error {
                print("Error during VNCoreMLRequest: \(error)")
                return
            }
            guard let results = request.results as? [VNClassificationObservation], let topResult = results.first else {
                return
            }

在完成处理程序中,检查是否有错误,并将结果转换为VNClassificationObservation数组,如果失败则返回。

4.设置默认的LabelText,检查顶部结果置信度

            var labelText = "No Result"
            if topResult.confidence > 0.8 {

5.针对不同的识别结果设置不同的LabelText,如果置信度不高则提示错误信息

                switch topResult.identifier {
                case "木制品":
                    labelText = "我恨你像块木头"
                case "金属":
                    labelText = "Fe、Cu、Al"
                case "皮毛":
                    labelText = "蛋白质是牛肉的六倍"
                case "电子产品":
                    labelText = "Ag、Au、Cu、Fe、Al"
                case "玻璃":
                    labelText = "Si、O"
                case "食物":
                    labelText = "糖类、脂肪、蛋白质"
                case "石":
                    labelText = "Ca"
                default:
                    labelText = "瓦达西希腊奶"
                }
            } else {
                labelText = "猪脑过载中"
            }

6.更新连续识别计数,只有在达到设置的连续识别次数才会更新LabelText

            if strongSelf.lastIdentifier == topResult.identifier{
                strongSelf.consecutiveCont += 1
            } else {
                strongSelf.consecutiveCont = 1
                strongSelf.lastIdentifier = topResult.identifier
            }
            
            if strongSelf.consecutiveCont >= strongSelf.requiredConsecutiveCount{
                DispatchQueue.main.async {
                    strongSelf.resultLabel.text = labelText
                }
            }

7.创建一个VNImageRequestHandler实例来执行请求

        })
        
        let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, orientation: .up, options: [:])
        do {
            try handler.perform([request])
        } catch {
            print("Error performing Vision request: \(error)")
        }
    }
}

完整代码如下(完整代码里还给Label加了识别点击手势跳转界面功能正文里我懒得写了):

tips:一定要在info里添加Privacy - Camera Usage Description的key来申请使用摄像头,不然白瞎了搞那么久

 

import UIKit
import AVFoundation
import CoreML
import Vision

class ViewController: UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate {
    var captureSession: AVCaptureSession!
    var videoOutput: AVCaptureVideoDataOutput!
    var videoDataOutputQueue: DispatchQueue!
    var previewLayer: AVCaptureVideoPreviewLayer!
    var resultLabel: UILabel!
    let model = Megumi()
    var lastIdentifier: String?
    var consecutiveCont: Int = 0
    let requiredConsecutiveCount = 3

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .black
        setupCamera()
        setupResultLabel()
    }

    func setupCamera() {
        captureSession = AVCaptureSession()
        videoOutput = AVCaptureVideoDataOutput()
        videoDataOutputQueue = DispatchQueue(label: "videoDataOutputQueue")
        previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)

        // 设置摄像头和预览图层
        previewLayer.frame = view.bounds
        previewLayer.videoGravity = .resizeAspectFill // 设置视频重力
        view.layer.addSublayer(previewLayer)

        // 获取摄像头设备并创建输入
        guard let camera = AVCaptureDevice.default(for: .video) else { return }
        do {
            let input = try AVCaptureDeviceInput(device: camera)
            if captureSession.canAddInput(input) {
                captureSession.addInput(input)
            }
        } catch {
            print("Error creating video device input: \(error)")
        }

        if captureSession.canAddOutput(videoOutput) {
            videoOutput.setSampleBufferDelegate(self, queue: videoDataOutputQueue)
            captureSession.addOutput(videoOutput)
        }

        // 启动摄像头会话
        DispatchQueue.global(qos: .userInitiated).async {
            self.captureSession.startRunning()
        }
    }

    func setupResultLabel() {
        resultLabel = UILabel()
        resultLabel.frame = CGRect(x: 20, y: view.frame.height - 50, width: view.frame.width - 40, height: 30)
        resultLabel.textColor = .white
        resultLabel.textAlignment = .center
        view.addSubview(resultLabel)

        resultLabel.isUserInteractionEnabled = true
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(labelTapped))
        resultLabel.addGestureRecognizer(tapGesture)
    }
    
    @objc func labelTapped() {
        if resultLabel.text != "猪脑过载中" {
            let nextViewController = ViewController2()
            nextViewController.modalPresentationStyle = .fullScreen
            present(nextViewController, animated: true, completion: nil)
        }
    }

    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
        guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }

        // 将Core ML模型转换为VNCoreMLModel
        guard let vnModel = try? VNCoreMLModel(for: model.model) else {
            print("Failed to load the model")
            return
        }

        let request = VNCoreMLRequest(model: vnModel, completionHandler: { [weak self] (request, error) in
            guard let strongSelf = self else { return }
            if let error = error {
                print("Error during VNCoreMLRequest: \(error)")
                return
            }
            guard let results = request.results as? [VNClassificationObservation], let topResult = results.first else {
                return
            }

            // 根据分类结果设置自定义文本
            var labelText = "No Result"
            if topResult.confidence > 0.8 {
                // 根据不同的分类结果设置不同的文本内容
                switch topResult.identifier {
                case "木制品":
                    labelText = "我恨你像块木头"
                case "金属":
                    labelText = "Fe、Cu、Al"
                case "皮毛":
                    labelText = "蛋白质是牛肉的六倍"
                case "电子产品":
                    labelText = "Ag、Au、Cu、Fe、Al"
                case "玻璃":
                    labelText = "Si、O"
                case "食物":
                    labelText = "糖类、脂肪、蛋白质"
                case "石":
                    labelText = "Ca"
                default:
                    labelText = "瓦达西希腊奶"
                }
            } else {
                labelText = "猪脑过载中"
            }

            // 更新UI
            if strongSelf.lastIdentifier == topResult.identifier{
                strongSelf.consecutiveCont += 1
            } else {
                strongSelf.consecutiveCont = 1
                strongSelf.lastIdentifier = topResult.identifier
            }
            
            if strongSelf.consecutiveCont >= strongSelf.requiredConsecutiveCount{
                DispatchQueue.main.async {
                    strongSelf.resultLabel.text = labelText
                }
            }
                
        })
        
        let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, orientation: .up, options: [:])
        do {
            try handler.perform([request])
        } catch {
            print("Error performing Vision request: \(error)")
        }
    }
}

 

 

 

 

 

 

 

 

 

 

 

 

更多推荐