CoreML 機器學習 VISION

ML是Machine Learning的縮寫,也就是‘機器學習’,這正是現在很火的一個技術,它也是人工智能最核心的內容。比如說橫掃世界棋壇大名鼎鼎的阿爾法狗(AlphaGo),或者已經深入大家生活場景的車牌識別,這其實都是機器學習的具體實際應用的結果。

‘機器學習’就是一種能讓計算機不需要不斷被人工顯示編程而能自己學習的人工智能技術,它不是通過具體的編碼算法,而是在大量的模型數據中找到一個合適的模式從而讓計算機能夠不斷的發展和完善自身算法。

這個技術所要模擬的就是一個龐大而複雜的‘神經網絡’,這個’神經網絡’就需要大量的訓練好的模型(model)來提供數據,使得這個’神經網絡’能對各種輸入(inputs)產生出一個對應的輸出結果(outputs),並且還能通過不斷的訓練數據來提高自己的算法準確性。

這裏的核心其實就是訓練各種模型(model)來處理各種不同的情況和需求。比如說有了一個專門識別人臉的模型,給一個輸入圖片他就能把人臉位置輸出出來等等等等。。。。最後如果將無限多的訓練好的模型都結合起來,那這個計算機可能就能像人一樣應對各種情況還能不斷地自我完善。


iOS11讓我們能做什麼?

上面說的看起來好像很複雜,其實。。。他真的很複雜

不過別擔心,複雜的是訓練模型,而我們要做的只是使用一些訓練好的模型來實現一些實際的需求。

蘋果新系統iOS11多了幾個新的開發庫,其中最核心的就是CoreML這個庫:官方文檔地址

根據官方文檔裏的這張圖就可以看出,其實它的作用就是將一個ML模型,轉換成我們的app工程可以直接使用的對象





所以不要擔心,使用它是很簡單的事情


除了CoreML,iOS11新出還有一個庫是很有用的,叫做Vision:官方文檔地址

這個庫是一個高性能的圖片分析庫,他能識別在圖片和視頻中的人臉、特徵、場景分類等

你如果打開Vision的官方文檔看,官方對他包含的所有類做了分類,比如有Face Detection and Recognition(人臉檢測識別)、Machine Learning Image Analysis(機器學習圖片分析)、Barcode Detection(條形碼檢測)、Text Detection(文本檢測)。。。。。等等等等

所以你可以這樣理解:Vision庫裏就已經自帶了很多訓練好的模型,這些模型是針對上面提到的人臉識別、條形碼檢測等等功能,如果你要實現的功能剛好是Vision庫本身就能實現的,那麼你直接使用Vision庫自帶的一些類和方法就行,也就沒有CoreML什麼事了。

那麼什麼時候Vision才需要CoreML呢?就是當你要使用一個你在網上找的訓練好的模型或者你自己訓練好的模型的時候,才需要CoreML來將這個相當於‘第三方’模型轉換成app認得的類,然後結合上面提到的Vision的Machine Learning Image Analysis(機器學習圖片分析)分類下的類和方法來使用這個模型進行圖形分析。


CoreML也可以看做一個模型的轉換器,可以將一個MLModel格式的模型文件自動生成一些類和方法,可以直接使用這些類去做分析,讓你更簡單是在app使用訓練好的模型。

Vision本身就是能對圖片視頻做分析的一個庫,他自帶了針對很多特徵檢測的功能,而他也能使用一個你設置好的MLModel來對圖片做分析。可以說在針對一些圖片分析的需求情況下,使用Vision結合CoreML的方法會比直接使用CoreML更加直觀,它應該是做了一點封裝

所以對於我們下面要說的例子,會給出兩種做法,一種就是隻使用CoreML,一種是使用CoreML + Vision


PS:其實上面說的有些不太準確,因爲Vision就是建立在CoreML層之上的,你使用Vision其實還是用到了CoreML,所以不可能沒有CoreML的事。只是你可能沒有顯式地直接寫CoreML的代碼而已。看下面這張官方給的圖你就應該明白了





工程實例講解

因爲CoreML和Vision都是iOS11纔有的功能,所以你要確保你有一臺裝了iOS11 beta版本的手機,還有一個beta版本的Xcode9:下載地址

開發語言我們使用Swift,Xcode9應該是用的Swift4了

1.模型文件下載

我們要做的demo例子就是使用一個下載的模型來識別圖像的場景,他能告訴我們這個圖片是海邊,還船舶或者森林等等。

可以先去蘋果官方下載一個已經訓練好的模型,叫 Places205-GoogLeNet:下載地址

這個模型是可以從圖片裏識別出205不同的場景。

