OpenCV在iOS端的集成及Mat和UIImage互相转化(附源码)

news/2024/7/20 23:11:59 标签: opencv, ios, 计算机视觉

OpenCV是一个非常强大的图形处理框架,可以运行在Linux、Windows、Android和Mac OS操作系统上,在自动驾驶、智能家居、人脸识别、图片处理等方面提供了非常丰富且功能强大的api,在图片处理方便,基本上可以满足对图片处理的所有需求。近期项目中有使用opencv作为图片处理框架的需求,而且项目对图片处理的需求并不是最常用的8bit色深图片,而是16bit色深,所以在开发的过程中踩了很多坑,同时也对opencv的使用有了更深的理解,特此记录回顾,也希望能给正在研究OpenCV的小伙伴提供一点思路。

本文简单讲解OpenCV的集成及Mat和UIImage互相转化,下一篇文章会详细记录使用OpenCV对图片进行类似于美图秀秀的各种处理功能。

一 集成OpenCV

OpenCV的集成有两种方式

1.使用cocoapods进行集成,在Podfile文件中使用

pod 'OpenCV', '~> 4.7.0'

即可集成opencv的4.7.0版本

2.手动集成

需要去Opencv官网下载iOS端使用的框架,下载地址
https://opencv.org/releases/
在这里插入图片描述
选择iOS端的包下载就好,然后将下载下来的文件夹整个导入项目中
在这里插入图片描述
即可正常使用

二 Mat和UIImage的互相转化

Mat是OpenCV中提供的一个重要的类,Mat中包含了图片的很多信息,比如图片的像素宽高、通道数量,iOS端使用opencv框架对图片的处理基本上也都需要转化为Mat对象之后才可以正常进行。

注意:转化方法使用c++代码,所以在代码的编写文件以及使用该文件的地方,都需要将.m改为.mm,以告诉编译器以c++的形式来编译这些文件,否则会报错。

代码里特意标明了清晰的注释,帮助小伙伴理解。想深入梳理的务必阅读,伸手党直接复制粘贴就好,互相转化的方法对8位及16位的RGB及RGBA图片都做了兼容,可以愉快使用。其他特殊格式如16bpp的图片,请照葫芦画瓢,单独处理,思路和方法是一样的。

1.UIImage转Mat

+(cv::Mat)matFromImage:(UIImage *)image
{
    //获取图片的CGImageRef结构体
    CGImageRef imageRef = CGImageCreateCopy([image CGImage]);
    //获取图片尺寸
    CGSize size = CGSizeMake(CGImageGetWidth(imageRef), CGImageGetHeight(imageRef));
    //获取图片宽度
    CGFloat cols = size.width;
    //获取图高度
    CGFloat rows = size.height;
    //获取图片颜色空间,创建图片对应Mat对象,需要使用同样的颜色空间
    CGColorSpaceRef colorSpace = CGImageGetColorSpace(image.CGImage);
    
    //判断图片的通道位深及通道数 默认使用8位4通道格式
    int type = CV_8UC4;
    //获取bitmpa位数
    size_t bitsPerPixel = CGImageGetBitsPerPixel(imageRef);
    //获取通道位深
    size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
    //获取通道数
    size_t channels = bitsPerPixel/bitsPerComponent;
    if(channels == 3 || channels == 4){  // 因为quartz框架只支持处理带有alpha通道的数据,所以3通道的图片采取跟4通道的图片一样的处理方式,转化的时候alpha默认会赋最大值,归一化的数值位1.0,这样即使给图片增加了alpha通道,也并不会影响图片的展示
        if(bitsPerComponent == 8){
            //8位3通道 因为iOS端只支持
            type = CV_8UC4;
        }else if(bitsPerComponent == 16){
            //16位3通道
            type = CV_16UC4;
        }else{
            printf("图片格式不支持");
            abort();
        }
    }else{
        printf("图片格式不支持");
        abort();
    }
    
    //创建位图信息  根据通道位深及通道数判断使用的位图信息
    CGBitmapInfo bitmapInfo;
    if(channels == 3 || channels == 4){
        if(bitsPerComponent == 8){
            bitmapInfo = kCGImageAlphaPremultipliedLast | kCGImageByteOrderDefault;
        }else if(bitsPerComponent == 16){
            bitmapInfo = kCGImageAlphaPremultipliedLast | kCGImageByteOrder16Little;
        }else{
            printf("图片格式不支持");
            abort();
        }
    }else{
        printf("图片格式不支持");
        abort();
    }

    //使用获取到的宽高创建mat对象CV_8UC4 为传入的矩阵类型
    cv::Mat cvMat(rows, cols, type); // 每通道8bit 共有4通道(RGB + Alpha通道 RGBA格式)
    CGContextRef contextRef = CGBitmapContextCreate(cvMat.data,                 // 数据源
                                                    cols,                       // 每行像素数
                                                    rows,                       // 列数(高度)
                                                    bitsPerComponent,                          // 每个通道bit数
                                                    cvMat.step[0],              // 每行字节数
                                                    colorSpace,                 // 颜色空间
                                                    bitmapInfo); // 位图信息(alpha通道信息,字节读取信息)
    //将图片绘制到上下文中mat对象中
    CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows), image.CGImage);
    //释放imageRef对象
    CGImageRelease(imageRef);
    //释放颜色空间
    CGColorSpaceRelease(colorSpace);
    //释放上下文环境
    CGContextRelease(contextRef);
    return cvMat;
}

