この分野の常識がよく見えないので、とりあえずということで、面白そうな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です。
それぞれ、以下のような情報を含みます。
1 2 3 4 5 6 7 8 9 10 11 12 |
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 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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というそのままのクラスがあって、以下のメソッドを使えば良いみたいです。
1 2 3 4 5 6 7 8 |
func CVPixelBufferCreate(_ allocator: CFAllocator?, _ width: Int, _ height: Int, _ pixelFormatType: OSType, _ pixelBufferAttributes: CFDictionary?, _ pixelBufferOut: UnsafeMutablePointer<CVPixelBuffer?>) |
# がっつりObjective-CというかメモリアロケーションしてそうなCのAPIですが、しょうがないのかな?
- UnsafeMutablePointer<Type> は、Type* のこと
と調べていたのですが、GitHubで便利なNSImageのextensionが公開されているのを見つけて、そのextensionを使って、PixelBufferに変換してます。
サイズ変換についても便利なExtensionがあったので、使いました。結局自分のコードはほぼ0になりました・・・
コードは、以下。評価したい画像をD&Dするとサイズを変更して読み込まれて、ボタンを押すと評価します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
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