Core ML框架詳細(xì)解析(二十一) —— 在iOS設(shè)備上使用Style Transfer創(chuàng)建一個(gè)自定義圖像濾波器(二)

版本記錄

版本號(hào) 時(shí)間
V1.0 2022.09.11 星期日

前言

目前世界上科技界的所有大佬一致認(rèn)為人工智能是下一代科技革命浮定,蘋果作為科技界的巨頭,當(dāng)然也會(huì)緊跟新的科技革命的步伐据某,其中ios API 就新出了一個(gè)框架Core ML。ML是Machine Learning的縮寫诗箍,也就是機(jī)器學(xué)習(xí)癣籽,這正是現(xiàn)在很火的一個(gè)技術(shù),它也是人工智能最核心的內(nèi)容滤祖。感興趣的可以看我寫的下面幾篇筷狼。
1. Core ML框架詳細(xì)解析(一) —— Core ML基本概覽
2. Core ML框架詳細(xì)解析(二) —— 獲取模型并集成到APP中
3. Core ML框架詳細(xì)解析(三) —— 利用Vision和Core ML對(duì)圖像進(jìn)行分類
4. Core ML框架詳細(xì)解析(四) —— 將訓(xùn)練模型轉(zhuǎn)化為Core ML
5. Core ML框架詳細(xì)解析(五) —— 一個(gè)Core ML簡(jiǎn)單示例(一)
6. Core ML框架詳細(xì)解析(六) —— 一個(gè)Core ML簡(jiǎn)單示例(二)
7. Core ML框架詳細(xì)解析(七) —— 減少Core ML應(yīng)用程序的大小(一)
8. Core ML框架詳細(xì)解析(八) —— 在用戶設(shè)備上下載和編譯模型(一)
9. Core ML框架詳細(xì)解析(九) —— 用一系列輸入進(jìn)行預(yù)測(cè)(一)
10. Core ML框架詳細(xì)解析(十) —— 集成自定義圖層(一)
11. Core ML框架詳細(xì)解析(十一) —— 創(chuàng)建自定義圖層(一)
12. Core ML框架詳細(xì)解析(十二) —— 用scikit-learn開(kāi)始機(jī)器學(xué)習(xí)(一)
13. Core ML框架詳細(xì)解析(十三) —— 使用Keras和Core ML開(kāi)始機(jī)器學(xué)習(xí)(一)
14. Core ML框架詳細(xì)解析(十四) —— 使用Keras和Core ML開(kāi)始機(jī)器學(xué)習(xí)(二)
15. Core ML框架詳細(xì)解析(十五) —— 機(jī)器學(xué)習(xí):分類(一)
16. Core ML框架詳細(xì)解析(十六) —— 人工智能和IBM Watson Services(一)
17. Core ML框架詳細(xì)解析(十七) —— Core ML 和 Vision簡(jiǎn)單示例(一)
18. Core ML框架詳細(xì)解析(十八) —— 基于Core ML 和 Vision的設(shè)備上的訓(xùn)練(一)
19. Core ML框架詳細(xì)解析(十九) —— 基于Core ML 和 Vision的設(shè)備上的訓(xùn)練(二)
20. Core ML框架詳細(xì)解析(二十) —— 在iOS設(shè)備上使用Style Transfer創(chuàng)建一個(gè)自定義圖像濾波器(一)

源碼

1. Swift

首先看下工程組織結(jié)構(gòu)

下面就是正文了

1. AppMain.swift
import SwiftUI

@main
struct AppMain: App {
  var body: some Scene {
    WindowGroup {
      ContentView()
    }
  }
}
2. ContentView.swift
import SwiftUI

struct AlertMessage: Identifiable {
  let id = UUID()
  var title: Text
  var message: Text
  var actionButton: Alert.Button?
  var cancelButton: Alert.Button = .default(Text("OK"))
}

struct PickerInfo: Identifiable {
  let id = UUID()
  let picker: PickerView
}

struct ContentView: View {
  @State private var image: UIImage?
  @State private var styleImage: UIImage?
  @State private var stylizedImage: UIImage?
  @State private var processing = false
  @State private var showAlertMessage: AlertMessage?
  @State private var showImagePicker: PickerInfo?

