วิธีสร้างแอพ iOS สำหรับจดจำรูปภาพด้วย CoreML และ Vision API ของ Apple

ด้วยการเปิดตัว CoreML และ Vision API ใหม่ในการประชุม Apple World Wide Developers Conference ในปีนี้การเรียนรู้ของเครื่องจะง่ายกว่าที่เคย วันนี้ผมจะแสดงวิธีสร้างแอปจดจำรูปภาพง่ายๆ

เราจะเรียนรู้วิธีเข้าถึงกล้องของ iPhone และวิธีส่งต่อสิ่งที่กล้องเห็นไปยังโมเดลการเรียนรู้ของเครื่องเพื่อการวิเคราะห์ เราจะทำทั้งหมดนี้โดยใช้โปรแกรมโดยไม่ต้องใช้สตอรี่บอร์ด! บ้าฉันรู้

นี่คือสิ่งที่เราจะทำให้สำเร็จในวันนี้:

// // ViewController.swift // cameraTest // // Created by Mark Mansur on 2017-08-01. // Copyright © 2017 Mark Mansur. All rights reserved. // import UIKit import AVFoundation import Vision class ViewController: UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate { let label: UILabel = { let label = UILabel() label.textColor = .white label.translatesAutoresizingMaskIntoConstraints = false label.text = "Label" label.font = label.font.withSize(30) return label }() override func viewDidLoad() { super.viewDidLoad() setupCaptureSession() view.addSubview(label) setupLabel() } func setupCaptureSession() { let captureSession = AVCaptureSession() // search for available capture devices let availableDevices = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .back).devices // setup capture device, add input to our capture session do { if let captureDevice = availableDevices.first { let captureDeviceInput = try AVCaptureDeviceInput(device: captureDevice) captureSession.addInput(captureDeviceInput) } } catch { print(error.localizedDescription) } // setup output, add output to our capture session let captureOutput = AVCaptureVideoDataOutput() captureOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "videoQueue")) captureSession.addOutput(captureOutput) let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) previewLayer.frame = view.frame view.layer.addSublayer(previewLayer) captureSession.startRunning() } // called everytime a frame is captured func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { guard let model = try? VNCoreMLModel(for: Resnet50().model) else {return} let request = VNCoreMLRequest(model: model) { (finishedRequest, error) in guard let results = finishedRequest.results as? [VNClassificationObservation] else { return } guard let Observation = results.first else { return } DispatchQueue.main.async(execute: { self.label.text = "\(Observation.identifier)" }) } guard let pixelBuffer: CVPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } // executes request try? VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:]).perform([request]) } func setupLabel() { label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true label.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -50).isActive = true } }

🙌🏻ขั้นตอนที่ 1: สร้างโครงการใหม่

เปิดใช้งาน Xcode และสร้างแอปพลิเคชันมุมมองเดียวใหม่ ตั้งชื่อให้เป็น "ImageRecognition" เลือก swift เป็นภาษาหลักและบันทึกโครงการใหม่ของคุณ

👋ขั้นตอนที่ 2: บอกลาสตอรี่บอร์ด

สำหรับบทช่วยสอนนี้เราจะทำทุกอย่างแบบเป็นโปรแกรมโดยไม่จำเป็นต้องใช้สตอรีบอร์ด บางทีฉันจะอธิบายว่าทำไมในบทความอื่น

ลบmain.storyboard.

นำทางไปยัง info.plistแล้วเลื่อนลงไปที่ Deployment Info เราต้องบอก Xcode ว่าเราไม่ได้ใช้สตอรี่บอร์ดอีกต่อไป

ลบอินเทอร์เฟซหลัก

หากไม่มีสตอรีบอร์ดเราจำเป็นต้องสร้างหน้าต่างแอพและตัวควบคุมมุมมองรูทด้วยตนเอง

เพิ่มสิ่งต่อไปนี้ในapplication()ฟังก์ชันในAppDelegate.swift:

 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. window = UIWindow() window?.makeKeyAndVisible() let vc = ViewController() window?.rootViewController = vc return true }