進入這個網址滾到下面就能找到,你還可以看到他還有另外幾個模型,每個模型都有它的描述和大小,我們挑這個Places205-GoogLeNet模型稍微小一點的(24.5M),下載完會得到一個GoogLeNetPlaces.mlmodel文件,一會我就要使用它。


2.創建工程和引入模型

我們先打開Xcode,創建一個Single View模板的工程,然後將上面我們下載的那個GoogLeNetPlaces.mlmodel模型文件拖入工程






我們再工程裏單擊一下GoogLeNetPlaces.mlmodel這個文件,然後右邊就會顯示出這個文件的信息如下圖





我們先看右邊最下面那一欄Model Evaluation Parameters

可以看出這個模型需要的input是一個圖片,大小是224*224。

ouput會有兩個參數一個參數叫sceneLabelProbs是一個[string:Double]的字典數組,數組裏每一個字典就是這個輸入圖片分析得出可能的一個結果,string就是對圖片類型的描述,而double就是可能性百分比。另一個sceneLabel就是最有可能的一個一個結果描述


然後我們再看到右邊中間那一欄Model Class,下面有有一個GoogLeNetPlaces(Swift generated source),看到右邊有一個小箭頭麼?剛開是可能你還看不到這個小箭頭,因爲這個時候Xcode正在給這個模型自動生成他的類和方法,等你看到這個小箭頭了就可以點擊一下這個箭頭,就會進入這個模型自動生成的類的文件裏,可以看到這個模型會有三個類:

類GoogLeNetPlacesInput: 輸入源,可以看到驗證了我們上面說的他就是需要一個CVPixelBuffer格式的圖片作爲輸入




類GoogLeNetPlacesOutput:可以看到輸出的兩個參數sceneLabel和sceneLabelProbs正式我們上面有介紹過的所有可能的結果數組與最有可能的結果描述




GoogLeNetPlaces:如果單純使用CoreML的話,那就是調用這個類的Prediction方法來開始進行分析。如果是使用CoreML+Vision的話,就是使用這個類的model: MLModel屬性來創建Vision的分析request,稍後會做介紹






3.編寫準備代碼

接下來我們去到Storyboard來創建幾個要用的控件,因爲這是個小demo,我就直接用Storyboard了,平時我還是習慣用純代碼的




可以看到我們就加了三個控件

imageView:用來顯示等會我們從相冊選取的圖片

button: 底部有一個叫做Photo Library的按鈕,點擊它我們就彈出相冊圖片選擇界面

indicator:屏幕中間放了個用來顯示轉圈的indicator,用來在圖片分析等待的時候顯示,記得勾選他的hides When Stopped屬性


然後我們就直接去到ViewController文件給我們的控制器添加三個屬性,然後將這三個屬性跟我們的空間鏈接起來

@IBOutlet weak var button: UIButton!

@IBOutlet weak var imageView: UIImageView!

@IBOutlet weak var indicator: UIActivityIndicatorView!


然後給我們的button創建一個buttonClick方法,記得把這個方法跟button空間的touchUpInside鏈接起來

@IBAction func buttonClick(_ sender: UIButton) {

        let picker =  UIImagePickerController() 

        picker.delegate = self

        picker.sourceType = .photoLibrary

      present(picker, animated: true, completion: nil)

}


可以看到我們在點擊按鈕的方法裏先創建了一個ImagePicker,然後將delegate設置爲當前控制器,再設置我們要從手機相冊選取圖片而不是相機,最後將這個ImagePicker顯示出來。

因爲我們的控制器成爲了圖片選擇器的代理,所以現在讓我們的ViewController再遵守下面兩個協議

UIImagePickerControllerDelegate, UINavigationControllerDelegate

然後給ViewController加上下面這個協議方法實現

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

      if let image = info[“UIImagePickerControllerOriginalImage”] as? UIImage {

              imageView.image = image

        }

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

}

可以看到我們將取到的圖片顯示到我們的imageView裏面了

這個時候運行程序就能看到下面的樣子了(注意你手機相冊要有照片喔)






現在我們基本就準備好了基本的工程,現在能從相冊選擇圖片並且拿到這個圖片UIImage類型的對象了。接下來再添加一個下面一個輔助的方法到ViewController我們就正式開始我們的圖片分析

func showAnalysisResultOnMainQueue(with message: String) {

        //回到主線程上

        DispatchQueue.main.async {

                //創建alert

                let alert = UIAlertController(title: “Completed”, message: message,  preferredStyle: .alert)

                let cancelAct = UIAlertAction(title: “Cancel”, style: .cancel, handler: nil)

                alert.addAction(cancelAct)

              self.present(alert, animated: true) {

                    //點擊取消後允許用戶操作

                    self.indicator.stopAnimating()

                    self.view.isUserInteractionEnabled = true

              }

      }

}

