質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.51%

[Swift3]カメラ画像→ピクセルデータ配列→UIImageと順番に変換したい[編集]

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,143

nobu09

score 28

 前提・実現したいこと

カメラからのキャプチャ画像に何かしらの操作をして、その画像をUIImageViewで表示したいです。

 発生している問題・エラーメッセージ[編集]

キャプチャ画像→ピクセルデータ配列→何かしらの処理→UIImageという処理をしたいのですが、
メソッド内で作成したUIImagを返り値で別の変数に代入した際、タイミングによって代入先の画像が壊れています。

具体的には、以下のソースコード内の
(1)の部分でreturnした場合は、正常に画像が渡され表示できます。
(2)の部分でreturnした場合は、異常な画像データが渡されてしまいます。

 該当のソースコード[編集]

class ViewController: UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate {

    @IBOutlet var previewView: UIImageView!

    var session = AVCaptureSession()
    var videoOutput = AVCaptureVideoDataOutput()

    var images:[UIImage] = []

    var count: Int = 0

    override func viewDidLoad() {
        super.viewDidLoad()

        if session.isRunning {
            return
        }

        images.removeAll()

        setupInputOutput()

        self.session.startRunning()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    func setupInputOutput() {

        session.sessionPreset = AVCaptureSession.Preset.medium

        do {
            let device = AVCaptureDevice.default(
                AVCaptureDevice.DeviceType.builtInWideAngleCamera,
                for: AVMediaType.video,
                position: AVCaptureDevice.Position.back
            )
            device?.activeVideoMinFrameDuration = CMTimeMake(1, 30)

            let input = try! AVCaptureDeviceInput(device: device!)
            if session.canAddInput(input) {
                session.addInput(input)
            } else {
                print("Can't add input to session")
                return
            }
        } catch let error as NSError {
            print("no camera \(error)")
            return
        }

        videoOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as AnyHashable : Int(kCVPixelFormatType_32BGRA)] as! [String : Any]
        videoOutput.alwaysDiscardsLateVideoFrames = true
        videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main)
        if session.canAddOutput(videoOutput) {
            session.addOutput(videoOutput)
        } else {
            print("Can't add output to session")
            return
        }
    }

    func setPreviewLayer() {
        let previewLayer = AVCaptureVideoPreviewLayer(session: session)
        guard let videoLayer: AVCaptureVideoPreviewLayer = previewLayer else {
            print("Can't create preview layer.")
            return
        }

        videoLayer.frame = previewView.bounds
        videoLayer.masksToBounds = true
        videoLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
        previewView.layer.addSublayer(videoLayer)
    }

    func captureOutput(_ captureOutput: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
        let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)!
        // イメージバッファのロック
        CVPixelBufferLockBaseAddress(imageBuffer, CVPixelBufferLockFlags(rawValue: 0))

        self.imageFromSampleBuffer(sampleBuffer: sampleBuffer)

        CVPixelBufferUnlockBaseAddress(imageBuffer, CVPixelBufferLockFlags(rawValue: 0))

        DispatchQueue.main.async {
            self.previewView.image = image
        }
    }

    func imageFromSampleBuffer(sampleBuffer :CMSampleBuffer) -> UIImage {
        let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)!

        // 画像情報を取得
        let base = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0)!
        let bytesPerRow = UInt(CVPixelBufferGetBytesPerRow(imageBuffer))
        let width = UInt(CVPixelBufferGetWidth(imageBuffer))
        let height = UInt(CVPixelBufferGetHeight(imageBuffer))

        // ビットマップコンテキスト作成
        let colorSpace = CGColorSpaceCreateDeviceRGB()
        let bitsPerCompornent = 8
        let bitmapInfo = CGBitmapInfo(rawValue: (CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue) as UInt32)
        let newContext = CGContext(data: base, width: Int(width), height: Int(height), bitsPerComponent: Int(bitsPerCompornent), bytesPerRow: Int(bytesPerRow), space: colorSpace, bitmapInfo: bitmapInfo.rawValue)! as CGContext

        // 画像作成
        let imageRef = newContext.makeImage()!

        // (1)ここでimgを返すとキャプチャした画像が正常に表示される
        //let img = UIImage(cgImage: imageRef, scale: 1.0, orientation: UIImageOrientation.right)
        //return img

        let data = imageRef.dataProvider!.data
        let length = CFDataGetLength(data)
        var rawData = [UInt8](repeating: 0, count: length)
        CFDataGetBytes(data, CFRange(location: 0, length: length), &rawData)

        let provider = CGDataProvider(dataInfo: nil,data: &rawData, size: length,releaseData: releaseData)
        let bitsPerPixel = imageRef.bitsPerPixel
        let cgImage = CGImage(width: Int(width), height: Int(height), bitsPerComponent: bitsPerCompornent, bitsPerPixel: bitsPerPixel, bytesPerRow:Int(bytesPerRow), space: colorSpace, bitmapInfo: bitmapInfo, provider: provider!, decode: nil, shouldInterpolate: false, intent: .defaultIntent)

        let editedimage = UIImage(cgImage:cgImage!, scale:1.0, orientation:UIImageOrientation.right)

        // (2)ここで返すと画像が異常になる。
        return editedimage
    }
}

 補足情報(FW/ツールのバージョンなど)

