在 Xcode 中将 OpenCV 集成到你的 Swift iOS 项目中并使用 UIImages

图片

假设在你的 iOS 应用程序中,你想对图像执行抓取或使用特定插值调整图像大小,作为机器学习模型的预处理步骤。你无法在 Xcode 上使用 Apple 的原生框架(例如 Core Image)轻易地做到这一点。如果你想坚持使用 Apple 的工具,你可能不得不使用Metal,并且仍然需要从头开始编写代码。

或者,你可以将 OpenCV 与你的 Xcode swift 项目集成,并使用 100 多种现成的优化图像处理算法来完成你的工作。尽管集成部分并不完全直接,但它执行起来非常快,因为它使用 C++。(这只会使你的应用程序在 iOS 设备上的大小增加大约 2MB。)

获取OpenCV iOS框架

首先,从其发布(https://opencv.org/releases/)页面下载最新的 OpenCV 版本 3 或 4 iOS 包。解压缩 zip 文件并获取opencv2.framework文件。

(本指南将基于 Xcode 12.3 和 OpenCV3。OpenCV4 的 API 会略有不同。)

将 OpenCV 框架添加到你的项目中

将 opencv2.framework 文件拖放到 Xcode 项目左侧的项目导航器窗格中。然后,Xcode 将提示一个窗口,其中包含用于添加框架的选项。

图片

对于Destination勾选Copy items if needed

对于Added folders选择Create groups选项

对于Add to targets勾选你的目标应用程序。

在左侧导航器窗格中单击你的项目,然后转到Build Phases -> Link Binary With Libraries。你应该能够在那里看到opencv2.framework。(注意,将框架复制到项目中可能需要几秒钟)

图片

(可选)添加与 OpenCV 配合使用的其他框架

根据你的项目使用的内容,你可能还必须链接以下一些框架,以便 OpenCV 正常工作。

  • AssetsLibrary.framework
  • CoreGraphics.framework
  • CoreMedia.framework
  • CoreFoundation.framework
  • Accelerate.framework

Build Phases -> Link Binary With Libraries下,点击+号,搜索上面的frameworks并添加。

设置框架搜索路径

在左侧导航器窗格中单击你的项目,然后转到Build Settings -> Framework Search Paths。确保在那里说明了opencv2.framework的正确目录路径。对于项目的根目录,你可以使用**$(PROJECT_DIR)/。**

创建包装器类文件

转到File -> New -> File。创建一个名为OpenCVWrapper的Cocoa Touch 类,并为Language选择Objective-C。将其保存在你的项目目录中,然后单击“ Create Bridging Header ”。

图片

你将在左侧项目导航器窗格中看到 3 个新文件。

  • OpenCVWrapper.h
  • OpenCVWrapper.m
  • {Project Name}-Bridging-Header.h

现在,通过将“OpenCVWrapper.m”重命名为“OpenCVWrapper.mm”,将“OpenCVWrapper.m”的文件扩展名从“ .m ”更改为“ .mm ”。然后,你可以看到 Xcode 将文件识别为Objective-C++文件,位于右侧窗格的File Inspector -> Identity and Type。(你可以按Option+Command+1打开文件检查器)

图片

编辑桥接头文件

打开**{Project Name}-Bridging-Header.h**并添加以下行。

#import "OpenCVWrapper.h"

添加前缀头文件

转到File -> New -> File并筛选“ PCH ”。创建并保存在项目目录中。

图片

将以下行添加到PrefixHeader.pch文件的最后一行#endif之前。

#ifdef __cplusplus
#include <opencv2/opencv.hpp>
#endif

现在,该文件应包含以下行。

#ifndef PrefixHeader_pch
#define PrefixHeader_pch

#ifdef __cplusplus
#include <opencv2/opencv.hpp>
#endif

#endif

单击左侧导航窗格中的项目,然后转到Build Settings -> Prefix Header。在那里为PrefixHeader.pch添加正确的文件路径。如果该文件位于项目的“ .xcodeproj ”文件所在的目录中,则可以使用$(SRCROOT)/PrefixHeader.pch.

编辑 OpenCVWrapper.h

我们将创建 3 个类方法。

  • getOpenCVVersion — 以字符串形式提供 OpenCV 版本。(我们可以用它来快速检查 OpenCV 是否集成成功。)
  • grayscaleImg — 采用 UIImage 并将其灰度输出为 UIImage。(这将给出如何使用 UIImages 的想法。)
  • resizeImg — 获取一个 UIImage 并输出其调整大小的UIImage。(这会让你了解如何使用带有 alpha 通道的 UIImages 。)

因此,OpenCVWrapper.h应如下所示。

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface OpenCVWrapper : NSObject
+ (NSString *)getOpenCVVersion;
+ (UIImage *)grayscaleImg:(UIImage *)image;
+ (UIImage *)resizeImg:(UIImage *)image :(int)width :(int)height :(int)interpolation;
@end

NS_ASSUME_NONNULL_END

编辑 OpenCVWrapper.mm

OpenCVWrapper.mm应如下所示。

#import <opencv2/opencv.hpp>
#import <opencv2/imgcodecs/ios.h>
#import "OpenCVWrapper.h"

/*
 * add a method convertToMat to UIImage class
 */

@interface UIImage (OpenCVWrapper)
- (void)convertToMat: (cv::Mat *)pMat: (bool)alphaExists;
@end

@implementation UIImage (OpenCVWrapper)

- (void)convertToMat: (cv::Mat *)pMat: (bool)alphaExists {
    if (self.imageOrientation == UIImageOrientationRight) {
        /*
         * When taking picture in portrait orientation,
         * convert UIImage to OpenCV Matrix in landscape right-side-up orientation,
         * and then rotate OpenCV Matrix to portrait orientation
         */

        UIImageToMat([UIImage imageWithCGImage:self.CGImage scale:1.0 orientation:UIImageOrientationUp], *pMat, alphaExists);
        cv::rotate(*pMat, *pMat, cv::ROTATE_90_CLOCKWISE);
    } else if (self.imageOrientation == UIImageOrientationLeft) {
        /*
         * When taking picture in portrait upside-down orientation,
         * convert UIImage to OpenCV Matrix in landscape right-side-up orientation,
         * and then rotate OpenCV Matrix to portrait upside-down orientation
         */

        UIImageToMat([UIImage imageWithCGImage:self.CGImage scale:1.0 orientation:UIImageOrientationUp], *pMat, alphaExists);
        cv::rotate(*pMat, *pMat, cv::ROTATE_90_COUNTERCLOCKWISE);
    } else {
        /*
         * When taking picture in landscape orientation,
         * convert UIImage to OpenCV Matrix directly,
         * and then ONLY rotate OpenCV Matrix for landscape left-side-up orientation
         */

        UIImageToMat(self, *pMat, alphaExists);
        if (self.imageOrientation == UIImageOrientationDown) {
            cv::rotate(*pMat, *pMat, cv::ROTATE_180);
        }
    }
}
@end

@implementation OpenCVWrapper

+ (NSString *)getOpenCVVersion {
    return [NSString stringWithFormat:@"OpenCV Version %s",  CV_VERSION];
}

+ (UIImage *)grayscaleImg:(UIImage *)image {
    cv::Mat mat;
    [image convertToMat: &mat :false];
    
    cv::Mat gray;
    
    NSLog(@"channels = %d", mat.channels());

    if (mat.channels() > 1) {
        cv::cvtColor(mat, gray, CV_RGB2GRAY);
    } else {
        mat.copyTo(gray);
    }

    UIImage *grayImg = MatToUIImage(gray);
    return grayImg;
}

+ (UIImage *)resizeImg:(UIImage *)image :(int)width :(int)height :(int)interpolation {
    cv::Mat mat;
    [image convertToMat: &mat :false];
    
    if (mat.channels() == 4) {
        [image convertToMat: &mat :true];
    }
    
    NSLog(@"source shape = (%d, %d)", mat.cols, mat.rows);
    
    cv::Mat resized;
    
//    cv::INTER_NEAREST = 0,
//    cv::INTER_LINEAR = 1,
//    cv::INTER_CUBIC = 2,
//    cv::INTER_AREA = 3,
//    cv::INTER_LANCZOS4 = 4,
//    cv::INTER_LINEAR_EXACT = 5,
//    cv::INTER_NEAREST_EXACT = 6,
//    cv::INTER_MAX = 7,
//    cv::WARP_FILL_OUTLIERS = 8,
//    cv::WARP_INVERSE_MAP = 16
    
    cv::Size size = {width, height};
    
    cv::resize(mat, resized, size, 0, 0, interpolation);
    
    NSLog(@"dst shape = (%d, %d)", resized.cols, resized.rows);
    
    UIImage *resizedImg = MatToUIImage(resized);
    
    return resizedImg;

}

@end

“ convertToMat”方法是(https://github.com/tobyliu-sw/OpenCVSample/blob/28ae9c733c4a7545eb126c9b90dca60e22c13e40/OpenCVSample/OpenCVSample.mm#L27)的修改版本,用于将UIImage转换为cv::Mat

或者你可以按照这个方法(https://docs.opencv.org/3.4.16/d3/def/tutorial_image_manipulation.html),虽然我没有亲自尝试过。

注意:

  • NSLog可用于将字符串打印到控制台。
  • 在 OpenCV4 中,方法grayscaleImg中的CV_RGB2GRAY应该是cv::COLOR_RGB2GRAY。
  • 在灰度图像中,没有 alpha 通道。因此,在grayscaleImg方法中调用convertToMat时,它被忽略,alphaExists 被设置为 false。
  • 调整大小时,alpha 通道也必须调整大小。因此,如果图像有 4 个通道,则在resizeImg方法中调用convertToMat 时,将考虑 alpha 通道并将 alphaExists 设置为 true。
  • 检查resizeImg方法下的注释以找到每个插值的输入整数代码。(例如 — 最近邻 = 0;双线性 = 1;双三次 = 2 等等。)

检查集成

你可以调用“getOpenCVVersion”,如下所示,在“ViewController.swift”中的super.viewDidLoad()行之后的viewDidLoad()中,如果集成成功,它会将OpenCV版本打印到控制台。

print("(OpenCVWrapper.getOpenCVVersion())")

使用 UIImages

假设你有一个名为“ sample_img.png ”的 1280×720 RGBA 图像作为应用程序中的资产,你将能够获得它的灰度UIImage使用swift如下(例如,在第 11 节中的ViewController.swift)。

let rgbaIn = UIImage(named: "sample_img")!
let grayOut = OpenCVWrapper.grayscaleImg(rgbaIn)

同样,你可以使用双线性插值获得其调整为 640×360 大小的 RGBA UIImage ,如下所示。

let rgbaIn = UIImage(named: "sample_img")!
let rgba256 = OpenCVWrapper.resizeImg(rgbaIn, 640, 360, 1)

(如果出现任何错误,请使用顶部的“Product”菜单再次尝试清洁和构建。)

现在你已经了解了如何使用 OpenCV 处理 UIImage。你可以参考 OpenCV C++ 文档来浏览该库。

OpenCV C++ 文档:https://docs.opencv.org/3.4.16/d3/d63/classcv_1_1Mat.html

作者:磐怼怼 | 来源:公众号——深度学习与计算机视觉

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论