เราสร้างด้วยตนเองหน้าต่าง app กับUIWindow(),สร้างตัวควบคุมมุมมองของเราและบอกให้หน้าต่างใช้เป็นตัวควบคุมมุมมองรูท

ตอนนี้แอปควรสร้างและทำงานโดยไม่มีสตอรี่บอร์ด😎

⚙️ขั้นตอนที่ 3: ตั้งค่า AVCaptureSession

ก่อนที่เราจะเริ่มนำเข้า UIKit, AVFoundation และ Vision วัตถุ AVCaptureSession จัดการกิจกรรมการจับภาพและจัดการการไหลของข้อมูลระหว่างอุปกรณ์อินพุต (เช่นกล้องหลัง) และเอาต์พุต

เราจะเริ่มต้นด้วยการสร้างฟังก์ชันเพื่อตั้งค่าเซสชันการจับภาพของเรา

สร้างsetupCaptureSession()ภายในViewController.swiftAVCaptureSessionและอินสแตนซ์ใหม่

func setupCaptureSession() { // creates a new capture session let captureSession = AVCaptureSession() }

ViewDidLoad()อย่าลืมที่จะเรียกฟังก์ชั่นใหม่นี้จาก

override func viewDidLoad() { super.viewDidLoad() setupCaptureSession() }

ต่อไปเราจะต้องอ้างอิงถึงกล้องมองหลัง เราสามารถใช้ไฟล์DiscoverySessionเพื่อค้นหาอุปกรณ์จับภาพที่มีอยู่ตามเกณฑ์การค้นหาของเรา

เพิ่มรหัสต่อไปนี้:

// search for available capture devices let availableDevices = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .back).devices 

AvailableDevicesตอนนี้มีรายการอุปกรณ์ที่ใช้ได้ตรงกับเกณฑ์การค้นหาของเรา

ตอนนี้เราจำเป็นต้องเข้าถึงcaptureDeviceและเพิ่มเป็นข้อมูลในcaptureSessionไฟล์.

เพิ่มข้อมูลเข้าในเซสชันการจับภาพ

// get capture device, add device input to capture session do { if let captureDevice = availableDevices.first { captureSession.addInput(try AVCaptureDeviceInput(device: captureDevice)) } } catch { print(error.localizedDescription) }

อุปกรณ์แรกที่มีคือกล้องหลัง เราสร้างไฟล์AVCaptureDeviceInputโดยใช้อุปกรณ์จับภาพของเราและเพิ่มลงในเซสชันการจับภาพ

ตอนนี้เรามีการตั้งค่าอินพุตแล้วเราสามารถเริ่มต้นเกี่ยวกับวิธีการส่งออกสิ่งที่กล้องจับได้

เพิ่มเอาต์พุตวิดีโอในเซสชันการจับภาพของเรา

// setup output, add output to our capture session let captureOutput = AVCaptureVideoDataOutput() captureSession.addOutput(captureOutput)

AVCaptureVideoDataOutputเป็นเอาต์พุตที่จับภาพวิดีโอ นอกจากนี้ยังให้เราเข้าถึงเฟรมที่ถูกจับเพื่อประมวลผลด้วยวิธีการมอบหมายที่เราจะเห็นในภายหลัง

ต่อไปเราต้องเพิ่มเอาต์พุตของเซสชันการจับภาพเป็นเลเยอร์ย่อยในมุมมองของเรา

เพิ่มเอาต์พุตเซสชันการจับภาพเป็นเลเยอร์ย่อยในมุมมองของตัวควบคุมมุมมอง

let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) previewLayer.frame = view.frame view.layer.addSublayer(previewLayer) captureSession.startRunning()

เราสร้างเลเยอร์ตามเซสชันการจับภาพของเราและเพิ่มเลเยอร์นี้เป็นเลเยอร์ย่อยในมุมมองของเรา CaptureSession.startRunning()เริ่มการไหลจากอินพุตไปยังเอาต์พุตที่เราเชื่อมต่อก่อนหน้านี้