  var body: some View {
    VStack {
      Text("PETRA")
        .font(.title)
      Spacer()
      Button(action: {
        if self.stylizedImage != nil {
          self.showAlertMessage = .init(
            title: Text("Choose new image?"),
            message: Text("This will clear the existing image!"),
            actionButton: .destructive(
              Text("Continue")) {
                self.stylizedImage = nil
                self.image = nil
                self.showImagePicker = PickerInfo(picker: PickerView(selectedImage: self.$image))
            },
            cancelButton: .cancel(Text("Cancel")))
        } else {
          self.showImagePicker = PickerInfo(picker: PickerView(selectedImage: self.$image))
        }
      }, label: {
        if let anImage = self.stylizedImage ?? self.image {
          Image(uiImage: anImage)
            .resizable()
            .scaledToFit()
            .aspectRatio(contentMode: ContentMode.fit)
            .border(.blue, width: 3)
        } else {
          Text("Choose a Pet Image")
            .font(.callout)
            .foregroundColor(.blue)
            .padding()
            .cornerRadius(10)
            .border(.blue, width: 3)
        }
      })
      Spacer()
      Text("Choose Style to Apply")
      Button(action: {
        self.showImagePicker = PickerInfo(picker: PickerView(selectedImage: self.$styleImage))
      }, label: {
        Image(uiImage: styleImage ?? UIImage(named: Constants.Path.presetStyle1) ?? UIImage())
          .resizable()
          .frame(width: 100, height: 100, alignment: .center)
          .scaledToFit()
          .aspectRatio(contentMode: ContentMode.fit)
          .cornerRadius(10)
          .border(.blue, width: 3)
      })
      Button(action: {
        guard let petImage = image, let styleImage = styleImage ?? UIImage(named: Constants.Path.presetStyle1) else {
          self.showAlertMessage = .init(
            title: Text("Error"),
            message: Text("You need to choose a Pet photo before applying the style!"),
            actionButton: nil,
            cancelButton: .default(Text("OK")))
          return
        }
        if !self.processing {
          self.processing = true
          MLStyleTransferHelper.shared.applyStyle(styleImage, on: petImage) { stylizedImage in
            processing = false
            self.stylizedImage = stylizedImage
          }
        }
      }, label: {
        Text(self.processing ? "Processing..." : "Apply Style!")
          .padding(EdgeInsets.init(top: 4, leading: 8, bottom: 4, trailing: 8))
          .font(.callout)
          .background(.blue)
          .foregroundColor(.white)
          .cornerRadius(8)
      })
      .padding()
    }
    .sheet(item: self.$showImagePicker) { pickerInfo in
      return pickerInfo.picker
    }
    .alert(item: self.$showAlertMessage) { alertMessage in
      if let actionButton = alertMessage.actionButton {
        return Alert(
          title: alertMessage.title,
          message: alertMessage.message,
          primaryButton: actionButton,
          secondaryButton: alertMessage.cancelButton)
      } else {
        return Alert(
          title: alertMessage.title,
          message: alertMessage.message,
          dismissButton: alertMessage.cancelButton)
      }
    }
  }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}
3. ImagePicker.swift
import Foundation
import SwiftUI
import UIKit

struct PickerView: UIViewControllerRepresentable {
  @Binding var selectedImage: UIImage?
  @Environment(\.presentationMode) private var presentationMode
  func makeUIViewController(context: Context) -> UIImagePickerController {
    let imagePicker = UIImagePickerController()
    imagePicker.sourceType = .photoLibrary
    imagePicker.delegate = context.coordinator
    return imagePicker
  }
  func makeCoordinator() -> Coordinator {
    Coordinator { image in
      self.selectedImage = image
      self.presentationMode.wrappedValue.dismiss()
    }
  }
  func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {
  }
  // Coordinator -
  final class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    private let onComplete: (UIImage?) -> Void
    init(withCompletion onComplete: @escaping (UIImage?) -> Void) {
      self.onComplete = onComplete
    }
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
      if let image = info[.originalImage] as? UIImage {
        self.onComplete(image.upOrientationImage())
      }
    }
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
      self.onComplete(nil)
    }
  }
}
4. MLStyleTransferHelper.swift
import Foundation
import SwiftUI
import UIKit