2.Mat转Image

+(UIImage *)imageFromMat:(cv::Mat)cvMat
{
    //获取矩阵数据
    NSData *data = [NSData dataWithBytes:cvMat.data length:cvMat.elemSize()*cvMat.total()];
    //判断矩阵使用的颜色空间
    CGColorSpaceRef colorSpace;
    if (cvMat.elemSize() == 1) {
        colorSpace = CGColorSpaceCreateDeviceGray();
    } else {
        colorSpace = CGColorSpaceCreateDeviceRGB();
    }
    //创建数据privder
    CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
    
    //获取bitmpa位数
    size_t bitsPerPixel = cvMat.elemSize()*8;
    //获取通道数
    size_t channels = cvMat.channels();
    //获取通道位深
    size_t bitsPerComponent = bitsPerPixel/channels;
    
    //创建位图信息  根据通道位深及通道数判断使用的位图信息
    CGBitmapInfo bitmapInfo;
    if(channels == 4 || channels == 3){
        if(bitsPerComponent == 8){
            bitmapInfo = kCGImageAlphaPremultipliedLast | kCGImageByteOrderDefault;
        }else if(bitsPerComponent == 16){
            bitmapInfo = kCGImageAlphaPremultipliedLast | kCGImageByteOrder16Little;
        }else{
            printf("Mat格式不支持");
            abort();
        }
    }else{
        printf("Mat格式不支持");
        abort();
    }
    //根据矩阵及相关信息创建CGImageRef结构体
    CGImageRef imageRef = CGImageCreate(cvMat.cols, //矩阵宽度
                                        cvMat.rows, //矩阵列数
                                        bitsPerComponent,        //通道位深
                                        8 * cvMat.elemSize(),  //每个像素位深
                                        cvMat.step[0],  //每行占用字节数
                                        colorSpace,    //使用的颜色空间
                                        bitmapInfo,//通道排序、大小端读取顺序信息
                                        provider, //数据源
                                        NULL,   //解码数组 一般传null
                                        true, //是否抗锯齿
                                        kCGRenderingIntentDefault   //使用默认的渲染方式
                                        );
    // 通过cgImage转化出来UIImage对象
    UIImage *finalImage = [UIImage imageWithCGImage:imageRef];
    //释放imageRef
    CGImageRelease(imageRef);
    //释放provider
    CGDataProviderRelease(provider);
    //释放颜色空间
    CGColorSpaceRelease(colorSpace);
    return finalImage;
}

3.一个小工具,使用Mat打印图片详细信息,方便核对数据

//获取图片信息
+(void)readInfoWithImage:(UIImage*)inputImage{
    Mat inputMat = [CVTools matFromImage:inputImage];
    printf("图片宽度 = %d \n",inputMat.cols);
    printf("图片高度 = %d \n",inputMat.rows);
    printf("通道位深 = %zu \n",inputMat.elemSize()*8/inputMat.channels());
    printf("通道数 %d \n",inputMat.channels());
    printf("每个像素bit数 = %zu \n",inputMat.elemSize()*8);

    printf("每行元素的字节数 = %zu \n",inputMat.step[0]);
}

三 常见问题

1.提示不支持的参数组合

因为quarzt 2D框架对于图片的处理有着严格的规定,所以对于Bitmapinfo内的alpha通道和读取顺序组合有着明确的规则,报错如下
在这里插入图片描述

解决方法
第一种方式是通过官网查阅quartz允许的组合搭配,官网截图如下:
请添加图片描述

第二种方法是根据提示去设置环境变量在Log窗口打印支持的组合搭配,设置方式如下
在这里插入图片描述
在这里插入图片描述
增加“CGBITMAP_CONTEXT_LOG_ERRORS”位图环境错误log信息的打印,然后再运行Log窗口输出如下:
在这里插入图片描述
可以看到,对于8Bit和16Bit通道位深的图片,quartz只支持带有alpha通道的,通道的读取方式也有明确规定,根据自己的图片格式采取相应的配置就可以了。
因为quartz框架只支持处理带有alpha通道的数据,所以3通道的图片采取跟4通道的图片一样的处理方式,转化的时候alpha默认会赋最大值,归一化的数值位1.0,这样即使给图片增加了alpha通道,也并不会影响图片的展示

