この分野の常識がよく見えないので、とりあえずということで、面白そうなYOLOv3を使ってみようかと思います。
以下、使い方の説明です。
Sponsor Link
Create MLは使わなくて良い
すでに、CreateMLで生成されたmlmodelファイルが置いてあるので、とくに、”Create ML”アプリケーションを使う必要はありません。
Input/Outputは?
Xcodeでプロジェクトを作って、mlmodelをD&Dすると、モデルのインプットやアウトプットがわかります。
- 入力1:画像
416×416の画像情報 - 入力3:出力データの閾値
画像認識して出力するときに、自信(confidence)がどれくらいあるときに出力してくるかの設定 - 出力1;自信の値
80種類のオブジェクトに分類してくるので、その分の配列込み - 出力2:座標値
認識されたオブジェクトの(x,y,w,h)の値。(の配列)
入力2:画像の圧縮情報?
画像処理にも疎いので、不明
と書いてありますが、なんとなくしか意味が分からないので、作って理解してみましょう。
Macアプリプロジェクトの作成
試しに使ってみるために、アプリを作ってみました。(なぜ、お試しアプリを公開してくれないのか不思議です)
Macアプリプロジェクトの準備
いろいろな画像に対して使ってみたくなるはずで、作業の効率を考えるとMacアプリとして作った方が良さそうなので、Macアプリとしてプロジェクトを作ります。
mlmodelの使い方
mlmodelをプロジェクトにD&Dするとモデルも作成されて、その情報によると、inputはYOLOv3Inputで、outputはYOLOv3Outputです。
それぞれ、以下のような情報を含みます。
class YOLOv3Input : MLFeatureProvider {
/// 416x416 RGB image as color (kCVPixelFormatType_32BGRA) image buffer, 416 pixels wide by 416 pixels high
var image: CVPixelBuffer
/// This defines the radius of suppression. as optional double value
var iouThreshold: Double? = nil
/// Remove bounding boxes below this threshold (confidences should be nonnegative). as optional double value
var confidenceThreshold: Double? = nil
class YOLOv3Output : MLFeatureProvider {
/// Source provided by CoreML
private let provider : MLFeatureProvider
/// Confidence derived for each of the bounding boxes. as multidimensional array of doubles
lazy var confidence: MLMultiArray = {
[unowned self] in return self.provider.featureValue(for: "confidence")!.multiArrayValue
}()!
/// Normalised coordiantes (relative to the image size) for each of the bounding boxes (x,y,w,h). as multidimensional array of doubles
lazy var coordinates: MLMultiArray = {
[unowned self] in return self.provider.featureValue(for: "coordinates")!.multiArrayValue
}()!
あとは、とりあえず、動くものを作って、出てきたものをみてみることにしました。
CVPixelBuffer
入力のCVPixelBufferって、なんでしょうか?32-bit BGRAとのことですが、それって何??
Appleの周辺ドキュメントを読んでいくと、どうやら、Core Videoで扱うことのできるフォーマットっぽい。(CoreImageでも扱えるみたいです)
CVPixelBufferというそのままのクラスがあって、以下のメソッドを使えば良いみたいです。
func CVPixelBufferCreate(_ allocator: CFAllocator?,
_ width: Int,
_ height: Int,
_ pixelFormatType: OSType,
_ pixelBufferAttributes: CFDictionary?,
_ pixelBufferOut: UnsafeMutablePointer)
# がっつりObjective-CというかメモリアロケーションしてそうなCのAPIですが、しょうがないのかな?
自分向けにメモです。(Swiftでポインタ扱うのにはいまだに慣れません)
- UnsafeMutablePointer<Type> は、Type* のこと
と調べていたのですが、GitHubで便利なNSImageのextensionが公開されているのを見つけて、そのextensionを使って、PixelBufferに変換してます。
サイズ変換についても便利なExtensionがあったので、使いました。結局自分のコードはほぼ0になりました・・・
コードは、以下。評価したい画像をD&Dするとサイズを変更して読み込まれて、ボタンを押すと評価します。
struct ContentView: View {
@State private var isDropTargeted = false
@State var nsImage:NSImage = (NSImage(contentsOf: Bundle.main.url(forResource: "View", withExtension: "jpg")! )?.copy(size: NSSize(width: 416, height: 416))!)!
// alert
@State private var showingAlert = false
@State private var alertTitle = ""
@State private var alertMessage = ""
var body: some View {
VStack {
Image(nsImage: self.nsImage)
.resizable()
.scaledToFit()
.onDrop(of: [kUTTypeFileURL as String], isTargeted: $isDropTargeted, perform: self.processDroppedFile(provideres:))
Button(action: {
self.applyYOLOv3()
}, label: {
Text("apply YOLOv3")
})
.padding()
}
.alert(isPresented: $showingAlert) {
Alert(title: Text(alertTitle), message: Text(alertMessage), dismissButton: .default(Text("OK")))
}
}
func applyYOLOv3() {
let model = YOLOv3()
guard let pixelBuffer = self.nsImage.pixelBuffer() else {
alertTitle = "Error!"
alertMessage = "failed to create PixelBuffer"
showingAlert = true
return
}
let input = YOLOv3Input(image: pixelBuffer, iouThreshold: nil, confidenceThreshold: nil)
do {
let output = try model.prediction(input: input)
alertTitle = "Success"
alertMessage = ""
print("confidence: \(output.confidence)")
print("coordinate: \(output.coordinates)")
} catch {
alertTitle = "Error!"
alertMessage = error.localizedDescription
}
showingAlert = true
}
func processDroppedFile(provideres:[NSItemProvider]) -> Bool {
guard let provider = provideres.first else { return false }
provider.loadItem(forTypeIdentifier: (kUTTypeFileURL as String), options: nil) { (urlData, error) in
DispatchQueue.main.async {
if let urlData = urlData as? Data {
let imageURL = NSURL(absoluteURLWithDataRepresentation: urlData, relativeTo: nil) as URL
if let localImage = NSImage(contentsOf: imageURL)?.copy(size: NSSize(width: 416, height: 416)) {
self.nsImage = localImage
}
}
}
}
return true
}
}
上記以外のコードに、上の方に記載したNSImageのextensionが必要となります。
認識が微妙・・・
自分のサンプルでは何も認識されず焦ったのですが、適切な画像を評価させると認識されるようです。
きちんと識別される画像を用意して動作確認しないと、何がいけないかわからなくなりそうです。
認識結果のデータの読み方は別記事で。
説明は以上です。
Sponsor Link