📷ขั้นตอนที่ 4: อนุญาตให้ใช้กล้องหรือไม่? ได้รับอนุญาต.

เกือบทุกคนเปิดแอปเป็นครั้งแรกและได้รับแจ้งให้อนุญาตให้แอปใช้กล้องถ่ายรูป ตั้งแต่ iOS 10 เป็นต้นไปแอปของเราจะหยุดทำงานหากเราไม่แจ้งผู้ใช้ก่อนที่จะพยายามเข้าถึงกล้อง

นำทางไปยัง info.plistNSCameraUsageDescriptionและเพิ่มคีย์ใหม่ที่ชื่อว่า ในคอลัมน์ค่าเพียงอธิบายให้ผู้ใช้ทราบว่าเหตุใดแอปของคุณจึงต้องการการเข้าถึงกล้อง

ตอนนี้เมื่อผู้ใช้เปิดแอปเป็นครั้งแรกพวกเขาจะได้รับแจ้งให้อนุญาตการเข้าถึงกล้อง

📊ขั้นตอนที่ 5: รับโมเดล

หัวใจหลักของโครงการนี้น่าจะอยู่ที่รูปแบบการเรียนรู้ของเครื่อง นางแบบจะต้องสามารถถ่ายภาพและให้เราคาดเดาได้ว่าภาพนั้นคืออะไร คุณสามารถค้นหาโมเดลที่ผ่านการฝึกอบรมได้ฟรีที่นี่ สิ่งที่ฉันเลือกคือ ResNet50

Once you obtain your model, drag and drop it into Xcode. It will automatically generate the necessary classes, providing you an interface to interact with your model.

🏞 Step 6: Image analysis.

To analyze what the camera is seeing, we need to somehow gain access to the frames being captured by the camera.

Conforming to the AVCaptureVideoDataOutputSampleBufferDelegategives us an interface to interact with and be notified every time a frame is captured by the camera.

Conform ViewController to the AVCaptureVideoDataOutputSampleBufferDelegate.

We need to tell our Video output that ViewController is its sample buffer delegate.

Add the following line in SetupCaptureSession():

captureOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "videoQueue")) 

Add the following function:

func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { guard let model = try? VNCoreMLModel(for: Resnet50().model) else { return } let request = VNCoreMLRequest(model: model) { (finishedRequest, error) in guard let results = finishedRequest.results as? [VNClassificationObservation] else { return } guard let Observation = results.first else { return } DispatchQueue.main.async(execute: { self.label.text = "\(Observation.identifier)" }) } guard let pixelBuffer: CVPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } // executes request try? VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:]).perform([request]) }

Each time a frame is captured, the delegate is notified by calling captureOutput(). This is a perfect place to do our image analysis with CoreML.

First, we create a VNCoreMLModelwhich is essentially a CoreML model used with the vision framework. We create it with a Resnet50 Model.

Next, we create our vision request. In the completion handler, we update the onscreen UILabel with the identifier returned by the model. We then convert the frame passed to us from a CMSampleBuffer to a CVPixelBuffer. Which is the format our model needs for analysis.

Lastly, we perform the Vision request with a VNImageRequestHandler.

🗒 Step 7: Create a label.

The last step is to create a UILabel containing the model’s prediction.

Create a new UILabeland position it using constraints.

let label: UILabel = { let label = UILabel() label.textColor = .white label.translatesAutoresizingMaskIntoConstraints = false label.text = "Label" label.font = label.font.withSize(30) return label }() func setupLabel() { label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true label.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -50).isActive = true }

Don’t forget to add the label as a subview and call setupLabel() from within ViewDidLoad().

view.addSubview(label) setupLabel()

You can download the completed project from GitHub here.

Like what you see? Give this post a thumbs up 👍, follow me on Twitter, GitHub, or check out my personal page.