struct MLStyleTransferHelper {
  static var shared = MLStyleTransferHelper()
  private var trainedModelPath: URL?
  mutating func applyStyle(_ styleImg: UIImage, on petImage: UIImage, onCompletion: @escaping (UIImage?) -> Void) {
    let sessionID = UUID()
    let sessionDir = Constants.Path.sessionDir.appendingPathComponent(sessionID.uuidString, isDirectory: true)
    debugPrint("Starting session in directory: \(sessionDir)")
    let petImagePath = Constants.Path.documentsDir.appendingPathComponent("MyPetImage.jpeg")
    let styleImagePath = Constants.Path.documentsDir.appendingPathComponent("StyleImage.jpeg")
    guard
      let petImageURL = petImage.saveImage(path: petImagePath),
      let styleImageURL = styleImg.saveImage(path: styleImagePath)
    else {
      debugPrint("Error Saving the image to disk.")
      return onCompletion(nil)
    }
    do {
      try FileManager.default.createDirectory(at: sessionDir, withIntermediateDirectories: true)
    } catch {
      debugPrint("Error creating directory: \(error.localizedDescription)")
      return onCompletion(nil)
    }
    // 1
    MLModelTrainer.trainModel(using: styleImageURL, validationImage: petImageURL, sessionDir: sessionDir) { modelPath in
      guard
        let aModelPath = modelPath
      else {
        debugPrint("Error creating the ML model.")
        return onCompletion(nil)
      }
      // 2
      MLPredictor.predictUsingModel(aModelPath, inputImage: petImage) { stylizedImage in
        onCompletion(stylizedImage)
      }
    }
  }
}
5. MLModelTrainer.swift
import Foundation
import CreateML
import Combine

enum MLModelTrainer {
  private static var subscriptions = Set<AnyCancellable>()
  static func trainModel(using styleImage: URL, validationImage: URL, sessionDir: URL, onCompletion: @escaping (URL?) -> Void) {
    // 1
    let dataSource = MLStyleTransfer.DataSource.images(
      styleImage: styleImage,
      contentDirectory: Constants.Path.trainingImagesDir ?? Bundle.main.bundleURL,
      processingOption: nil)
    // 2
    let sessionParams = MLTrainingSessionParameters(
      sessionDirectory: sessionDir,
      reportInterval: Constants.MLSession.reportInterval,
      checkpointInterval: Constants.MLSession.checkpointInterval,
      iterations: Constants.MLSession.iterations)
    // 3
    let modelParams = MLStyleTransfer.ModelParameters(
      algorithm: .cnn,
      validation: .content(validationImage),
      maxIterations: Constants.MLModelParam.maxIterations,
      textelDensity: Constants.MLModelParam.styleDensity,
      styleStrength: Constants.MLModelParam.styleStrength)
    // 4
    guard let job = try? MLStyleTransfer.train(
      trainingData: dataSource,
      parameters: modelParams,
      sessionParameters: sessionParams) else {
      onCompletion(nil)
      return
    }
    // 5
    let modelPath = sessionDir.appendingPathComponent(Constants.Path.modelFileName)
    job.result.sink(receiveCompletion: { result in
      debugPrint(result)
    }, receiveValue: { model in
      do {
        try model.write(to: modelPath)
        onCompletion(modelPath)
        return
      } catch {
        debugPrint("Error saving ML Model: \(error.localizedDescription)")
      }
      onCompletion(nil)
    })
    .store(in: &subscriptions)
  }
}
6. MLPredictor.swift
import Foundation
import UIKit
import Vision
import CoreML

enum MLPredictor {
  static func predictUsingModel(_ modelPath: URL, inputImage: UIImage, onCompletion: @escaping (UIImage?) -> Void) {
    // 1
    guard
      let compiledModel = try? MLModel.compileModel(at: modelPath),
      let mlModel = try? MLModel.init(contentsOf: compiledModel)
    else {
      debugPrint("Error reading the ML Model")
      return onCompletion(nil)
    }
    // 2
    let imageOptions: [MLFeatureValue.ImageOption: Any] = [
      .cropAndScale: VNImageCropAndScaleOption.centerCrop.rawValue
    ]
    guard
      let cgImage = inputImage.cgImage,
      let imageConstraint = mlModel.modelDescription.inputDescriptionsByName["image"]?.imageConstraint,
      let inputImg = try? MLFeatureValue(cgImage: cgImage, constraint: imageConstraint, options: imageOptions),
      let inputImage = try? MLDictionaryFeatureProvider(dictionary: ["image": inputImg])
    else {
      return onCompletion(nil)
    }
    // 3
    guard
      let stylizedImage = try? mlModel.prediction(from: inputImage),
      let imgBuffer = stylizedImage.featureValue(for: "stylizedImage")?.imageBufferValue
    else {
      return onCompletion(nil)
    }
    let stylizedUIImage = UIImage(withCVImageBuffer: imgBuffer)
    return onCompletion(stylizedUIImage)
  }
}
7. Constants.swift
import Foundation

