Text Detection in iOS11
Chúng ta sẽ dùng Vision Framework của iOS11 để xây dựng một ứng dụng sẽ có thể phát hiện text bất kể font, đối tượng và màu sắc, nó có thể nhận ra là text được in và viết tay. Lưu ý là project này sẽ cần XCode9 và device chạy iOS11 để test. Ta khởi tạo 1 object AVCaptureSession để thực ...
Chúng ta sẽ dùng Vision Framework của iOS11 để xây dựng một ứng dụng sẽ có thể phát hiện text bất kể font, đối tượng và màu sắc, nó có thể nhận ra là text được in và viết tay. Lưu ý là project này sẽ cần XCode9 và device chạy iOS11 để test.
Ta khởi tạo 1 object AVCaptureSession để thực hiện real-time hay offline capture.
var session = AVCaptureSession()
Tiếp theo, ta thực hiện kết nối session đến device
func startVideo() { session.sessionPreset = AVCaptureSession.Preset.photo let captureDevice = AVCaptureDevice.default(for: AVMediaType.video) let deviceInput = try! AVCaptureDeviceInput(device: captureDevice!) let deviceOutput = AVCaptureVideoDataOutput() deviceOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_32BGRA)] deviceOutput.setSampleBufferDelegate(self, queue: DispatchQueue.global(qos: DispatchQoS.QoSClass.default)) session.addInput(deviceInput) session.addOutput(deviceOutput) let imageLayer = AVCaptureVideoPreviewLayer(session: session) imageLayer.frame = imageView.bounds imageView.layer.addSublayer(imageLayer) session.startRunning() }
- Ta setting cho AVCaptureSession và set AVMediaType là video vì ta sẽ thực hiện quay camera do đó nó sẽ liên tục chạy
- Tiếp theo, ta định nghĩa device output và input. Input sẽ là những gì camera nhìn thấy và output là video. Ta set type cho video format là kCVPixelFormatType_32BGRA. Tham khảo thêm type cho video https://developer.apple.com/documentation/corevideo/cvpixelformatdescription/1563591-pixel_format_types
- Cuối cùng, ta add sublayer chứa video vào imageView và start session. Ta gọi hàm này trong viewDidLoad. Ngoài ra ta còn cần set lại frame của layer.
override func viewDidLoad() { super.viewDidLoad() startLiveVideo() } override func viewDidLayoutSubviews() { imageView.layer.sublayers?[0].frame = imageView.bounds }
Lưu ý là phải set Privacy - Camera Usage Description trong plist. Vậy là ta đã xong bước tạo camera với AVCapture.
Trước tiên, ta cần hiểu cách hoạt động của Vision framework. Về cơ bản, có 3 bước để implement vision vào app:
- Requests: là khi ta yêu cầu framework detect
- Handlers: là khi ta muốn framework thực hiện cái gì đó sau khi request được gọi
- Observations: là những gì ta muốn làm với dữ liệu được cung cấp Ta bắt đầu với 1 request.
var requests = [VNRequest]() func startTextDetection() { let textRequest = VNDetectTextRectanglesRequest(completionHandler: self.detectTextHandler) textRequest.reportCharacterBoxes = true self.requests = [textRequest] } func detectTextHandler(request: VNRequest, error: Error?) { guard let observations = request.results else { print("no result") return } let result = observations.map({$0 as? VNTextObservation}) }
Ta tạo 1 text request là VNDetectTextRectanglesRequest. Về cơ bản nó chỉ là một loại VNRequest mà chỉ viền xung quanh các text. Khi framework hoàn thành request, ta sẽ gọi hàm detectTextHandler. Ta cũng sẽ muốn biết chính xác cái mà framework recognized cho nên ta set biến reportCharacterBoxes = true. Ngoài ra, ta bắt đầu định nghĩa observations chứa tất cả kết quả của VNDetectTextRectanglesRequest. Ta add Vision vào camera output.
extension ViewController: AVCaptureVideoDataOutputSampleBufferDelegate { func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } var requestOptions:[VNImageOption : Any] = [:] if let camData = CMGetAttachment(sampleBuffer, kCMSampleBufferAttachmentKey_CameraIntrinsicMatrix, nil) { requestOptions = [.cameraIntrinsics:camData] } let imageRequestHandler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, orientation: CGImagePropertyOrientation(rawValue: 6)!, options: requestOptions) do { try imageRequestHandler.perform(self.requests) } catch { print(error) } } }
Về cơ bản hàm trên sẽ kiểm tra nếu CMSampleBuffer tồn tại và đưa ra AVCaptureOutput. Sau đó tạo 1 biến requestOptions là 1 dictionary loại VNImageOption. VNImageOption là loại cấu trúc có thể chứa các thuộc tính và dữ liệu từ camera. Cuối cùng ta tạo VNImageRequestHandler và thực hiện text request
Ta sẽ vẽ 2 loại: 1 là cho mỗi kí tự detect được, 2 là cho cả từ. Ta sẽ bắt đầu với từ
func highlightWord(box: VNTextObservation) { guard let boxes = box.characterBoxes else { return } var maxX: CGFloat = 9999.0 var minX: CGFloat = 0.0 var maxY: CGFloat = 9999.0 var minY: CGFloat = 0.0 for char in boxes { if char.bottomLeft.x < maxX { maxX = char.bottomLeft.x } if char.bottomRight.x > minX { minX = char.bottomRight.x } if char.bottomRight.y < maxY { maxY = char.bottomRight.y } if char.topRight.y > minY { minY = char.topRight.y } } let xCord = maxX * imageView.frame.size.awidth let yCord = (1 - minY) * imageView.frame.size.height let awidth = (minX - maxX) * imageView.frame.size.awidth let height = (minY - maxY) * imageView.frame.size.height let outline = CALayer() outline.frame = CGRect(x: xCord, y: yCord, awidth: awidth, height: height) outline.borderWidth = 2.0 outline.borderColor = UIColor.red.cgColor imageView.layer.addSublayer(outline) }
Sau đó là cho từng kí tự
func highlightLetters(box: VNRectangleObservation) { let xCord = box.topLeft.x * imageView.frame.size.awidth let yCord = (1 - box.topLeft.y) * imageView.frame.size.height let awidth = (box.topRight.x - box.bottomLeft.x) * imageView.frame.size.awidth let height = (box.topLeft.y - box.bottomLeft.y) * imageView.frame.size.height let outline = CALayer() outline.frame = CGRect(x: xCord, y: yCord, awidth: awidth, height: height) outline.borderWidth = 1.0 outline.borderColor = UIColor.blue.cgColor imageView.layer.addSublayer(outline) }
Sau đó cập nhật hàm detectTextHandler
func detectTextHandler(request: VNRequest, error: Error?) { guard let observations = request.results else { print("no result") return } let result = observations.map({$0 as? VNTextObservation}) DispatchQueue.main.async() { self.imageView.layer.sublayers?.removeSubrange(1...) for region in result { guard let rg = region else { continue } self.highlightWord(box: rg) if let boxes = region?.characterBoxes { for characterBox in boxes { self.highlightLetters(box: characterBox) } } } } }
Build app ta được