Android OpenCV基础之core模块

一、概述

在真实世界中,我们(人类)看到的是图像,而让数字设备来“看“的时候,则是在记录图像中的每一个点的数值。

如上面的图像,数字设备看到的是一个矩阵,该矩阵包含了所有像素点的值。最终在计算机世界里所有图像都可以简化为数值矩以及矩阵信息。
 OpenCV的core模块定义了如何在内存中存储图像,还包括矩阵、向量、点等一些基础操作的定义。

二、基本图像容器

OpenCV定义了Mat类作为基本图像容器,此外Mat还可以只单纯地表示一个矩阵。Mat由两个数据部分组成:矩阵头(包含矩阵尺寸,存储方法,存储地址等信息)和一个指向存储所有像素值的矩阵(根据所选存储方法的不同矩阵可以是不同的维数)的指针。矩阵头的尺寸是常数值,但矩阵本身的尺寸会依图像的不同而不同。例如,一个RGB的图片,其Mat对象的矩阵就是一个分别存储R、G、B通道值的三维矩阵。
 在视觉算法中经常需要传递图片、拷贝图片等操作,每次都拷贝矩阵开销较大,因此OpenCV采用了引用计数机制,让每个Mat对象有自己的信息头,但共享同一个矩阵,拷贝构造函数则只拷贝信息头和矩阵指针,而不拷贝矩阵。

2.1 创建Mat

创建一个Mat的方法如下:

/**
* @param rows行;对应bitmap的高
* @param clos列;对应bitmap的宽
* @param 颜色空间&数据类型CvType
* @param 矩阵的数据
**/
Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP);

Android OpenCV基础(一、OpenCV入门)中,我们已经看到过从bitmap对象创建Mat的方法:

void *pixels = 0;
AndroidBitmapInfo info;
// 获取bitmap的信息
AndroidBitmap_getInfo(env, bitmap, &info);
// 获取bitmap的像素值
AndroidBitmap_lockPixels(env, bitmap, &pixels);
cv::Mat rgbMat(info.height, info.width, CV_8UC4, pixels);

2.2 拷贝Mat

第一章中讲到过拷贝构造函数只拷贝信息头和矩阵指针,而不拷贝矩阵。以下操作都不会拷贝矩阵:

Mat B(A);                                 // 使用拷贝构造函数
C = A;                                    // 赋值运算符

如果你确实需要拷贝矩阵本身,可以通过以下两个方法:

cv::Mat tmp(info.height, info.width, CV_8UC4, pixels);
cv::Mat dst;
// 方法1:copyTo
tmp.copyTo(dst);
// 方法2:clone
dst = tmp.clone

2.3 CvType

在Mat的构造函数中,需要传入的CvType是OpenCV内置的类型,格式含义如下:

// CvType含义:[每个颜色所占位数][是否带符号][基本数据类型][每个颜色的通道数]
CV_[The number of bits per item][Signed or Unsigned][Type Prefix]C[The channel number]
// 例如CV_8UC4表示:每个颜色占8位,用unsigned char表示,每个颜色有4个通道(R、G、B、A)
cv::Mat rgbMat(info.height, info.width, CV_8UC4, pixels);

2.3.1 颜色空间

颜色空间是指对一个给定的颜色,如何组合颜色元素以对其编码。常见的有:

  • RGB:用红色Red、绿色Green和蓝色Blue作为基本色,是最常见的,这是因为人眼采用相似的工作机制,它也被显示设备所采用。
  • RGBA:在RGB的基础上加入了透明度Alpha。
  • YCrCb:在JPEG图像格式中广泛使用。
  • YUV:用于Android相机的颜色空间,"Y"表示明亮度(Luminance或Luma,也就是灰度值);而"U"和"V"表示色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。

2.3.2 数据类型

数据类型是指每个元素如何存储,存储的方式决定了颜色在其定义域上能够控制的精度。例如,在RGB空间,如果对于单个的R、G、B用char存储,char占8位,那么RGB就可以表示出1600万种可能的颜色(256 * 256 * 256)。
 如果使用更多的类型存储(比如32位的float)或64位的double),则能给出更加精细的颜色分辨能力,单也会增加图像所占的内存空间。

三、Bitmap与Mat

3.1 Bitmap转Mat