這個方法就是方便等會對圖片分析完成後彈個框顯示一下結果用的,因爲我們等會的分析方法需要一點點耗時,所以我們會讓他在後臺線程去分析,完成後再調用這個方法回到主線程來顯示提示。

另外再添加一個下面ViewController的extension到ViewController文件裏,這個extension裏有兩個方法,也是我們等會要用到的輔助方法。

這兩個方法很長,又是C的方法,你可以不用理解他們具體實現的原理,只要知道一會我們用這個方法是爲了將一個UIImage類型的圖片對象,轉換成一個CVPixelBuffer類型的對象

extension ViewController {

    func CreatePixelBufferFromImage(_ image: UIImage) -> CVPixelBuffer?{

            let size = image.size

            var pxbuffer : CVPixelBuffer?

            let pixelBufferPool = createPixelBufferPool(Int32(size.width), Int32(size.height),        FourCharCode(kCVPixelFormatType_32BGRA), 2056)

            let status = CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pixelBufferPool!, &pxbuffer)

            guard (status == kCVReturnSuccess) else{

                    return nil

            }

            CVPixelBufferLockBaseAddress(pxbuffer!, CVPixelBufferLockFlags(rawValue: 0))

            let pxdata = CVPixelBufferGetBaseAddress(pxbuffer!)

            let rgbColorSpace = CGColorSpaceCreateDeviceRGB()

            let context = CGContext(data: pxdata,

                                                      width: Int(size.width),

                                                      height: Int(size.height),

                                                      bitsPerComponent: 8,

                                                      bytesPerRow: CVPixelBufferGetBytesPerRow(pxbuffer!),

                                                      space: rgbColorSpace,

                                                      bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue)

            context?.translateBy(x: 0, y: image.size.height)

            context?.scaleBy(x: 1.0, y: -1.0)

            UIGraphicsPushContext(context!)

            image.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height))

            UIGraphicsPopContext()

            CVPixelBufferUnlockBaseAddress(pxbuffer!, CVPixelBufferLockFlags(rawValue: 0))

          return pxbuffer

    }


    func createPixelBufferPool(_ width: Int32, _ height: Int32, _ pixelFormat: FourCharCode, _ maxBufferCount: Int32) -> CVPixelBufferPool? {

              var outputPool: CVPixelBufferPool? = nil

              let sourcePixelBufferOptions: NSDictionary = [kCVPixelBufferPixelFormatTypeKey: pixelFormat,

                                                                                          kCVPixelBufferWidthKey: width,

                                                                                          kCVPixelBufferHeightKey: height,

                                                                                        kCVPixelFormatOpenGLESCompatibility: true,

                                                                                        kCVPixelBufferIOSurfacePropertiesKey: NSDictionary()]

              let pixelBufferPoolOptions: NSDictionary = [kCVPixelBufferPoolMinimumBufferCountKey: maxBufferCount]

            CVPixelBufferPoolCreate(kCFAllocatorDefault, pixelBufferPoolOptions,        sourcePixelBufferOptions, &outputPool)

          return outputPool

      }

}




4.使用純CoreML進行圖片分析

現在就在當前ViewController文件內給ViewController類添加一個extension,我們將分析方法都寫在這個extension裏,然後添加兩個空方法

extension ViewController {

        //只使用CoreML方法來分析

        func analysisImageWithoutVision(image: UIImage) {

        }

      //使用CoreML+Vision方法來分析

        func analysisImageWithVision(image: UIImage) {

        }

}

我們就先只使用CoreML來實現,現在先在analysisImageWithoutVision(image:):方法里加上下面的代碼

indicator.startAnimating()

view.isUserInteractionEnabled = false

我們先顯示轉圈,告訴用戶當前在分析中,同時禁用了用戶交互


接下來繼續添加核心代碼到方法裏

//—————- 1——————–

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

          //—————- 2—————-

          let imageWidth:CGFloat = 224.0

          let imageHeight:CGFloat = 224.0

          UIGraphicsBeginImageContext(CGSize(width:imageWidth, height:imageHeight))

          image.draw(in:CGRect(x:0, y:0, width:imageHeight, height:imageHeight))

          let resizedImage = UIGraphicsGetImageFromCurrentImageContext()

          UIGraphicsEndImageContext()

          guard let newImage = resizedImage else {

                  fatalError(“resized Image fail”)

          }

          //—————- 3—————-

        guard let pixelBuffer = self.CreatePixelBufferFromImage(newImage) else {

              fatalError(“convert PixelBuffer fail”)

        }

        // —————-4—————-

        guard let output = try? GoogLeNetPlaces().prediction(sceneImage: pixelBuffer) else {

              fatalError(“predict fail”)

        }

        // —————-5—————-

        let result = “\(output.sceneLabel)(\(Int(output.sceneLabelProbs[output.sceneLabel]! * 100))%)”

      // —————-6—————-

      self.showAnalysisResultOnMainQueue(with: result)

}

