喋る同人誌が作りたい!⑧ iOS版+OpenCV リアルタイムカメラ処理
iOSはAndroidのCamera2 APIに比べてかなりシンプルに利用できました。
// UIに描き戻すだけ class AVCaptureViewController: UIViewController{ @IBOutlet weak var matchPreviewView: UIImageView! let avCapture = AVCapture() let openCv = OpenCv() override func viewDidLoad() { super.viewDidLoad() avCapture.delegate = self } func capture(image: UIImage) { matchPreviewView.image = openCv.filter(image) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } } // カメラに関するクラス class AVCapture:NSObject, AVCaptureVideoDataOutputSampleBufferDelegate { var captureSession: AVCaptureSession! var delegate: AVCaptureViewController? var counter = 0 //更に処理を少なくする override init(){ super.init() captureSession = AVCaptureSession() // 解像度 captureSession.sessionPreset = AVCaptureSessionPreset640x480 let videoDevice = AVCaptureDevice.defaultDevice( withMediaType: AVMediaTypeVideo) // カメラ videoDevice?.activeVideoMinFrameDuration = CMTimeMake(1, 30)// 1/30秒 (1秒間に30フレーム) let videoInput = try! AVCaptureDeviceInput.init(device: videoDevice) captureSession.addInput(videoInput) let videoDataOutput = AVCaptureVideoDataOutput() videoDataOutput.setSampleBufferDelegate( self, queue: DispatchQueue.main) // ピクセルフォーマット(32bit BGRA) videoDataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as AnyHashable : Int(kCVPixelFormatType_32BGRA)] // 処理中の場合は、フレームを破棄する videoDataOutput.alwaysDiscardsLateVideoFrames = false captureSession.addOutput(videoDataOutput) DispatchQueue.global(qos: .userInitiated).async { self.captureSession.startRunning() } } // 新しいキャプチャの追加で呼ばれる(1/30秒に1回) func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, from connection: AVCaptureConnection!) { if (counter % 5) == 0 { let image = imageFromSampleBuffer(sampleBuffer: sampleBuffer) delegate?.capture(image: image) } counter += 1 } func imageFromSampleBuffer(sampleBuffer :CMSampleBuffer) -> UIImage { let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)! // イメージバッファのロック CVPixelBufferLockBaseAddress( imageBuffer, CVPixelBufferLockFlags(rawValue: 0)) // 画像情報を取得 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()! let image = UIImage(cgImage: imageRef, scale: 1.0, orientation: UIImageOrientation.right) // イメージバッファのアンロック CVPixelBufferUnlockBaseAddress(imageBuffer, CVPixelBufferLockFlags(rawValue: 0)) return image } }
という感じでカメラ映像を取得するだけならこんな感じです…が、それをOpenCVで加工・分析していかなければなりません。それがどうせSwiftラッパーがあるだろうと思っていたのですが、ないんですよこれが…つまり、Objective-CまたはC++で書くしかないのですわよ。
// C++ using namespace std; using namespace cv; class FeaturedImage{ Ptr<AKAZE> akaze; Mat mat; public: Ptr<vector<cv::KeyPoint>> keyPoints; Ptr<Mat> descriptors; FeaturedImage(const cv::Mat& image){ akaze = cv::AKAZE::create(); mat = image; cvtColor(mat, mat, CV_BGR2GRAY); // サイズが大きいとなぜかUIImageが全て描画されなくなってしまう resize(mat, mat, cv::Size(200, 100)); keyPoints = new vector<KeyPoint>(); descriptors = new Mat(); akaze->detectAndCompute(mat, noArray(), *keyPoints, *descriptors); drawKeypoints(mat, *keyPoints, mat); } FeaturedImage(const NSString& path){ } ~FeaturedImage(){ mat.release(); keyPoints.release(); descriptors.release(); akaze.release(); } cv::Mat getMat(){ return mat; } }; // Objective-C @implementation OpenCv : NSObject - (id) init { return self; } -(UIImage *)Filter:(UIImage *)image{ // 方向を修正 UIGraphicsBeginImageContext(image.size); [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)]; image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); //UIImageをcv::Matに変換 cv::Mat mat; UIImageToMat(image, mat); FeaturedImage fi(mat); return MatToUIImage(fi.getMat()); } @end
つらみ(´・ω・`)
加えてSSではちゃんと特徴点をマークした画像が表示されていますが、AKAZEに掛ける画像のサイズを200x200ぐらい以上にしたとたん、UIに画像が描画されなくなってしまいます。デバッガ上では正しく処理されていく様は確認できるのですが、なぜか UIImageView.image = processedImage
が反映されません。メモリリークしているんじゃないかと思っているのですが、プロファイラでは確認できず、詳細な調査が必要そうで先が思いやられます。