void bitmapToMat(JNIEnv *env, jobject bitmap, cv::Mat &dst) {
    AndroidBitmapInfo info;
    void *pixels = 0;
    try {
        CV_Assert(AndroidBitmap_getInfo(env, bitmap, &info) >= 0);
        CV_Assert(info.format == ANDROID_BITMAP_FORMAT_RGBA_8888 ||
                  info.format == ANDROID_BITMAP_FORMAT_RGB_565);
        CV_Assert(AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0);
        CV_Assert(pixels);
        dst.create(info.height, info.width, CV_8UC4);
        if (info.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
            cv::Mat tmp(info.height, info.width, CV_8UC4, pixels);
            tmp.copyTo(dst);
        } else {
            cv::Mat tmp(info.height, info.width, CV_8UC2, pixels);
            cvtColor(tmp, dst, CV_BGR5652RGBA);
        }
        AndroidBitmap_unlockPixels(env, bitmap);
        return;
    }catch (...) {
        AndroidBitmap_unlockPixels(env, bitmap);
        jclass je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, "Unknown exception in JNI code {nBitmapToMat}");
        return;
    }
}

3.2 Mat转Bitmap

/**
* 创建Bitmap对象
*/
jobject createBitmap(JNIEnv *env, int width, int height, std::string config) {
    jclass bitmapConfig = env->FindClass("android/graphics/Bitmap$Config");
    jfieldID configFieldID = env->GetStaticFieldID(bitmapConfig, config.c_str(),
                                                   "Landroid/graphics/Bitmap$Config;");
    jobject rgb565Obj = env->GetStaticObjectField(bitmapConfig, configFieldID);
    jclass bitmapClass = env->FindClass("android/graphics/Bitmap");
    jmethodID createBitmapMethodID = env->GetStaticMethodID(bitmapClass,"createBitmap",
                                                            "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
    jobject bitmapObj = env->CallStaticObjectMethod(bitmapClass, createBitmapMethodID,
                                                    width, height, rgb565Obj);
    env->DeleteLocalRef(bitmapConfig);
    env->DeleteLocalRef(bitmapClass);
    return bitmapObj;
}

/**
* Mat转Bitmap
*/
void matToBitmap(JNIEnv *env, cv::Mat &src, jobject bitmap) {
    AndroidBitmapInfo info;
    void *pixels = 0;
    try {
        if (AndroidBitmap_getInfo(env, bitmap, &info) < 0) {
            return;
        }
        if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888 &&
            info.format != ANDROID_BITMAP_FORMAT_RGB_565) {
            return;
        }
        if (src.dims != 2 || info.height != (uint32_t) src.rows ||
            info.width != (uint32_t) src.cols) {
            return;
        }
        if (src.type() != CV_8UC1 && src.type() != CV_8UC3 && src.type() != CV_8UC4) {
            return;
        }
        if (AndroidBitmap_lockPixels(env, bitmap, &pixels) < 0) {
            return;
        }
        if (pixels == 0) {
            return;
        }
        if (info.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
            cv::Mat tmp(info.height, info.width, CV_8UC4, pixels);
            if (src.type() == CV_8UC1) {
                cvtColor(src, tmp, CV_GRAY2RGBA);
            } else if (src.type() == CV_8UC3) {
                cvtColor(src, tmp, CV_RGB2RGBA);
            } else if (src.type() == CV_8UC4) {
                src.copyTo(tmp);
            }
        } else {
            // info.format == ANDROID_BITMAP_FORMAT_RGB_565
            cv::Mat tmp(info.height, info.width, CV_8UC2, pixels);
            if (src.type() == CV_8UC1) {
                cvtColor(src, tmp, CV_GRAY2BGR565);
            } else if (src.type() == CV_8UC3) {
                cvtColor(src, tmp, CV_RGB2BGR565);
            } else if (src.type() == CV_8UC4) {
                cvtColor(src, tmp, CV_RGBA2BGR565);
            }
        }
        AndroidBitmap_unlockPixels(env, bitmap);
        return;
    } catch (const cv::Exception &e) {
        AndroidBitmap_unlockPixels(env, bitmap);
        jclass je = env->FindClass("org/opencv/core/CvException");
        if (!je) je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, e.what());
        return;
    } catch (...) {
        AndroidBitmap_unlockPixels(env, bitmap);
        jclass je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, "Unknown exception in JNI code {nMatToBitmap}");
        return;
    }
}