分析處理完成後的流程是這樣

1.因爲我們要將圖片轉換爲224*224大小,還要將圖片轉成CVPixelBuffer格式,我們的模型才能對它進行分析,加上分析也比較耗時,所有我們先讓這些所有的動作都在後臺線程異步執行

2.將UIImage圖片變成224*224大小

3.調用我們事先添加的輔助方法將UIImage對象轉成CVPixelBuffer對象

4.正式調用GoogLeNetPlaces().prediction的方法進行圖片分析

5.把分析輸出的結果最有可能的那個描述 和 這個描述對應的準確率 作爲顯示的內容

6.調用我們最開始寫的輔助方法,返回主線程顯示分析結果


現在去只需要去imagePickerController獲取到圖片的代理方法裏,在“imageView.image = image”這一句後面添加調用一下我們寫好的分析方法即可

analysisImageWithoutVision(image: image)


現在運行一下程序看看是不是成功啦!






4.使用純CoreML + Vision進行圖片分析

再換種方法來做,結合Vision來做圖片分析其實更直觀,也不用再把圖片轉成合適大小的CVPixelBuffer那麼費勁了

現在去到analysisImageWithVision(image:)方法裏,將下面代碼加入進入

indicator.startAnimating()

view.isUserInteractionEnabled = false

//轉換圖片類型

guard let ciImage = CIImage(image: image) else {

      fatalError(“convert CIImage error”)

}

前兩句還是一樣先轉個圈,讓用戶暫停交互

再將圖片參數轉換爲CIImage格式,因爲等會需要這個格式,而不是UIImage


接下來再添加下面代碼

guard let model = try? VNCoreMLModel(for: GoogLeNetPlaces().model) else {

      fatalError(“load GoogLeNetPlaces model error”)

}

我們通過GoogLeNetPlaces().model這個模型參數創建了一個Vision庫裏面的VNCoreMLModel類型的模型對象。


繼續添加代碼

let request = VNCoreMLRequest(model: model) { [weak self] (request, error) in

          //—————- 1—————-

        guard let results = request.results as? [VNClassificationObservation] else {

                fatalError(“unexpected result type”)

          }

          // —————-2—————-

        guard let topResult = results.first else {

            fatalError(“No result!”)

        }

        // —————-3—————-

        let result = “\(topResult.identifier)(\(Int(topResult.confidence * 100))%)”

        //—————- 4—————-

        self?.showAnalysisResultOnMainQueue(with: result)

}

這裏我們使用了剛剛創建的model來創建一個VNCoreMLRequest請求,這個請求就會使用我們引入的模型來對圖片進行分析,同時設置了分析完成後的處理流程。

分析處理完成後的流程是這樣

1.判斷request.results結果數組是否爲VNClassificationObservation對象的數組,VNClassificationObservation是什麼呢?它就是對分析結果的一種描述類,因爲我們使用的模型就是用來識別不同類型的場景的,所以它是一個場景識別器,所以他的分析結果就會是ClassificationObservation類型,Vision庫還有很多其他的分析結果類型,你有興趣可以去看看,就能理解這個類型的含義了

2.從結果results結果數組裏,拿出第一個結果,因爲第一結果就是準確率最高的那個。

3.我們把剛剛取到的第一個結果的名字和準確率作爲顯示的內容,confidence就是準確率,大小爲0-1,如果是1也就是百分之100肯定。

4.調用我們最開始寫的輔助方法,返回主線程顯示分析結果


現在去imagePickerController獲取到圖片的代理方法裏,它分析方法調用替換爲我們的新方法

analysisImageWithVision(image: image)

現在運行程序再試試看是不是也是可以的








問題

不知道你們是否發現了有個問題,仔細看看我發的這兩個方法實現的運行結果gif圖。

我們只使用CoreML和使用CoreML+Vision兩種方法來做的圖片類型分析的結果有小小出入,然而針對同一張圖片,兩種方法分析得到的最可能的圖片類型都是pagoda(塔),但是準確率卻不一樣,第一種認爲51%是塔而第二種44%是塔,這是爲什麼呢?

我猜可能是因爲第一種方法對圖片做了大小的轉換導致的,兩個方法分析的圖片其實是有差別的了。另外也可能Vision使用MLModel做分析不止是簡單的封裝了一層,可能還有有一些自己特殊的處理。當然這只是我自己的猜想,如果有高人瞭解的具體原因的話也可以賜教一下

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章