这个地方很坑,以16位图片来说,即使明知图片是含有alpha通道的,而且alpha通道的位置在最后,也并不能使用kCGImageAlphaLast的图片通道信息,而是要使用kCGImageAlphaPremultipliedLast的枚举来约束,但是如果是8位的图片却并没有这个限制,而且字节读取顺序需要额外注明使用16位小端读取kCGImageByteOrder16Little,做16位图片处理的小伙伴一定要注意,深坑啊。

2.在导入头文件的时候,一定要将oencv用到的头文件放在所有OC的文件引用之前引用,否则会出现函数重定义冲突

以该测试工程里的文件为例,头文件引用方式为:

#import <opencv2/opencv.hpp>
#import <opencv2/imgcodecs/ios.h>
#include <math.h>
#include <iostream>
using namespace cv;
using namespace std;

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

使用的命名空间也需要额外声明。

有想法欢迎交流,等你。


http://www.niftyadmin.cn/n/343901.html

相关文章

怎么通过ssh连上ipv6的服务器?阿里云怎么配置ipv6?wsl2怎么支持ipv6?

最近在研究ipv6&#xff0c;光调通环境居然让我折腾了好多回&#xff0c;现在终于通了 在这里提一句&#xff0c;IPV6和IPV4是两种东西&#xff0c;不要想着ipv6兼容ipv4&#xff0c;你就当它是全新的东西 1.前置条件 1.1我的电脑能访问ipv6 测试通过就代表你电脑可以访问ip…

lwIP更新记01:全局互斥锁替代消息机制

从 lwIP-2.0.0 开始&#xff0c;在 opt.h 中多了一个宏开关 LWIP_TCPIP_CORE_LOCKING&#xff0c;默认使能。这个宏用于启用 内核锁定 功能&#xff0c;使用 全局互斥锁 实现。在之前&#xff0c;lwIP 使用 消息机制 解决 lwIP 内核线程安全问题。消息机制易于实现&#xff0c;…

一、MongoDB简介

文章目录 一、MongoDB简介1、NoSQL简介2、什么是MongoDB ?3、MongoDB 特点4、安装mongodb5、MongoDB 概念解析5.1 数据库5.2 文档5.3 集合5.4 MongoDB 数据类型 6、适用场景 一、MongoDB简介 1、NoSQL简介 NoSQL(NoSQL Not Only SQL)&#xff0c;意即反SQL运动&#xff0c;…

MySQL高级篇——索引失效的11种情况

导航&#xff1a; 【黑马Java笔记踩坑汇总】Java基础进阶JavaWebSSMSpringBoot瑞吉外卖SpringCloud黑马旅游谷粒商城学成在线设计模式牛客面试题 目录 1. 索引优化思路 2. 索引失效的11种情况 2.0. 数据准备 2.1 要尽量满足全值匹配 2.2 要满足最佳左前缀法则 2.3 主键插…

C语言的位运算

1. 位操作符综述 位操作有逻辑运算和移位运算&#xff0c;如位与、位或、位取反、按位异或、移位等操作。位运算通常会和底层代码寄存器的操作结合在一起使用&#xff0c;比如想要让寄存器中的任意1位或者任意几位位设置为1&#xff0c;或者设置为0&#xff0c;从而实现对寄存…

在 Python 中交换列表的元素

列表是 Python 中的可变&#xff08;可变&#xff09;数据结构&#xff0c;用于存储有序的项目集合。 在本文中&#xff0c;我们将了解几种交换列表元素的不同方法。 在 Python 中使用赋值运算符交换列表的元素 交换元素列表的最简单和最常用的方法之一是通过赋值运算符和逗号…

HTTP协议【面试高频考点】

目录 一、HTTP 响应 1.首行 2.状态码&#xff08;经典面试题&#xff0c;必考&#xff09; 2.1 200 OK 2.2 404 Not Found 2.3 403 Forbidden 2.4 500 Internal Server Error 2.5 504 Gateway Timeout 2.6 302 Move temporarily 2.7 301 Moved Permanently 2.8 状态…

如何快速搭建springboot项目

在IntelliJ IDEA中&#xff0c;可以按照以下步骤快速创建一个Spring Boot项目&#xff1a; 1. 打开 IntelliJ IDEA&#xff0c;点击欢迎界面上的"Create New Project"或者从菜单栏选择"File" -> "New" -> "Project"。 2. 在创…