Android OpenCV基礎(chǔ)之core模塊

一、概述

在真實世界中来吩,我們(人類)看到的是圖像敢辩,而讓數(shù)字設(shè)備來“看“的時候,則是在記錄圖像中的每一個點的數(shù)值弟疆。

如上面的圖像戚长,數(shù)字設(shè)備看到的是一個矩陣,該矩陣包含了所有像素點的值怠苔。最終在計算機世界里所有圖像都可以簡化為數(shù)值矩以及矩陣信息同廉。
?OpenCV的core模塊定義了如何在內(nèi)存中存儲圖像,還包括矩陣柑司、向量迫肖、點等一些基礎(chǔ)操作的定義。

二攒驰、基本圖像容器

OpenCV定義了Mat類作為基本圖像容器蟆湖,此外Mat還可以只單純地表示一個矩陣。Mat由兩個數(shù)據(jù)部分組成:矩陣頭(包含矩陣尺寸玻粪,存儲方法隅津,存儲地址等信息)和一個指向存儲所有像素值的矩陣(根據(jù)所選存儲方法的不同矩陣可以是不同的維數(shù))的指針。矩陣頭的尺寸是常數(shù)值劲室,但矩陣本身的尺寸會依圖像的不同而不同伦仍。例如,一個RGB的圖片很洋,其Mat對象的矩陣就是一個分別存儲R充蓝、G、B通道值的三維矩陣蹲缠。
?在視覺算法中經(jīng)常需要傳遞圖片棺克、拷貝圖片等操作,每次都拷貝矩陣開銷較大线定,因此OpenCV采用了引用計數(shù)機制,讓每個Mat對象有自己的信息頭确买,但共享同一個矩陣斤讥,拷貝構(gòu)造函數(shù)則只拷貝信息頭和矩陣指針,而不拷貝矩陣湾趾。

2.1 創(chuàng)建Mat

創(chuàng)建一個Mat的方法如下:

/**
* @param rows行;對應(yīng)bitmap的高
* @param clos列;對應(yīng)bitmap的寬
* @param 顏色空間&數(shù)據(jù)類型CvType
* @param 矩陣的數(shù)據(jù)
**/
Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP);

Android OpenCV基礎(chǔ)(一芭商、OpenCV入門)中,我們已經(jīng)看到過從bitmap對象創(chuàng)建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

第一章中講到過拷貝構(gòu)造函數(shù)只拷貝信息頭和矩陣指針搀缠,而不拷貝矩陣铛楣。以下操作都不會拷貝矩陣:

Mat B(A);                                 // 使用拷貝構(gòu)造函數(shù)
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的構(gòu)造函數(shù)中艺普,需要傳入的CvType是OpenCV內(nèi)置的類型簸州,格式含義如下:

// CvType含義:[每個顏色所占位數(shù)][是否帶符號][基本數(shù)據(jù)類型][每個顏色的通道數(shù)]
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作為基本色读虏,是最常見的责静,這是因為人眼采用相似的工作機制,它也被顯示設(shè)備所采用盖桥。
  • RGBA:在RGB的基礎(chǔ)上加入了透明度Alpha泰演。
  • YCrCb:在JPEG圖像格式中廣泛使用。
  • YUV:用于Android相機的顏色空間葱轩,"Y"表示明亮度(Luminance或Luma睦焕,也就是灰度值);而"U"和"V"表示色度(Chrominance或Chroma)靴拱,作用是描述影像色彩及飽和度垃喊,用于指定像素的顏色。

2.3.2 數(shù)據(jù)類型

數(shù)據(jù)類型是指每個元素如何存儲袜炕,存儲的方式?jīng)Q定了顏色在其定義域上能夠控制的精度本谜。例如,在RGB空間偎窘,如果對于單個的R乌助、G、B用char存儲陌知,char占8位他托,那么RGB就可以表示出1600萬種可能的顏色(256 * 256 * 256)。
?如果使用更多的類型存儲(比如32位的float)或64位的double)仆葡,則能給出更加精細的顏色分辨能力赏参,單也會增加圖像所占的內(nèi)存空間。

三沿盅、Bitmap與Mat

3.1 Bitmap轉(zhuǎn)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轉(zhuǎn)Bitmap

