Je voulais comprendre le code source de l'exemple de code "Classification des images dans Vision et Core ML" par Apple, alors je l'ai organisé.
macOS 10.15.7 Xcode 12.1 Swift 5.3
Le projet qui peut effectivement être téléchargé n'a pas pu être construit tel quel avec le Xcode actuel (2020.10.29). Par conséquent, après avoir effectué l'optimisation par Xcode, j'ai modifié certaines parties. Ce qui suit est la partie implémentation omise.
ImageClassificationViewController.swift
class ImageClassificationViewController: UIViewController {
// IBOutlets
@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var cameraButton: UIBarButtonItem!
@IBOutlet weak var classificationLabel: UILabel!
// Image Classification
//Configuration du modèle
lazy var classificationRequest: VNCoreMLRequest = {
let modelURL = Bundle.main.url(forResource: "MobileNetV2", withExtension: "mlmodelc")!
let model = try! VNCoreMLModel(for: MobileNetV2(contentsOf: modelURL).model)
let request = VNCoreMLRequest(model: model, completionHandler: { [weak self] request, error in
self?.processClassifications(for: request, error: error)
})
request.imageCropAndScaleOption = .centerCrop
return request
}()
//Exécuter la demande
func updateClassifications(for image: UIImage) {...}
///Mettre à jour l'affichage à l'écran avec le résultat de la classification
//Faire un classement
func processClassifications(for request: VNRequest, error: Error?) {...}
}
//Procédure photographique
@IBAction func takePicture() {...}
func presentPhotoPicker(sourceType: UIImagePickerController.SourceType) {...}
}
extension ImageClassificationViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
//Sélection par sélecteur d'images
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {...}
}
La classe «ImageClassificationViewController» a «4 propriétés» et «5 méthodes». La mise en œuvre est la suivante.
Crée et renvoie une "requête" de type VNCoreMLRequest
.
Maintenant, faites attention au mot-clé «paresseux». Autrement dit, la valeur est calculée uniquement lors de l'accès.
En d'autres termes, "créer une instance de modèle -> créer une demande" est exécuté à chaque appel.
classificationRequest, propriété
lazy var classificationRequest: VNCoreMLRequest = {
do {
//Utiliser la classe MobileNet générée automatiquement par Core ML
//En réécrivant cette partie, vous pouvez utiliser n'importe quel modèle Core ML
let modelURL = Bundle.main.url(forResource: "MobileNetV2", withExtension: "mlmodelc")
let model = try VNCoreMLModel(for: MobileNetV2(contentsOf: modelURL!).model)
let request = VNCoreMLRequest(model: model, completionHandler: { [weak self] request, error in
self?.processClassifications(for: request, error: error)
})
request.imageCropAndScaleOption = .centerCrop
return request
} catch {
fatalError("Failed to load Vision ML model: \(error)")
}
}()
Tout d'abord, un prétraitement (orientation et format) est effectué sur l'image de type ʻUIImage` reçue. Ensuite, après avoir créé un gestionnaire de demandes, la demande est transmise au gestionnaire et exécutée. Notez que l'exécution du gestionnaire est gérée en arrière-plan.
updateClassifications()Méthode
func updateClassifications(for image: UIImage) {
classificationLabel.text = "Classifying..."
let orientation = CGImagePropertyOrientation(image.imageOrientation)
guard let ciImage = CIImage(image: image) else { fatalError("Unable to create \(CIImage.self) from \(image).") }
DispatchQueue.global(qos: .userInitiated).async {
let handler = VNImageRequestHandler(ciImage: ciImage, orientation: orientation)
try! handler.perform([self.classificationRequest])
}
}
Exécute le traitement sur le résultat de la classification. Tout le code est traité dans la file d'attente principale. Sur l'écran de l'interface utilisateur, le résultat de la classification est affiché sous la forme d'une chaîne de caractères telle que «(0,37) cliff, drop, drop-off» ».
processClassifications()Méthode
func processClassifications(for request: VNRequest, error: Error?) {
DispatchQueue.main.async {
guard let results = request.results else {
self.classificationLabel.text = "Unable to classify image.\n\(error!.localizedDescription)"
return
}
//Le type de résultats est toujours une collection de VNClassificationObservation
let classifications = results as![VNClassificationObservation]
if classifications.isEmpty {
self.classificationLabel.text = "Nothing recognized."
} else {
//Les classifications sont affichées par ordre décroissant de précision
let topClassifications = classifications.prefix(2)
let descriptions = topClassifications.map { classification in
//Format d'affichage de la classification: "(0.37) cliff, drop, drop-off"
return String(format: " (%.2f) %@", classification.confidence, classification.identifier)
}
self.classificationLabel.text = "Classification:\n" + descriptions.joined(separator: "\n")
}
}
}
Acquérir l'image à classer. Laissez l'utilisateur sélectionner un appareil photo ou une photothèque à partir de l'affichage des alertes.
takePicture()Méthode
@IBAction func takePicture() {
//Afficher la sélection du sélecteur uniquement si la caméra est disponible
guard UIImagePickerController.isSourceTypeAvailable(.camera) else {
presentPhotoPicker(sourceType: .photoLibrary)
return
}
let photoSourcePicker = UIAlertController()
let takePhoto = UIAlertAction(title: "Take Photo", style: .default) { [unowned self] _ in
self.presentPhotoPicker(sourceType: .camera)
}
let choosePhoto = UIAlertAction(title: "Choose Photo", style: .default) { [unowned self] _ in
self.presentPhotoPicker(sourceType: .photoLibrary)
}
photoSourcePicker.addAction(takePhoto)
photoSourcePicker.addAction(choosePhoto)
photoSourcePicker.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
present(photoSourcePicker, animated: true)
}
Affichez l'appareil photo ou la photothèque.
presentPhotoPicker()Méthode
func presentPhotoPicker(sourceType: UIImagePickerController.SourceType) {
let picker = UIImagePickerController()
picker.delegate = self
picker.sourceType = sourceType
present(picker, animated: true)
}
Définissez l'image sélectionnée par le sélecteur sur la propriété du membre.
imagePickerController()Méthode
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
picker.dismiss(animated: true)
//Créer l'image d'origine renvoyée par cette méthode
let image = info[UIImagePickerController.InfoKey.originalImage] as! UIImage
imageView.image = image
updateClassifications(for: image)
}
Méthode takePicture ()
→ méthode
presentPhotoPicker (_: didFinishPickerMediaWithInfo:) `
Obtenez une image.
Méthode ʻimagePickerController (_: didFinishPickingMediaWithInfo :) Passez "l'image sélectionnée" à la méthode ʻupdateClassifications (for:)
pour démarrer le processus de classification.
Méthode ʻupdateClassifications (for:) Créez un gestionnaire de requêtes et exécutez-le. Le gestionnaire est traité comme une file d'attente d'arrière-plan. À ce stade, transmettez la propriété
classificationRequest` au gestionnaire.
Propriété classificationRequest
ʻUpdateClassifications (for :) La propriété est calculée en étant référencée à partir de la méthode, et la requête créée est retournée. Ici, la requête est passée à la méthode
processClassifications (for: _:)`.
Méthode processClassifications (for: _:)
L'affichage du résultat de la classification est traité de manière asynchrone dans la file d'attente principale.