四、图片变换

一般来说,图像处理算子是指带有一幅或多幅输入图像、产生一幅输出图像的函数。图像变换可分为以下两种:

  • 点算子(像素变换):这类算子仅仅根据输入像素值(有时可加上某些全局信息或参数)计算得到相应的输出像素值,常见算子包括亮度和对比度调整 ,以及颜色校正和变换。
  • 邻域(基于区域的)算子:这类算子根据据输入像素值以及其周围的像素值计算得到相应的输出像素值,常见算子包括核函数、滤波器等。

4.1 灰度图

首先声明JNI接口如下:

public class OpenCVSample {
    static {
        try {
            System.loadLibrary("native-lib");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }

    public static native Bitmap greyBitmap(Bitmap bitmap);

}

然后与Android OpenGL图片后处理中类似,采用相同的方式计算灰度值:

grey=0.2126∗red+0.7152∗green+0.0722∗bluegrey = 0.2126 * red + 0.7152 * green + 0.0722 * bluegrey=0.2126∗red+0.7152∗green+0.0722∗blue

extern "C"
JNIEXPORT jobject JNICALL
Java_com_bc_sample_OpenCVSample_greyBitmap(
        JNIEnv *env,
        jclass thiz, jobject bitmap) {
    cv::Mat rgbMat;
    bitmapToMat(env, bitmap, rgbMat);
    // 灰度图是单通道
    cv::Mat dst = cv::Mat(rgbMat.size(), CV_8UC1);
    // grey = 0.2126 * r + 0.7152 * g + 0.0722 * b;
    for (int y = 0; y < rgbMat.rows; y++) {
        for (int x = 0; x < rgbMat.cols; x++) {
            // 灰度图是单通道,所以取的(y,x)是uchar类型
            // rgbMat是四通道,所以取的(y,x)是Vec4b类型
            dst.at<uchar>(y, x) =
                    cv::saturate_cast<uchar>(
                            0.2126f * rgbMat.at<cv::Vec4b>(y, x)[0]
                            + 0.7152f * rgbMat.at<cv::Vec4b>(y, x)[1]
                            + 0.0722f * rgbMat.at<cv::Vec4b>(y, x)[2]);
        }
    }
    jobject resultBitmap = createBitmap(env, dst.cols, dst.rows, "ARGB_8888");
    matToBitmap(env, dst, resultBitmap);
    return resultBitmap;
}

运行结果如下所示:

4.2 图片亮化

首先声明JNI接口如下:

public class OpenCVSample {
    static {
        try {
            System.loadLibrary("native-lib");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }
    public static native Bitmap lightenBitmap(Bitmap bitmap);
}

然后在native层实现如下点算子计算逻辑:

g(i,j)=alpha∗f(i,j)+betag(i, j) = alpha * f(i, j) + betag(i,j)=alpha∗f(i,j)+beta

其中 i 和 j 表示第 i 行第 j 列的像素点,alpha和beta是参数,程序中我们使用alpha = 2.2,beta=50,实现如下:

extern "C"
JNIEXPORT jobject JNICALL
Java_com_bc_sample_OpenCVSample_lightenBitmap(
        JNIEnv *env,
        jclass thiz, jobject bitmap) {
    cv::Mat rgbMat;
    bitmapToMat(env, bitmap, rgbMat);
    // 创建一个同样大小的结果Mat
    cv::Mat dst = cv::Mat(rgbMat.size(), rgbMat.type());
    /// 执行运算 new_image(i,j) = alpha*image(i,j) + beta
    float alpha = 2.2f;
    int beta = 50;
    for (int y = 0; y < rgbMat.rows; y++) {
        for (int x = 0; x < rgbMat.cols; x++) {
            for (int c = 0; c < rgbMat.channels(); c++) {
                // Vec4b因为我们的使用的是RGBA通道,如果RGB则是Vec3b
                // 实现计算逻辑
                dst.at<cv::Vec4b>(y, x)[c] = cv::saturate_cast<uchar>(
                        alpha * (rgbMat.at<cv::Vec4b>(y, x)[c]) + beta);
            }
        }
    }
    jobject lightenBitmap = createBitmap(env, dst.cols, dst.rows, "ARGB_8888");
    matToBitmap(env, dst, lightenBitmap);
    return lightenBitmap;
}

运行结果如下所示:


4.3 图片锐化

锐化是一个简单的邻域算子。与OpenGL实现锐化原理类似,锐化其实就是根据掩码矩阵(也称作核)重新计算图像中每个像素的值。
 这次我们以如下矩阵作为锐化的kenal核函数:

[0−10−15−10−10]\begin{bmatrix} 0 & -1 & 0 \ -1 & 5 & -1 \ 0 & -1 & 0 \ \end{bmatrix}⎣⎢⎡0−10−15−10−10⎦⎥⎤

首先声明JNI接口如下:

public class OpenCVSample {
    static {
        try {
            System.loadLibrary("native-lib");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }
    public static native Bitmap sharpenBitmap(Bitmap bitmap);
}

然后在native层实现如下邻域算子计算逻辑,先把矩阵中心的元素(上面的例子中是(0,0)位置的元素,也就是5)对齐到要计算的目标像素上,再把邻域像素值和相应的矩阵元素值的乘积加起来。:

extern "C"
JNIEXPORT jobject JNICALL
Java_com_bc_sample_OpenCVSample_sharpenBitmap(
        JNIEnv *env,
        jclass thiz, jobject bitmap) {
    cv::Mat rgbMat;
    bitmapToMat(env, bitmap, rgbMat);
    cv::Mat dst;
    sharpen(rgbMat, dst);
    jobject sharpenBitmap = createBitmap(env, dst.cols, dst.rows, "ARGB_8888");
    matToBitmap(env, dst, sharpenBitmap);
    return sharpenBitmap;
}

/**
* 锐化核函数实现
**/
void sharpen(const cv::Mat &myImage, cv::Mat &Result) {
    CV_Assert(myImage.depth() == CV_8U);  // 仅接受uchar图像
    Result.create(myImage.size(), myImage.type());
    const int nChannels = myImage.channels();
    for (int j = 1; j < myImage.rows - 1; ++j) {
        // 矩阵中的当前点
        const uchar *previous = myImage.ptr<uchar>(j - 1);
        // 矩阵中当前点的前一个点(当前列-1)
        const uchar *current = myImage.ptr<uchar>(j);
        // 矩阵中当前点的下一个点(当前列+1)
        const uchar *next = myImage.ptr<uchar>(j + 1);
        uchar *output = Result.ptr<uchar>(j);
        for (int i = nChannels; i < nChannels * (myImage.cols - 1); ++i) {
            *output++ = cv::saturate_cast<uchar>(5 * current[i]
                                                 - current[i - nChannels] - current[i + nChannels] -
                                                 previous[i] - next[i]);
              // 或者使用其他锐化核函数
//            *output++ = cv::saturate_cast<uchar>(9 * current[i]
//                                                 - current[i - nChannels] - current[i + nChannels]
//                                                 -previous[i] - previous[i - nChannels] - previous[i + nChannels]
//                                                 -next[i] - next[i - nChannels] - next[i + nChannels]);
        }
    }
    // 不对边界点使用掩码,直接把它们设为0
    Result.row(0).setTo(cv::Scalar(0)); // 上边界
    Result.row(Result.rows - 1).setTo(cv::Scalar(0)); // 下边界
    Result.col(0).setTo(cv::Scalar(0)); // 左边界
    Result.col(Result.cols - 1).setTo(cv::Scalar(0));// 右边界
}

运行结果如下所示,左边是APP运行结果,右边是放大后的对比:


4.4 imgproc 模块

imgproc模块提供了许多图片处理的API,实际开发中可以直接调用OpenCV提供的图片处理API,我们将在下一章介绍imgproc模块。上述图片处理都可以在imgproc模块找到对应API:

// 颜色转换:RGBA转灰度
cv::cvtColor(rgbMat, dst, CV_RGBA2GRAY);
// 滤波器
CVAPI(void) cvFilter2D( const CvArr* src, CvArr* dst, const CvMat* kernel,
                        CvPoint anchor CV_DEFAULT(cvPoint(-1,-1)));

作者:BC
转载来源于:https://juejin.cn/post/7083054545294589988
如有侵权,请联系删除!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容