本文爲做者原創,轉載請註明出處(http://www.cnblogs.com/mar-q/)by 負贔屓ios
其實標題中這個問題並不許確,準確的說法應該是iOS下的OpenCV開發是使用OC仍是Swift+OC。這個問題糾結了好久,研究了不少例子。先說結論:若是用到的算法規模不大且不熟悉cap_ios.h儘可能用Swift+OC。(歡迎高手來打臉)git
iOS下OpenCV開發的例子不少,你們能夠直接去GitHub上扒拉,可是Swift仍是比較少,先貢獻幾個能運行的例子:Objective-C(《Instant OpenCV for iOS》的例程,做者是 Alexander Shishkov和 Kirill Kornyakov,OpenCV的活躍分子),Swift(做者是Hiroki Ishiura,一個日本的程序員老哥,飽受禿頂的困擾。。。)16年Joseph Howse出版了一本書《iOS Application Development with OpenCV 3》,算是小白的入門教程吧(目前無中文,正版很貴,並且比較難下到電子書,Google圖書裏有部分英文電子版),第一章就提升了關於language的選擇,因爲opencv的核心是用C++寫的,Swift不能直接調用C++,須要經過Objective-C做爲中間層,因此做者在書中的例程都是基於Objective-C。程序員
1、Objective-C下開發OpenCV的基本流程github
熟悉Android下OpenCV開發的都知道,OpenCV提供了封裝好的JavaCameraView和NativeCameraView兩個類,鏈接攝像頭時須要經過類實例進行初始化設置(如設置畫面幀大小、幀率等信息),在iOS的開發中,OpenCV也提供了cap_ios.h,對攝像頭的初始化經過CvVideoCamera類來實現。和Android開發相似,CvVideoCamera也對攝像頭的初始化進行了封裝。算法
@interface ViewController : UIViewController<CvVideoCameraDelegate>{ CvVideoCamera *videoCamera; } @property (nonatomic, retain) CvVideoCamera* videoCamera; @property (weak, nonatomic) IBOutlet UIImageView *imgView;
如代碼所示:OC中須要在.h頭文件中定義CvVideoCamera,ViewController繼承UIViewController,能夠看出CvVideoCameraDelegate是一個協議。編程
@class CvVideoCamera; @protocol CvVideoCameraDelegate <NSObject> #ifdef __cplusplus // delegate method for processing image frames - (void)processImage:(cv::Mat&)image; #endif @end
經過cap_ios.h能夠看出,處理畫面幀時只須要重寫processImage方法。數組
- (void)viewDidLoad { [super viewDidLoad]; self.videoCamera = [[CvVideoCamera alloc] initWithParentView:imgView];self.videoCamera.delegate = self; self.videoCamera.defaultAVCaptureDevicePosition = AVCaptureDevicePositionFront; self.videoCamera.defaultAVCaptureSessionPreset = AVCaptureSessionPreset640x480; self.videoCamera.defaultAVCaptureVideoOrientation = AVCaptureVideoOrientationPortrait; } - (void)processImage:(cv::Mat&)image{ cv::cvtColor(image, image, CV_BGR2GRAY); }
在ViewController.m的viewDidLoad方法中對攝像頭進行初始化。如須要對畫面幀進行處理重寫processImage方法便可,有點相似Android中onCameraViewStarted的回調方法(上面代碼經過OpenCV中的cv::cvtColor方法,實現了攝像頭的灰度模式)。 session
2、Swift下開發OpenCV的基本流程多線程
因爲Swift不能直接調用C++,全部對C++的調用須要通過OC來包裹處理,因此Swift使用OpenCV須要和OC進行混編,關於Swift+OC+OpenCV的混編流程大概描述一下:併發
一、新建Swift項目;
二、在Swift項目中新建ObjectiveC文件,後綴爲.m的請修改成.mm,此時會提示是否生成bridgeXXX.h的頭文件,確認生成;
三、爲.mm文件新建一個頭文件,並在生成的bridgeXXX.h中import該頭文件;
四、至此在.mm文件中已經能夠調用OpenCV中的方法,若是你須要cpp或者hpp文件,請記住,它們不能和Swift直接交互,必須經過OC做爲橋樑。
因爲在Swift代碼中沒法直接使用CvVideoCamera,初始化攝像頭須要經過AVFoundation來實現。
import AVFoundation class ViewController: UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate {//粗體爲AVFoundation中的delegate @IBOutlet weak var imageView: UIImageView! var session: AVCaptureSession! var device: AVCaptureDevice! var output: AVCaptureVideoDataOutput! override func viewDidLoad() { super.viewDidLoad() // Prepare a video capturing session. self.session = AVCaptureSession() self.session.sessionPreset = AVCaptureSessionPreset640x480 …… }
……
如上代碼所示,在Swift初始化攝像頭的過程當中沒有用到OpenCV的任何東西。若是須要對畫面幀進行處理,須要實現AVCaptureVideoDataOutputSampleBufferDelegate的captureOutput方法,從命名上能夠看出這也是一個Delegate(協議),其中有一個captureOutput方法(代碼以下),能夠在方法內處理捕捉到的實時畫面幀。
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, from connection: AVCaptureConnection!) { // 將捕捉到的image buffer 轉換成 UIImage. guard let buffer: CVPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { print("could not get a pixel buffer") return } let capturedImage: UIImage do { CVPixelBufferLockBaseAddress(buffer, CVPixelBufferLockFlags.readOnly) defer { CVPixelBufferUnlockBaseAddress(buffer, CVPixelBufferLockFlags.readOnly) } let address = CVPixelBufferGetBaseAddressOfPlane(buffer, 0) let bytes = CVPixelBufferGetBytesPerRow(buffer) let width = CVPixelBufferGetWidth(buffer) let height = CVPixelBufferGetHeight(buffer) let color = CGColorSpaceCreateDeviceRGB() let bits = 8 let info = CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue guard let context = CGContext(data: address, width: width, height: height, bitsPerComponent: bits, bytesPerRow: bytes, space: color, bitmapInfo: info) else { print("could not create an CGContext") return } guard let image = context.makeImage() else { print("could not create an CGImage") return } capturedImage = UIImage(cgImage: image, scale: 1.0, orientation: UIImageOrientation.up)
//調用你寫在.mm文件中的OpenCV方法 } }
代碼中的captureOutput方法捕捉到了實時的畫面幀,經過提取buffer中的畫面幀並將其轉換爲UIImage,爲了實現和OC中一樣的灰度畫面,須要另外定義一個ViewProcress.mm和ViewProcress.h,經過ViewProcress.mm中的OpenCV方法來處理你捕捉到的UIImage,將其轉換爲灰度模式。
3、對比
從代碼來看,Swift是在曲線救國,爲了驗證,我寫了一個簡單的高斯背景建模方法處理實時畫面幀cv::BackgroundSubtractorMOG2,這個方法的實時開銷在OpenCV的經常使用方法中應該僅次於光流法,在兩段代碼中我都將幀率設置爲30FPS。經過實際運行來看,Swift平均幀率達到30FPS,而OC只有15FPS。主要緣由有兩點:
1. 併發編程,OC中沒用併發,而在Swift中使用了DispatchQueue,這得益於Swift3帶來的改變,處理畫面幀的時候須要併發,可是不能隨便併發,而是要在一個隊列中,因此就用到了DispatchQueue,這是Swift下幀率高的主要緣由,你可能會說,那我在OC下也用多線程就行了,OK,請看下一個點分析。
2. OC中的攝像頭是經過OpenCV封裝的協議來初始化的,高斯背景建模方法直接做用於畫面幀(processImage方法已經將實時畫面幀轉換爲Mat,能夠直接處理)。而Swift的是直接用AVFoundation初始化攝像頭,captureOutput方法獲取的是UIImage,須要在.mm文件中先將UIImage轉換爲Mat再調用高斯背景建模方法,處理完成再返回給UIView。關於幀率的初始化,OpenCV只提供了一個defaultFPS的設置方法,若是幀率超出範圍,反而會衰減的更厲害,而在Swift中由於沒法直接使用OpenCV提供的方法,因此初始化幀率必須經過AVFoundation的CMTimeMake函數來處理,設置一個最小幀率,當幀率沒法知足時系統會自動平衡。這是幀率穩定的主要緣由。
特別提示:注意內存管理!注意內存管理!注意內存管理!重要的事情說三遍。若是你用Swift來寫,那麼就會涉及Swift、Objective-C、C++。。。內存怎麼辦。。。有幾點建議:1、使用完的mat注意release掉;2、多用vector少用數組;3、非計算儘可能面向對象,專用計算儘可能靜態方法;4、若是能夠,除了須要調用的庫外,不要用C++,儘可能用Objective-C。若是你有更好的辦法,請指點。。。
如今回到開頭的結論,其實在OC中也可使用AVFoundation來初始化攝像頭,可是爲了偷懶,相信大多數人都會直接用OpenCV自帶的方法完成初始化。而OpenCV封裝類對Android或iOS的系統是以兼容爲主的,效率是其次的,因此若是在實際開發中,不論Android仍是iOS,都建議使用系統推薦的方式來調用camera,只有在處理畫面幀時才調用OpenCV中的方法。並且隨着Swift愈來愈完善,固然是推薦使用Swift+OC來進行OpenCV下的開發了。