/**
* 創(chuàng)建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轉(zhuǎn)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;
    }
}

四把篓、圖片變換

一般來說,圖像處理算子是指帶有一幅或多幅輸入圖像腰涧、產(chǎn)生一幅輸出圖像的函數(shù)韧掩。圖像變換可分為以下兩種:

  • 點算子(像素變換):這類算子僅僅根據(jù)輸入像素值(有時可加上某些全局信息或參數(shù))計算得到相應(yīng)的輸出像素值,常見算子包括亮度和對比度調(diào)整 窖铡,以及顏色校正和變換疗锐。
  • 鄰域(基于區(qū)域的)算子:這類算子根據(jù)據(jù)輸入像素值以及其周圍的像素值計算得到相應(yīng)的輸出像素值坊谁,常見算子包括核函數(shù)、濾波器等窒悔。

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;
}

運行結(jié)果如下所示:

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層實現(xiàn)如下點算子計算邏輯:

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是參數(shù),程序中我們使用alpha = 2.2聋庵,beta=50膘融,實現(xiàn)如下:

extern "C"
JNIEXPORT jobject JNICALL
Java_com_bc_sample_OpenCVSample_lightenBitmap(
        JNIEnv *env,
        jclass thiz, jobject bitmap) {
    cv::Mat rgbMat;
    bitmapToMat(env, bitmap, rgbMat);
    // 創(chuàng)建一個同樣大小的結(jié)果Mat
    cv::Mat dst = cv::Mat(rgbMat.size(), rgbMat.type());
    /// 執(zhí)行運算 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
                // 實現(xiàn)計算邏輯
                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;
}

運行結(jié)果如下所示:


4.3 圖片銳化

銳化是一個簡單的鄰域算子祭玉。與OpenGL實現(xiàn)銳化原理類似氧映,銳化其實就是根據(jù)掩碼矩陣(也稱作核)重新計算圖像中每個像素的值。
?這次我們以如下矩陣作為銳化的kenal核函數(shù):

[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層實現(xiàn)如下鄰域算子計算邏輯脱货,先把矩陣中心的元素(上面的例子中是(0,0)位置的元素岛都,也就是5)對齊到要計算的目標像素上,再把鄰域像素值和相應(yīng)的矩陣元素值的乘積加起來振峻。:

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;
}

/**
* 銳化核函數(shù)實現(xiàn)
**/
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]);
              // 或者使用其他銳化核函數(shù)
//            *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]);
        }
    }
    // 不對邊界點使用掩碼臼疫,直接把它們設(shè)為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));// 右邊界
}

運行結(jié)果如下所示,左邊是APP運行結(jié)果扣孟,右邊是放大后的對比:


4.4 imgproc 模塊

imgproc模塊提供了許多圖片處理的API烫堤,實際開發(fā)中可以直接調(diào)用OpenCV提供的圖片處理API,我們將在下一章介紹imgproc模塊凤价。上述圖片處理都可以在imgproc模塊找到對應(yīng)API:

// 顏色轉(zhuǎn)換:RGBA轉(zhuǎn)灰度
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
轉(zhuǎn)載來源于:https://juejin.cn/post/7083054545294589988
如有侵權(quán)鸽斟,請聯(lián)系刪除!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末利诺,一起剝皮案震驚了整個濱河市富蓄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌立轧,老刑警劉巖格粪,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異氛改,居然都是意外死亡,警方通過查閱死者的電腦和手機比伏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門胜卤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人赁项,你說我怎么就攤上這事葛躏〕憾危” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵舰攒,是天一觀的道長败富。 經(jīng)常有香客問我,道長摩窃,這世上最難降的妖魔是什么兽叮? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮猾愿,結(jié)果婚禮上鹦聪,老公的妹妹穿的比我還像新娘。我一直安慰自己蒂秘,他們只是感情好泽本,可當我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著姻僧,像睡著了一般规丽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上撇贺,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天赌莺,我揣著相機與錄音,去河邊找鬼显熏。 笑死雄嚣,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的喘蟆。 我是一名探鬼主播缓升,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蕴轨!你這毒婦竟也來了港谊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤橙弱,失蹤者是張志新(化名)和其女友劉穎歧寺,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體棘脐,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡斜筐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蛀缝。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片顷链。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖屈梁,靈堂內(nèi)的尸體忽然破棺而出嗤练,到底是詐尸還是另有隱情榛了,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布煞抬,位于F島的核電站霜大,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏革答。R本人自食惡果不足惜战坤,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蝗碎。 院中可真熱鬧湖笨,春花似錦、人聲如沸蹦骑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽眠菇。三九已至边败,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間捎废,已是汗流浹背笑窜。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留登疗,地道東北人排截。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像辐益,于是被迫代替她去往敵國和親断傲。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內(nèi)容