How do you integrate a custom image classification model created in keras in your iOS App? [Swift]

Using the tutorial found at blog.keras.io/building-powerful-image-classification-models-using-very-little-data.html, I trained a Keras model to recognize the difference between cats and dogs.

''' Directory structure: 
     data/ 
          train/ 
               dogs/ 
                 dog001.jpg 
                 dog002.jpg 
                 ...
               cats/ 
                 cat001.jpg 
                 cat002.jpg 
                  ... 
          validation/ 
               dogs/ 
                 dog001.jpg 
                 dog002.jpg
                 ... 
               cats/ 
                  cat001.jpg 
                  cat002.jpg
                  ... 
'''


from keras.preprocessing.image import ImageDataGenerator 
from keras.models import Sequential 
from keras.layers import Conv2D, MaxPooling2D 
from keras.layers import Activation, Dropout, Flatten, Dense 
from keras import backend as K 
from PIL import Image import numpy as np 

# dimensions of our images. 
img_width, img_height = 150, 150 

train_data_dir = 'data/train' 
validation_data_dir = 'data/validation' 
nb_train_samples = 2000 
nb_validation_samples = 800 
epochs = 50 
batch_size = 16 

if K.image_data_format() == 'channels_first': 
     input_shape = (3, img_width, img_height) 
else: 
     input_shape = (img_width, img_height, 3) 

model = Sequential() 
model.add(Conv2D(32, (3, 3), input_shape=input_shape)) 
model.add(Activation('relu')) 
model.add(MaxPooling2D(pool_size=(2, 2))) 

model.add(Conv2D(32, (3, 3))) 
model.add(Activation('relu')) 
model.add(MaxPooling2D(pool_size=(2, 2))) 

model.add(Conv2D(64, (3, 3))) 
model.add(Activation('relu')) 
model.add(MaxPooling2D(pool_size=(2, 2))) 

model.add(Flatten()) model.add(Dense(64)) 
model.add(Activation('relu')) 
model.add(Dropout(0.5)) 
model.add(Dense(1)) 
model.add(Activation('sigmoid'))

model.compile(loss='binary_crossentropy', optimizer='rmsprop', metrics=['accuracy']) 

# this is the augmentation configuration we will use for training
train_datagen = ImageDataGenerator( rescale=1. / 255, shear_range=0.2, zoom_range=0.2, horizontal_flip=True) 

# this is the augmentation configuration we will use for testing: 
# only rescaling 
test_datagen = ImageDataGenerator(rescale=1. / 255) 

train_generator = train_datagen.flow_from_directory( train_data_dir, target_size=(img_width, img_height), batch_size=batch_size, class_mode='binary') 

validation_generator = test_datagen.flow_from_directory( validation_data_dir, target_size=(img_width, img_height), batch_size=batch_size, class_mode='binary') 

