-
swift 에서 C++ 사용하기 - opencv 연동iOS Swift 2020. 9. 9. 23:44
swift에서는 직접적으로 C++ 을 사용할 수 없다. C++을 사용하기 위해서는 Object-C 파일에서 C++ 를 사용하고, Object-C 소스 파일과 헤더파일을 생성한 후 Header 파일을 브릿지해서 swift에서 object-c 함수를 호출하도록 해야한다.
구조는 아래와 같다.
실제 C++ 코드는 OpenCVWapper.mm 에서 사용되고 해당 인터페이스의 정의 부분인 OpenCVWrapper.h 파일을 EdgeDector-Bridging-Header.h를 통해서 브릿지 하면 swfit 소스 파일에서 이를 호출해 사용할 수 있는 구조로 이루어 진다.
여기서는 opencv의 프레임워크를 연동해 이미지의 에지를 그리는 예제를 통해서 실제 어떻게 C++ 라이브러리를 swift를 통해서 사용할 수 있는지를 알아보자.
iOS를 위한 opencv2.framework는 cocopad을 통해서 설치하거나, 소스를 컴파일해서 사용할 수 있다.
소스를 컴파일하는 경우 소스 코드에서 아래와 같이 build_framwork.py 파일을 이용해 컴파일 할 수 있다.
python opencv/platforms/ios/build_framework.py ios
cocopad은 아래의 이름으로 PodFile에 아래 와 같이 추가해서 opencv2.framework를 프로젝트에 추가할 수 있다.
pod 'OpenCV2'
여기서는 pod을 사용하지 않고 컴파일된 opencv2.framework를 사용해 개발한다.
우선 프로젝트를 생성하고 프로젝트의 하위에 framework 그룹을 생성한 후 opencv2.framework 파일을 추가한다.
이제 OpenCV 라이브러리를 사용하기 위해서 OpenCVWrapper Object-C 파일을 아래와 같이 생성한다. 여기서 중요한 점은
Object-C bridging header 생성 여부를 묻는 창에서 이를 허용해야 한다. 또 생성된 OpenCVWrapper.m 파일의 확장을 .mm으로 변경해야 한다. 이는 .m 파일의 경우 컴파일러가 표준 C++ 헤더 파일을 추가시키지 않기 때문이다. C++을 사용할 경우에는 .mm을 C를 사용할 경우에는 .m 확장자를 사용해야 한다고 기억해 두면 된다.
이제 OpenCVWrapper 클래스에 클래스 함수 detectEdge:(UIImage*) image 함수를 추가해 보자 .
OpenCVWrapper.h
// // OpenCVWrapper.h // edgeDetector // // Created by HYEONJUN PARK on 2020/09/09. // #import <Foundation/Foundation.h> #import <UIKit/UIKit.h> NS_ASSUME_NONNULL_BEGIN @class UIImage; @interface OpenCVWrapper : NSObject +(UIImage*) detectEdge:(UIImage*) image; @end NS_ASSUME_NONNULL_END
OpenCVWrapper.mm
// // OpenCVWrapper.m // edgeDetector // // Created by HYEONJUN PARK on 2020/09/09. // #import <opencv2/opencv.hpp> #import <opencv2/imgcodecs/ios.h> #import "OpenCVWrapper.h" @implementation OpenCVWrapper +(UIImage*) detectEdge:(UIImage*) image { cv::Mat imageMat; UIImageToMat(image, imageMat); if(imageMat.channels() == 1) { return image; } cv::Mat grayMat; cv::cvtColor(imageMat, grayMat, cv::COLOR_BGR2GRAY); cv::Mat cannyMat; cv::Canny(grayMat, cannyMat, 50, 150, 3); return MatToUIImage(cannyMat); } @end
edgeDetector-Bridging-Header.h
// // Use this file to import your target's public headers that you would like to expose to Swift. // #include "OpenCVWrapper.h"
이제 CollectionView를 이용해 포토라이브러리에서 사진을 읽어와 해당 사진의 edgeDection을 할 수 있도록 아래와 같이 스트리보드를 디자인한다.
포토 선택화면은 UICollectionViewController를 생성해 구현하고 해당 선택 화면의 컨트롤러의 소스를 아래와 같이 작성한다.
// // GalleryCollectionViewController.swift // edgeDetector // // Created by HYEONJUN PARK on 2020/09/09. // import UIKit import Photos private let reuseIdentifier = "PhotoCell" protocol GalleryViewDelegate { func didSelectedImage(image : UIImage); } class GalleryViewController: UICollectionViewController { var selectedPhoto: UIImage! var delegate: GalleryViewDelegate? private var images = [PHAsset]() override func viewDidLoad() { super.viewDidLoad() fetchPhotos() } override func viewWillDisappear(_ animated: Bool) { } private func fetchPhotos() { //1. 포토라이브러리 인증 요청 PHPhotoLibrary.requestAuthorization { [weak self] status in if status == .authorized { let assets = PHAsset.fetchAssets(with: PHAssetMediaType.image, options: nil) assets.enumerateObjects {(asset, count, stop) in self?.images.append(asset) } self?.images.reverse() //이미지를 모두 가져오면 콜렉션뷰 리로드 DispatchQueue.main.async { self?.collectionView.reloadData() } } } } } extension GalleryViewController { // MARK: UICollectionViewDataSource override func numberOfSections(in collectionView: UICollectionView) -> Int { return 1 } override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return images.count } override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as? PhotoCollectionViewCell else { fatalError("PhotoCell Not Found") } let asset = self.images[indexPath.row] //PHAsset 을 UIImage 로 변환 let manager = PHImageManager.default() manager.requestImage(for: asset, targetSize: CGSize(width: 120, height: 120), contentMode: .aspectFit, options: nil) { (image, _) in DispatchQueue.main.async { cell.imageView.image = image } } return cell } // MARK: UICollectionViewDelegate override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { let asset = images[indexPath.row] PHImageManager.default().requestImage(for: asset, targetSize: CGSize(width: 300, height: 300), contentMode: .aspectFit, options: nil) { [weak self](image, info) in guard let info = info else { return } let isDegradedImage = info["PHImageResultIsDegradedKey"] as! Bool if !isDegradedImage { if let image = image { self?.delegate?.didSelectedImage(image: image) self?.dismiss(animated: true, completion: nil) } } } } } //Mark - UICollectionViewDelegateFlowLayout 프로토콜 구현 //CollectionViewCell의 크기 설정 extension GalleryViewController: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let cellRatio: CGFloat = 1.0 if (UIDevice.current.orientation.isLandscape) { return getCellSize(numberOFItemsRowAt: 5, cellRatio: cellRatio) //return CGSize(width: 120, height: 120) } else { return getCellSize(numberOFItemsRowAt: 3, cellRatio: cellRatio) //return CGSize(width: 120, height: 120) } } private func getCellSize(numberOFItemsRowAt: Int, cellRatio: CGFloat) -> CGSize { var screenWidth = UIScreen.main.bounds.width if #available(iOS 11.0, *) { let window = UIApplication.shared.windows.first{ $0.isKeyWindow } let leftPadding = window?.safeAreaInsets.left ?? 0 let rightPadding = window?.safeAreaInsets.right ?? 0 screenWidth -= (leftPadding + rightPadding) } screenWidth -= 10 let cellWidth = screenWidth / CGFloat(numberOFItemsRowAt) let cellHeight = cellWidth * cellRatio return CGSize(width: cellWidth, height: cellHeight) } }
위에서 fetchPhotos()는 PHPhotoLibrary 에 접근하기 위해 info.plist 에 "Privacy - Photo Library Usage Description"을 추가해 포토라이브러리 사용 허가 요청을 할 수 있게 해야한다.
UICollectionViewDelegateFlowLayout을 추가해 해당 Collection Cell의 크기를 조절하고, collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) 에서는 PHAsset을 images 배열에 저장하고
선택 시 collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)가 호출되는 데 이때 PHAsset을 PHImageMager를 통해 UIImage로 변환한다.
선택된 이미지는 self?.delegate?.didSelectedImage(image: image)를 통해 ViewController로 전달된다.
// // ViewController.swift // edgeDetector // // Created by HYEONJUN PARK on 2020/09/09. // import UIKit class ViewController: UIViewController { @IBOutlet weak var applyButton: UIButton! @IBOutlet weak var photoView: UIImageView! override func viewDidLoad() { super.viewDidLoad() //applyButton.isHidden = true } private func updateUI(with image: UIImage) { photoView.image = image //applyButton.isHidden = false } } //MARK: sugue 관련 연산 extension ViewController { @IBAction func goToGallery(_ sender: UIButton) { performSegue(withIdentifier: "goToGalleryView", sender: self) } override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "goToGalleryView" { guard let galleryVC = segue.destination as? GalleryViewController else { fatalError("GalleryViewControlller error") } galleryVC.delegate = self } } @IBAction func apply() { if let image = photoView.image { let edgeImage = OpenCVWrapper.detectEdge(image) photoView.image = edgeImage } } } extension ViewController: GalleryViewDelegate { func didSelectedImage(image : UIImage) { photoView.image = image; } }
ViewController의 apply() 함수는 Object-C 클래스 OpenCVWrapper.detectEdge() 함수를 호출하게 되는 데 이는 내부적으로 opencv 라이브러리의 함수를 호출해 선택된 이미지에 대한 edgeImage를 반환해주고 이를 photoView.image에 출력한다.
아래 소스에는 opencv2.framework 의 크기 상 이를 삭제한 소스이다. 테스트를 위해서는 opencv2.framework를 cocopod 을 이용해 추가하거나 소스를 다운받아서 컴파일 해서 사용해야 한다.
'iOS Swift' 카테고리의 다른 글
BLE 프로토콜 구조 (0) 2023.06.07 Adding Support for Background Tag Reading (0) 2023.06.06 NDEF 의 TNF(Type Name Format) (1) 2023.06.06 NDEF(NFC Data Exchage Format) 메시지 구조 (3) 2023.06.06 Playground를 이용한 UI 프로토타입 설계 (0) 2020.09.09