■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()
}
}