model.fit_generator( train_generator, steps_per_epoch=nb_train_samples / epochs=epochs, validation_data=validation_generator, validation_steps=nb_validation_samples / 

model.save('first_try.h5')

Using the coremltools documentation as a guide, I tried converting my model to the coreml format:

import coremltools 
import h5py 
coreml_model = coremltools.converters.keras.convert('first_try.h5',input_names='image',output_names='class',image_input_names = 'image',class_labels = ['cat', 'dog'], is_bgr=True) 
coreml_model.save('cats_dogs.mlmodel')

When I import the model into XCode and run it with the following code (which works with the resnet50 and inceptionv3 models found on Apple's website), I get the error "Thread 8: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)." I have attached a screenshot of the error as it appears in XCode.


import UIKit
import Vision
import CoreML


class ViewController: UIViewController, UINavigationControllerDelegate {


    //MARK: - Properties




    @IBOutlet weak var imageView: UIImageView!
    @IBOutlet weak var textView: UITextView!


    let imagePicker = UIImagePickerController()


    //MARK: - ViewController


    override func viewDidLoad() {
        super .viewDidLoad()
        self.imagePicker.delegate = self
    }


    @IBAction func openImagePicker(_ sender: Any) {
        imagePicker.allowsEditing = false
        imagePicker.sourceType = .photoLibrary
        present(imagePicker, animated: true, completion: nil)
    }


    @IBAction func camera(_ sender: Any) {


        if !UIImagePickerController.isSourceTypeAvailable(.camera) {
            return
        }


        let cameraPicker = UIImagePickerController()
        cameraPicker.delegate = self
        cameraPicker.sourceType = .camera
        cameraPicker.allowsEditing = false


        present(cameraPicker, animated: true)
    }


}


extension ViewController: UIImagePickerControllerDelegate {


    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {


        if let pickedImage = info[UIImagePickerControllerOriginalImage] as? UIImage {
            imageView.contentMode = .scaleAspectFit
            imageView.image = pickedImage
            textView.text = "Guessing..."
            processImage(pickedImage.cgImage!) { [weak self] text in
                self?.textView.text = text
            }
        }


        picker.dismiss(animated: true, completion: nil)
    }


}


//MARK: - CoreML
extension ViewController {


    func processImage(_ image: CGImage, completion: @escaping (String)->Void ){


        DispatchQueue.global(qos: .background).async {


            //Init Core Vision Model
            guard let vnCoreModel = try? VNCoreMLModel(for: cats_dogs().model) else { return }


            //Init Core Vision Request
            let request = VNCoreMLRequest(model: vnCoreModel) { (request, error) in
                guard let results = request.results as? [VNClassificationObservation] else { fatalError("Failure") }
                var text = ""
                for classification in results {
                    text.append("\n" + "\(classification.identifier, classification.confidence)")
                }


                DispatchQueue.main.async {
                    completion(text)
                }
            }
            //Init Core Vision Request Handler
            let handler = VNImageRequestHandler(cgImage: image)


            //Perform Core Vision Request
            do {
                try handler.perform([request])
            } catch {
                print("did throw on performing a VNCoreRequest")
            }
        }
    }
}


I have searched the web extensively to solve this issue. Help to fix this issue would be much appreciated!

Screen shots are not available on the forum. So it's not possible to see what you mean.


Where does the error show exactly ?

fatal error: Failure: file /Users/john/Desktop/CustomCoreML/cats_dogs/ViewController.swift, line 75 (lldb)

Have you checked you have not a non printing character in the file at line 75 ? Problem is that such characters do not show and are very difficult to track.


Why do you define imagePickerController in an extension and not directly in the class (even if that does not make a difference) ?

Claude31

Because he is seperating the delegate implementation of image picker from his main ViewController implementation ...


John

Copying the code builds without issue when used with the apple supplied class ml model. When you generated the data set where there any errors in doing so and in running the apple py scripts did those complete successfully?


Model

I was able to setup the keras & tensorflow environment - use the reuters data set and convert it using core ml tools without issue ..

I reformatted the image picker controller and coreml code blocks to work without the Vision class.

extension ViewController: UIImagePickerControllerDelegate {
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        dismiss(animated: true, completion: nil)
    }

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
        picker.dismiss(animated: true)
        textView.text = "Analyzing Image..."
        guard let image = info["UIImagePickerControllerOriginalImage"] as? UIImage else {
            return
        }
    
        UIGraphicsBeginImageContextWithOptions(CGSize(width: 150, height: 150), true, 2.0)
        image.draw(in: CGRect(x: 0, y: 0, width: 150, height: 150))
        let newImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
    
        let attrs = [kCVPixelBufferCGImageCompatibilityKey: kCFBooleanTrue, kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue] as CFDictionary
        var pixelBuffer : CVPixelBuffer?
        let status = CVPixelBufferCreate(kCFAllocatorDefault, Int(newImage.size.width), Int(newImage.size.height), kCVPixelFormatType_32ARGB, attrs, &pixelBuffer)
        guard (status == kCVReturnSuccess) else {
            return
        }
    
        CVPixelBufferLockBaseAddress(pixelBuffer!, CVPixelBufferLockFlags(rawValue: 0))
        let pixelData = CVPixelBufferGetBaseAddress(pixelBuffer!)
    
        let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
        let context = CGContext(data: pixelData, width: Int(newImage.size.width), height: Int(newImage.size.height), bitsPerComponent: 8, bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer!), space: rgbColorSpace, bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue) /
    
        context?.translateBy(x: 0, y: newImage.size.height)
        context?.scaleBy(x: 1.0, y: -1.0)
    
        UIGraphicsPushContext(context!)
        newImage.draw(in: CGRect(x: 0, y: 0, width: newImage.size.width, height: newImage.size.height))
        UIGraphicsPopContext()
        CVPixelBufferUnlockBaseAddress(pixelBuffer!, CVPixelBufferLockFlags(rawValue: 0))
        imageView.image = newImage
    
        guard let prediction = try? model.prediction(image: pixelBuffer!) else {
            print("error!")
            return
        }
        textView.text = "I think this is a \(String(describing: prediction.classLabel))."
    }
}


