【물체 검출】YOLOv4를 iOS상에서 움직인다

소개



마지막으로, 물체 탐지의 모델인 YOLO의 최신 버전 v4가 발표되었습니다!
속도를 유지한 채 크게 정밀도를 올릴 수 있었던 것 같습니다!

그런 YOLOv4를 이번에는 iOS에서 움직여 보았습니다.



환경



python=3.7.2
tensorflow=1.13.1
keras=2.3.1
coremltools=3.3

YOLOv4에 대한 학습된 모델 얻기



여기에서 다운로드할 수 있습니다.



학습된 모델 변환 Darknet → (Keras →) CoreML



Darknet에서 학습한 or 학습한 모델을 iOS에서 움직일 수 있도록 변환합니다.
내부에서는 일단 Keras의 모델로 변환되고 나서 CoreML용의 모델로 변환되고 있습니다.

컨버터는 YOLOv3의 것을 기반으로합니다. ( keras-yolo3/convert.py )
따라서 YOLOv4에서 채택한 Mish Activation 레이어에 새롭게 대응시켜야 합니다.

우선 레이어 정의에서

convert.py
class Mish(Layer):
    def __init__(self, **kwargs):
        super(Mish, self).__init__(**kwargs)
        self.supports_masking = True

    def call(self, inputs):
        return inputs * K.tanh(K.softplus(inputs))

    def get_config(self):
        config = super(Mish, self).get_config()
        return config

    def compute_output_shape(self, input_shape):
        return input_shape

이 레이어를 activation == 'Mish' 의 경우에 적응해 줍니다.

convert.py
if activation == 'linear':
    all_layers.append(prev_layer)
elif activation == 'mish':
    act_layer = Mish()(prev_layer)
    prev_layer = act_layer
    all_layers.append(act_layer)
elif activation == 'leaky':
    act_layer = LeakyReLU(alpha=0.1)(prev_layer)
    prev_layer = act_layer
    all_layers.append(act_layer)

또한 CoreML 측에도 Mish 레이어에 대한 정보를 제공해야 합니다.
제대로 className 를 주지 않으면 Xcode 측에서 올바르게 읽을 수없는 것 같습니다.

convert.py
def convert_mish(layer):
    params = NeuralNetwork_pb2.CustomLayerParams()
    params.className = "Mish"
    params.description = "Mish Activation Layer"
    return params

convert.py
coreml_model = coremltools.converters.keras.convert(
    model, input_names='input1', image_input_names='input1', 
    output_names=['output3', 'output2', 'output1'], image_scale=1/255.,
    add_custom_layers=True,custom_conversion_functions={ "Mish": convert_mish })

convert.py 소스 코드

zsh
python3 convert.py yolov4.cfg yolov4.weights yolov4.mlmodel

실행하면 다음과 유사한 오류가 발생하지만 문제가 없습니다. yolov4.mlmodel 가 작성되었다고 생각합니다.
You will not be able to run predict() on this Core ML model. Underlying exception message was: Error compiling model: "compiler error:  Error creating Core ML custom layer implementation from factory for layer "Mish".".
  RuntimeWarning)

iOS에서 실행



이번에 사용한 기기는 iPhone XS, iOS 13.3입니다.
작성한 .mlmodel를 사용하기 위해 이번 리포지토리를 사용했습니다.


.mlmodel를 프로젝트 폴더에 넣습니다.



모델을 이번에 만든 모델로 전환합니다.

YOLO.swift
// let model = YOLOv3()
let model = Yolov4()

Mish 레이어 만들기



Swift 측에도 Mish 레이어를 정의합니다.

Mish.swift
@objc(Mish) class Mish: NSObject, MLCustomLayer {
    // (略)
    func evaluate(inputs: [MLMultiArray], outputs: [MLMultiArray]) throws {
        for i in 0..<inputs.count {
            let input = inputs[i]
            let output = outputs[i]

            let count = input.count
            let iptr = UnsafeMutablePointer<Float>(OpaquePointer(input.dataPointer))
            let optr = UnsafeMutablePointer<Float>(OpaquePointer(output.dataPointer))

            var countAsInt32 = Int32(count)
            var one: Float = 1
            let vdspLength = vDSP_Length(count)

            vvexpf(optr, iptr, &countAsInt32)
            vDSP_vsadd(optr, 1, &one, optr, 1,vdspLength)
            vvlogf(optr, optr, &countAsInt32)
            vvtanhf(optr, optr, &countAsInt32)
            vDSP_vmul(optr, 1, iptr, 1, optr, 1, vdspLength)
        }
    }
}

후반에 vvexpf라든지, vDSP_vsadd 라든지의 키모인 함수가 줄지어 있습니다만, 수치 계산 등을 고속으로 처리해 주는 함수 같고,
Accelerate라고 말하는 것 같습니다. Apple Document | Accelerate

여기의 부분, 보기 쉽게 쓰면 다음과 같은 느낌이 됩니다만, YOLOv4 중에서 72회 불리고 있다고 하는 일도 있어, for문으로 1개 1개 처리하고 있으면 굉장히 무거워져 버리는군요, 그래서 배열로 단번에 계산하자는 느낌입니다.
for j in 0..<input.count {
    let x = input[j].floatValue
    let y = x / (1 + exp(-x))
    output[j] = NSNumber(value: y)
}

그 밖에도 GPU를 사용하도록 하고 있습니다.Mish.metalMish.swift 와 같은 장소에 둡니다.

Mish.swift
Mish.metal

실행의 모습



무사히 실행할 수 있었지만 전반적으로 모사리입니다. 그리고, 정밀도도 YOLOv3 와 비교해 오히려 나빠지고 있는 것 같은 인상을 받습니다.

YOLOv4





YOLOv3





조사해 보면, Mish 레이어를 무효로 하고 있는 경우의 CPU 사용률은 160% 정도(아마 최대 400%), 메모리 사용량은 60MB 정도인 것에 대해, 유효하게 하고 있는 경우의 CPU 사용률은 30% 정도, 사용 메모리는 250MB까지 증가합니다.

메모리를 굉장히 사용하는데 있어서, CPU를 그다지 사용할 수 없다고 하는 형태군요. 이 근처는 Swift 측의 코드를 최적화하는 것이 맞을 것 같습니다.

좋은 웹페이지 즐겨찾기