enum Constants {
  enum Path {
    static let trainingImagesDir = Bundle.main.resourceURL?.appendingPathComponent("TrainingData")
    static var documentsDir: URL = {
      return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
    }()
    static let sessionDir = documentsDir.appendingPathComponent("Session", isDirectory: true)
    static let modelFileName = "StyleTransfer.mlmodel"
    static let presetStyle1 = "PresetStyle_1"
  }
  enum MLSession {
    static var iterations = 100
    static var reportInterval = 50
    static var checkpointInterval = 25
  }
  enum MLModelParam {
    static var maxIterations = 200
    static var styleDensity = 128 // Multiples of 4
    static var styleStrength = 5 // Range 1 to 10
  }
}
8. UIImage+Utilities.swift
import Foundation
import UIKit
import VisionKit

extension UIImage {
  func saveImage(path: URL) -> URL? {
    guard
      let data = self.jpegData(compressionQuality: 0.8),
      (try? data.write(to: path)) != nil
    else {
      return nil
    }
    return path
  }
  convenience init?(withCVImageBuffer cvImageBuffer: CVImageBuffer) {
    let ciImage = CIImage(cvImageBuffer: cvImageBuffer)
    let context = CIContext.init(options: nil)
    guard
      let cgImage = context.createCGImage(ciImage, from: ciImage.extent)
    else {
      return nil
    }
    self.init(cgImage: cgImage)
  }
  func upOrientationImage() -> UIImage? {
    switch imageOrientation {
    case .up:
      return self
    default:
      UIGraphicsBeginImageContextWithOptions(size, false, scale)
      draw(in: CGRect(origin: .zero, size: size))
      let result = UIGraphicsGetImageFromCurrentImageContext()
      UIGraphicsEndImageContext()
      return result
    }
  }
}

后記

本篇主要講述了在iOS設(shè)備上使用Style Transfer創(chuàng)建一個(gè)自定義圖像濾波器匠童,感興趣的給個(gè)贊或者關(guān)注~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末埂材,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子汤求,更是在濱河造成了極大的恐慌俏险,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扬绪,死亡現(xiàn)場(chǎng)離奇詭異竖独,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)勒奇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門预鬓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)巧骚,“玉大人赊颠,你說(shuō)我怎么就攤上這事∨耄” “怎么了竣蹦?”我有些...
    開(kāi)封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)沧奴。 經(jīng)常有香客問(wèn)我痘括,道長(zhǎng),這世上最難降的妖魔是什么滔吠? 我笑而不...
    開(kāi)封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任纲菌,我火速辦了婚禮,結(jié)果婚禮上疮绷,老公的妹妹穿的比我還像新娘翰舌。我一直安慰自己,他們只是感情好冬骚,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布椅贱。 她就那樣靜靜地躺著懂算,像睡著了一般。 火紅的嫁衣襯著肌膚如雪庇麦。 梳的紋絲不亂的頭發(fā)上计技,一...
    開(kāi)封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音山橄,去河邊找鬼垮媒。 笑死,一個(gè)胖子當(dāng)著我的面吹牛航棱,可吹牛的內(nèi)容都是我干的涣澡。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼丧诺,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼入桂!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起驳阎,我...
    開(kāi)封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤抗愁,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后呵晚,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蜘腌,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年饵隙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了撮珠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡金矛,死狀恐怖芯急,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情驶俊,我是刑警寧澤娶耍,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站饼酿,受9級(jí)特大地震影響榕酒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜故俐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一想鹰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧药版,春花似錦辑舷、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)株汉。三九已至,卻和暖如春歌殃,著一層夾襖步出監(jiān)牢的瞬間乔妈,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工氓皱, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留路召,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓波材,卻偏偏與公主長(zhǎng)得像股淡,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子廷区,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容