iOS側のOpenCVの統合とMatとUIImageの相互変換(ソースコード付き)

OpenCV は、Linux、Windows、Android、Mac OS オペレーティング システム上で実行できる非常に強力なグラフィックス処理フレームワークであり、自動運転、スマート ホーム、顔認識、画像処理などの分野で非常に豊富で強力な API を提供します。基本的に画像処理のあらゆるニーズに対応できます。最近のプロジェクトでは、画像処理フレームワークとして opencv を使用する必要があり、プロジェクトの画像処理要件は、最も一般的に使用される 8 ビット色深度画像ではなく、16 ビット色深度画像であるため、開発プロセス中に多くの落とし穴を踏んでしまいました。 、そして同時に、opencv の使用についての理解が深まりました。ここにレビューを記録し、OpenCV を研究している小規模パートナーにいくつかのアイデアを提供したいと考えています。

この記事ではOpenCVの統合とMatとUIImageの相互変換について簡単に説明し、次の記事ではOpenCVを利用したMeitu Xiuxiuと同様の各種処理機能について詳しく記録していきます。

1. 統合された OpenCV

OpenCVを統合するには2つの方法があります

1.統合には Cocoapod を使用し、それを Podfile で使用します

pod 'OpenCV', '~> 4.7.0'

opencv の 4.7.0 バージョンを統合できます

2. 手動統合

iOS 側で使用するフレームワークをダウンロードするには、Opencv 公式 Web サイトにアクセスする必要があります。ダウンロードアドレスは
https://opencv.org/releases/
ここに画像の説明を挿入
で、iOS 側でダウンロードするパッケージを選択し、ダウンロードしたものをインポートするだけです。通常使用するために、フォルダーをプロジェクトに追加します
ここに画像の説明を挿入

2つのMatとUIImageの相互変換

Mat は OpenCV で提供される重要なクラスです. Mat には画像のピクセルの幅と高さ, チャンネル数など画像に関する多くの情報が含まれています. 基本的に iOS 側の opencv フレームワークを使用した画像の処理はMat オブジェクトに変換する必要があります。通常どおり続行できます。

注: 変換方法では C++ コードを使用するため、コード記述ファイルとそのファイルが使用される場所で、これらのファイルを C++ 形式でコンパイルするようにコンパイラーに指示するために、 .m を .mm に変更する必要があります。変更しないとエラーが発生します。報告される。

友人が理解しやすいように、コード内に明確なコメントが特別にマークされています。整理したいなら必ず読んでください、直接コピペするだけです、相互変換方式は8ビット、16ビットのRGB、RGBA画像に対応しており楽しくお使いいただけます。16bpp の画像などの他の特殊な形式の場合は、ひょうたん絵に従って個別に処理してください。考え方と方法は同じです。

1. UIImageをマットに変換

+(cv::Mat)cvMatFromUIImage:(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_16UC4;
    //获取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(bitsPerComponent == 8){
        if(channels == 3){
            bitmapInfo = kCGImageAlphaNone | kCGImageByteOrderDefault;
        }else  if(channels == 4){
            bitmapInfo = kCGImageAlphaPremultipliedLast | kCGImageByteOrderDefault;
        }else{
            printf("图片格式不支持");
            abort();
        }
    }else if(bitsPerComponent == 16){
        if(channels == 3){  //虽然是三通道,但是iOS端的CGBitmapContextCreate方法不支持16位3通道的创建,所以仍然作为4通道处理
            bitmapInfo = kCGImageAlphaPremultipliedLast | kCGImageByteOrder16Little;
        }else  if(channels == 4){
            bitmapInfo = kCGImageAlphaPremultipliedLast | kCGImageByteOrder16Little;
        }else{
            printf("图片格式不支持");
            abort();
        }
    }else{
        printf("图片格式不支持");
        abort();
    }


    //使用获取到的宽高创建mat对象CV_16UC4 为传入的矩阵类型
    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.マットからイメージへ

+(UIImage *)UIImageFromCVMat:(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(bitsPerComponent == 8){
        if(channels == 3){
            bitmapInfo = kCGImageAlphaNone | kCGImageByteOrderDefault;
        }else if(channels == 4){
            bitmapInfo = kCGImageAlphaPremultipliedLast | kCGImageByteOrderDefault;
        }else{
            printf("图片格式不支持");
            abort();
        }
    }else if(bitsPerComponent == 16){
        if(channels == 3){
            bitmapInfo = kCGImageAlphaNone | kCGImageByteOrder16Little;
        }else if(channels == 4){
            bitmapInfo = kCGImageAlphaPremultipliedLast | kCGImageByteOrder16Little;
        }else{
            printf("图片格式不支持");
            abort();
        }
    }else{
        printf("图片格式不支持");
        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]);
}

よくある3つの質問

1. サポートされていないパラメータの組み合わせを求めるプロンプト

quarzt 2D フレームワークには画像処理に関する厳しい規定があるため、Bitmapinfo のアルファ チャネルと読み取り順序の組み合わせには明確なルールがあり、エラーは次のとおりです。
ここに画像の説明を挿入

解決策
1 つ目は、公式 Web サイトからクォーツで許可されている組み合わせを確認することです。
画像の説明を追加してください

2 番目の方法は、プロンプトに従って環境変数を設定し、サポートされている組み合わせをログ ウィンドウに出力する方法です。設定方法は次のとおりです: "CGBITMAP_CONTEXT_LOG_ERRORS" を追加してビットマップ環境エラー ログ情報を出力し、ログ ウィンドウを実行し
ここに画像の説明を挿入
ここに画像の説明を挿入
てご覧のとおり、
ここに画像の説明を挿入
8Bit の場合、16Bit チャネルのビット深度を持つ画像の場合、Quartz はアルファ チャネルを持つ画像のみをサポートし、チャネルの読み取り方法も明確に規定されており、独自の構成に従って対応する構成を採用するだけで十分です。画像フォーマット。
Quartz フレームワークはアルファ チャネルを使用したデータの処理のみをサポートしているため、3 チャネルの画像は 4 チャネルの画像と同じ方法で処理されます。変換するとき、アルファにはデフォルトで最大値が割り当てられ、正規化された値は 1.0 です。アルファ チャネルが画像に追加される場合でも、画像の表示には影響しません。

ここが非常に厄介で、16ビット画像の場合、画像にアルファチャンネルが含まれており、アルファチャンネルの位置が最後であることが分かっていても、kCGImageAlphaLastの画像チャンネル情報は利用できず、 kCGImageAlphaPremultipliedLast の列挙を使用して制約しますが、8 ビット画像の場合はそのような制限はなく、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>

使用される名前空間には追加の宣言も必要です。

アイデア交換へようこそ、お待ちしています。

おすすめ

転載: blog.csdn.net/mumubumaopao/article/details/130774835