Memo

メモ > 技術 > IDE: Xcode > SwiftUI+顔認識

■SwiftUI+顔認識
リアルタイム顔検出と画像を重ねての表示ができるなら、独自に差分検出などもできるかもしれない 以下の参考サイトはSwiftであってSwiftUIでは無いようなので注意 【iOS】Vision Frameworkを使ってリアルタイム顔検出アプリを作ってみた - 株式会社ライトコード https://rightcode.co.jp/blog/information-technology/ios-vision-framework-real-time-face-detection-ap... iOSでリアルタイム顔検出を行う - Qiita https://qiita.com/renchild8/items/b2e04fe48cb2cf60bcbc [コピペで使える]swift3/swift4/swift5でリアルタイム顔認識をする方法 - Qiita https://qiita.com/TakahiroYamamoto/items/e970658a98a4e659cf9e ■検証中 SwiftUI-Vision/Detected-in-Still-Image/Detected-in-Still-Image at main - SatoTakeshiX/SwiftUI-Vision - GitHub https://github.com/SatoTakeshiX/SwiftUI-Vision/tree/main/Detected-in-Still-Image/Detected-in-Still-I... をもとに検証中 SwiftUI-Visionで作られている また、リアルタイムな顔認識は「SwiftUI+リアルタイム顔認識」に記載している https://raw.githubusercontent.com/SatoTakeshiX/SwiftUI-Vision/main/Detected-in-Still-Image/Detected-... 画像をダウンロードし、Assets.xcassets に配置 (ドラッグ&ドロップすると「people」という名前で配置された) VisionClient.swift DetectorViewModel.swift
import Foundation import SwiftUI import UIKit import Vision import Combine final class DetectorViewModel: ObservableObject { @Published var image: UIImage = UIImage() @Published var detectedFrame: [CGRect] = [] @Published var detectedPoints: [(closed: Bool, points: [CGPoint])] = [] @Published var detectedInfo: [[String: String]] = [] private var cancellables: Set<AnyCancellable> = [] private var errorCancellables: Set<AnyCancellable> = [] private let visionClient = VisionClient() private var imageViewFramePublisher = PassthroughSubject<CGRect, Never>() private var originImagePublisher = PassthroughSubject<(CGImage, VisionRequestTypes.Set), Never>() // 初期処理 init() { visionClient.$result .receive(on: RunLoop.main) .sink { type in switch type { case .faceLandmarks(let drawPoints, let info): self.detectedPoints = drawPoints self.detectedInfo = info case .faceRect(let rectBox, let info): self.detectedFrame = rectBox self.detectedInfo = info case .word(let rectBoxes, let info): self.detectedFrame += rectBoxes self.detectedInfo = info case .character(let rectBox, let info): self.detectedFrame += rectBox self.detectedInfo = info case .textRecognize(let info): self.detectedInfo = info case .barcode(let rectBoxes, let info): self.detectedFrame = rectBoxes self.detectedInfo = info case .rect(let drawPoints, let info): self.detectedPoints = drawPoints self.detectedInfo = info case .rectBoundingBoxes(let rectBoxes): self.detectedFrame = rectBoxes default: break } } .store(in: &cancellables) visionClient.$error .receive(on: RunLoop.main) .sink { error in print(error?.localizedDescription ?? "") } .store(in: &errorCancellables) imageViewFramePublisher .removeDuplicates() // イベント送信を2つに絞る、最後のイベントを受け取る(GeometryReaderのハンドラが2回呼ばれており、2回目のみ正しい座標を取得できたため。overlayを利用しているためか) .prefix(2).last() // originImagePublisherのイベントと組み合わせて最後の値を取る .combineLatest(originImagePublisher) // Publisherのイベントの値を受け取る .sink { (imageRect, originImageArg) in // 画像サイズをimage viewのサイズに合わせてリサイズ let (cgImage, detectType) = originImageArg let fullImageWidth = CGFloat(cgImage.width) let fullImageHeight = CGFloat(cgImage.height) let targetWidh = imageRect.width let ratio = fullImageWidth / targetWidh let imageFrame = CGRect(x: 0, y: 0, width: imageRect.width, height: fullImageHeight / ratio) self.visionClient.configure(type: detectType, imageViewFrame: imageFrame) print(cgImage) // 画像向きを作成 let cgOrientation = CGImagePropertyOrientation(self.image.imageOrientation) // 情報をクリア self.clearAllInfo() // 画像と向きを返す self.visionClient.performVisionRequest(image: cgImage, orientation: cgOrientation) } .store(in: &cancellables) } // 画像情報と検出タイプを受け取る func onAppear(image: UIImage, detectType: VisionRequestTypes.Set) { self.image = image guard let resizedImage = resize(image: image) else { return } print(resizedImage.description) // Transform image to fit screen. guard let cgImage = resizedImage.cgImage else { print("Trying to show an image not backed by CGImage!") return } // 画像情報をイベントとして送信 originImagePublisher.send((cgImage, detectType)) } // 情報を入力 func input(imageFrame: CGRect) { // ImageViewの矩形情報をイベントとして送信 // 複数回呼ばれる可能性がある imageViewFramePublisher.send(imageFrame) } // 画像をリサイズ func resize(image: UIImage) -> UIImage? { let width: Double = 640 let aspectScale = image.size.height / image.size.width let resizedSize = CGSize(width: width, height: width * Double(aspectScale)) UIGraphicsBeginImageContextWithOptions(resizedSize, false, 0.0) image.draw(in: CGRect(x: 0, y: 0, width: resizedSize.width, height: resizedSize.height)) let resizedImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return resizedImage } // 情報をクリア private func clearAllInfo() { detectedFrame.removeAll() detectedPoints.removeAll() detectedInfo.removeAll() } } // UIImageOrientationをCGImageOrientationに変換 extension CGImagePropertyOrientation { init(_ uiImageOrientation: UIImage.Orientation) { switch uiImageOrientation { case .up: self = .up case .down: self = .down case .left: self = .left case .right: self = .right case .upMirrored: self = .upMirrored case .downMirrored: self = .downMirrored case .leftMirrored: self = .leftMirrored case .rightMirrored: self = .rightMirrored @unknown default: fatalError() } } }
ContentView.swift
import SwiftUI struct ContentView: View { @StateObject var viewModel = DetectorViewModel() var body: some View { VStack { Image(uiImage: viewModel.image) .resizable() .aspectRatio(contentMode: .fit) .opacity(0.6) .overlay( // 画像Viewの座標情報を取得 GeometryReader { proxy -> AnyView in viewModel.input(imageFrame: proxy.frame(in: .local)) return AnyView(EmptyView()) } ) .overlay( // 開いたパスを描画 Path { path in for frame in viewModel.detectedFrame { // 矩形を描画 path.addRect(frame) } } .stroke(Color.green, lineWidth: 2.0) // Visionの座標系からSwiftUIの座標系に変換 .scaleEffect(x: 1.0, y: -1.0, anchor: .center) ) .overlay( // 閉じたパスを描画 Path { path in for (closed, points) in viewModel.detectedPoints { // 線を描画 path.addLines(points) if closed { // パスを閉じる path.closeSubpath() } } } .stroke(Color.blue, lineWidth: 2.0) // Visionの座標系からSwiftUIの座標系に変換 .scaleEffect(x: 1.0, y: -1.0, anchor: .center) ) Text("Vision Framework") .padding() } .onAppear { // 画像情報と検出タイプをViewModelに渡す viewModel.onAppear(image: UIImage(named: "people")!, detectType: [.faceRect]) } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }

Advertisement