By inserting "print(try! model.prediction(image: pixelBuffer!) as Any)" in place of the code snippet at line 41 I was able to get the following error:


fatal error: 'try!' expression unexpectedly raised an error:

"Dimensions of layer 'output' is not the same size as the number of class labels."

NSLocalizedDescription=Dimensions of layer 'output' is not the same size as the number of class labels.:



I am relatively new to swift and keras, so I am not sure what "Dimensions of layer 'output' is not the same size as the number of class labels" means.

This may (?) be of interest : it is seems you have to configure keras.json

h ttps://forums.developer.apple.com/message/237148#237148

Hm...mine seems to be just as the article described. Thanks for posting the article reference anyways.

This is the code I use to convert the model to the .mlmodel format


import coremltools 
import h5py 
output_labels = ['cat','dog'] 
coreml_model = coremltools.converters.keras.convert('first_try.h5',input_names='image',image_input_names = 'image',class_labels = output_labels, is_bgr=False) 
coreml_model.author = '' 
coreml_model.short_description = 'Model to classify images as either cats or dogs' 
coreml_model.input_description['image'] = 'Image of a cat or dog' 
print coreml_model 


coreml_model.save('cats_dogs.mlmodel')


This is the terminal output:


0 : conv2d_1_input, <keras.engine.topology.InputLayer object at 0x1194c6c50>

1 : conv2d_1, <keras.layers.convolutional.Conv2D object at 0x1194c6c90>

2 : activation_1, <keras.layers.core.Activation object at 0x119515b90>

3 : max_pooling2d_1, <keras.layers.pooling.MaxPooling2D object at 0x119501e50>

4 : conv2d_2, <keras.layers.convolutional.Conv2D object at 0x119520cd0>

5 : activation_2, <keras.layers.core.Activation object at 0x1194e8150>

6 : max_pooling2d_2, <keras.layers.pooling.MaxPooling2D object at 0x11955cc50>

7 : conv2d_3, <keras.layers.convolutional.Conv2D object at 0x11955ce50>

8 : activation_3, <keras.layers.core.Activation object at 0x11954d9d0>

9 : max_pooling2d_3, <keras.layers.pooling.MaxPooling2D object at 0x119594cd0>

10 : flatten_1, <keras.layers.core.Flatten object at 0x1195a08d0>

11 : dense_1, <keras.layers.core.Dense object at 0x119579f10>

12 : activation_4, <keras.layers.core.Activation object at 0x1195c94d0>

13 : dense_2, <keras.layers.core.Dense object at 0x1195ea450>

14 : activation_5, <keras.layers.core.Activation object at 0x119614b10>

input {

name: "image"

shortDescription: "Image of a cat or dog"

type {

imageType {

width: 150

height: 150

colorSpace: RGB

}

}

}

output {

name: "output1"

type {

dictionaryType {

stringKeyType {

}

}

}

}

output {

name: "classLabel"

type {

stringType {

}

}

}

predictedFeatureName: "classLabel"

predictedProbabilitiesName: "output1"

metadata {

shortDescription: "Model to classify images as either cats or dogs"

author: ""

}

How do you integrate a custom image classification model created in keras in your iOS App? [Swift]
 
 
Q