[CoreML] CoreMLのサンプルモデルを使ってみる(YOLOv3)

コーヒーと起床時間のモデルの使い方は分かったので、Appleのページにあるサンプルモデルを使ってみます。

この分野の常識がよく見えないので、とりあえずということで、面白そうなYOLOv3を使ってみようかと思います。

以下、使い方の説明です。

Create MLは使わなくて良い

すでに、CreateMLで生成されたmlmodelファイルが置いてあるので、とくに、"Create ML"アプリケーションを使う必要はありません。

Input/Outputは?

Xcodeでプロジェクトを作って、mlmodelをD&Dすると、モデルのインプットやアウトプットがわかります。

XcodeへD&Dした

  • 入力1:画像
    416x416の画像情報
  • 入力2:画像の圧縮情報?
    画像処理にも疎いので、不明

  • 入力3:出力データの閾値
    画像認識して出力するときに、自信(confidence)がどれくらいあるときに出力してくるかの設定
  • 出力1;自信の値
    80種類のオブジェクトに分類してくるので、その分の配列込み
  • 出力2:座標値
    認識されたオブジェクトの(x,y,w,h)の値。(の配列)

と書いてありますが、なんとなくしか意味が分からないので、作って理解してみましょう。




Macアプリプロジェクトの作成

試しに使ってみるために、アプリを作ってみました。(なぜ、お試しアプリを公開してくれないのか不思議です)

Macアプリプロジェクトの準備

いろいろな画像に対して使ってみたくなるはずで、作業の効率を考えるとMacアプリとして作った方が良さそうなので、Macアプリとしてプロジェクトを作ります。

mlmodelの使い方

mlmodelをプロジェクトにD&Dするとモデルも作成されて、その情報によると、inputはYOLOv3Inputで、outputはYOLOv3Outputです。
それぞれ、以下のような情報を含みます。

YOLOv3Input

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
YOLOv3Output

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ですが、しょうがないのかな?

MEMO
自分向けにメモです。(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が必要となります。

認識が微妙・・・

自分のサンプルでは何も認識されず焦ったのですが、適切な画像を評価させると認識されるようです。

きちんと識別される画像を用意して動作確認しないと、何がいけないかわからなくなりそうです。

認識結果のデータの読み方は別記事で。

説明は以上です。




コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です