xcode Version 9.4.1
Swift4.0

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • nobu09

    2018/08/03 14:58

    その部分はコメントアウトにしています。ソースコードが間違っており申し訳ありません。

    キャンセル

  • u39ueda

    2018/08/03 15:10

    実際に試してみたんですが、for文をコメントアウトして動かすとクラッシュしませんか?

    キャンセル

  • nobu09

    2018/08/03 19:59

    iphone5sで動作確認していますが、ちゃんと動いています。。。念のため、全ソースコードを載せましたのでご確認お願い致します。

    キャンセル

回答 1

checkベストアンサー

+2

func captureOutput(_ captureOutput: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) がメインスレッド以外から呼ばれていませんか?
sampleBufferCallbackQueue にセットされているDispatchQueueから呼ばれるようです。

もしメインスレッドから呼んでいた場合は getByteArrayFromImage() の実行をCVPixelBufferUnlockBaseAddress() の前に移動してみてはどうでしょうか。

 追記

実際に試してみましたが以下のような問題があるようです。

  • メインスレッドでfor文をぶん回しているせいで処理に時間がかかっている
    →バックグラウンドスレッドで実行させればマシになる(それでもカクカクだけど)
    →for文をコメントアウトするならそのままでもOK
  • 真っ白に表示されるのはforで真っ白に塗り潰しているから
    →コメントアウト
  • rawDataが画像として使っている間に解放されてしまってクラッシュする
    今クラッシュしていないのはよくわからない。CPU使用率100%だから?
    →UnsafeMutablePointer.allocate(capacity:)を使ってスコープを抜けても解放されないようにする

rawDataを解放されないようにするのと、for文をコメントアウトすれば動きました。
変更前のコードはコメントアウトしてありますので見比べてください。

func getByteArrayFromImage(imageRef: CGImage) -> UIImage {
    let editedimage:UIImage

    let width_n :Int = Int((imageRef.width))
    let height_n :Int = Int((imageRef.height))

    let data = imageRef.dataProvider!.data
    let length = CFDataGetLength(data)
//    var rawData = [UInt8](repeating: 0, count: length)
    let rawData = UnsafeMutablePointer<UInt8>.allocate(capacity: length)

//    CFDataGetBytes(data, CFRange(location: 0, length: length), &rawData)
    CFDataGetBytes(data, CFRange(location: 0, length: length), rawData)
    // for文で回すならUnsafeMutableBufferPointerを使う
    let rawDataBuffer = UnsafeMutableBufferPointer<UInt8>(start: rawData, count: length)
    //for (index, _) in rawData.enumerated() {
    //    rawData[index] = 255
    //}

//    let provider = CGDataProvider(dataInfo: nil, data: &rawData, size: length,releaseData: releaseData)
    let provider = CGDataProvider(dataInfo: nil, data: rawData, size: length,releaseData: releaseData)
    let colorSpaceRef = imageRef.colorSpace
    let bitmapInfo_n = imageRef.bitmapInfo
    let bitsPerComponent:Int =  imageRef.bitsPerComponent
    let bitsPerPixel = imageRef.bitsPerPixel
    let bytesPerRow_n = imageRef.bytesPerRow
    let cgImage = CGImage(width: width_n, height: height_n, bitsPerComponent: bitsPerComponent, bitsPerPixel: bitsPerPixel, bytesPerRow:bytesPerRow_n, space: colorSpaceRef!, bitmapInfo: bitmapInfo_n, provider: provider!, decode: nil, shouldInterpolate: false, intent: .defaultIntent)

    editedimage = UIImage(cgImage:cgImage!, scale:1.0, orientation:UIImageOrientation.right)

    // (2) editedimageには画像データが正常に入っている
    return editedimage

}

// releaseDataの定義が見当たらなかったので適当に実装しました
func releaseData(info: UnsafeMutableRawPointer?, data: UnsafeRawPointer, length: Int) {
    print(#function, info ?? "nil", data, length)
    data.deallocate()
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/08/02 21:57

    以下の通り、メインスレッドで実行して見ましたが、変化はありませんでした。

    ```
    func captureOutput() {
    DispatchQueue.main.async {
      let image = self.imageFromSampleBuffer(sampleBuffer: sampleBuffer)
    previewView.image = image
    }
    }
    ```

    また、`getByteArrayFromImage()`を`CVPixelBufferUnlockBaseAddress()`の前に移動しましたが、こちらも変化はありませんでした。

    キャンセル

  • 2018/08/03 18:25

    回答に追記しました。
    for文をコメントアウトするとこちらではクラッシュしたのですが、そちらではそのようなことを言及していないので何か違いがあるのかもしれません。

    キャンセル

  • 2018/08/03 22:17

    ソースコードまで記載していただきありがとうございます。
    ソースコード通りに修正したところ、動作しました。
    また、こちらではクラッシュしないので、他の部分が違うのかもしれません。
    どうもありがとうございました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.51%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる