計(jì)算機(jī)視覺 OpenCV Android | Mat像素操作(圖像像素的讀寫、均值方差屡贺、算術(shù)蠢棱、邏輯等運(yùn)算、權(quán)重疊加甩栈、歸一化等操作)

本文目錄

1. 像素讀寫
2. 圖像通道與均值方差計(jì)算
3. 算術(shù)操作與調(diào)整圖像的亮度和對(duì)比度
4. 基于權(quán)重的圖像疊加
5. Mat的其他各種像素操作





1. 像素讀寫

  • Mat作為圖像容器泻仙,其數(shù)據(jù)部分存儲(chǔ)了圖像的像素?cái)?shù)據(jù),我們可以通過相關(guān)的API來獲取圖像數(shù)據(jù)部分量没;
  • 在獲取圖像數(shù)據(jù)的時(shí)候玉转,知道Mat的類型通道數(shù)目關(guān)重要,
    根據(jù)Mat的類型通道數(shù)目允蜈,開辟適當(dāng)大小的內(nèi)存空間冤吨,
    然后通過get方法就可以循環(huán)實(shí)現(xiàn)每個(gè)像素點(diǎn)值的讀取蒿柳、修改
    然后再通過put方法修改與Mat對(duì)應(yīng)的數(shù)據(jù)部分漩蟆。

常見的Mat的像素讀寫get與put方法支持如下表:

  • 默認(rèn)情況下垒探,imread方式將Mat對(duì)象類型加載為CV_8UC3
    本系列筆記跟隨原著默認(rèn)提到的加載圖像文件均為Mat對(duì)象怠李、類型均為CV_8UC3圾叼、通道順序均為BGR
  • 上表中所列舉的是當(dāng)前OpenCV支持的讀取圖像的方法捺癞;
    使用時(shí)若需要將像素值寫入到Mat對(duì)象中夷蚊,使用與每個(gè)get方法相對(duì)應(yīng)的put方法即可。
  • 根據(jù)開辟緩存區(qū)域data數(shù)組的大小髓介,
    讀寫像素既可以每次從Mat中讀取一個(gè)像素點(diǎn)數(shù)據(jù)惕鼓,
    或者可以每次從Mat中讀取一行像素?cái)?shù)據(jù),
    還可以一次從Mat中讀取全部像素?cái)?shù)據(jù)唐础。

下面演示對(duì)Mat對(duì)象中的每個(gè)像素點(diǎn)的值都進(jìn)行取反操作箱歧,并且分別用這三種方法實(shí)現(xiàn)像素操作

  • 首先要將圖像加載為Mat對(duì)象一膨,
    然后獲取圖像的寬呀邢、高以及通道數(shù)channels(特別注意這三個(gè)值,接下來一直用到豹绪,尤其channels)
Mat src = Imgcodecs.imread(fileUri.getPath());
if(src.empty()){
  return;
}
int channels = src.channels();
int width = src.cols();
int height = src.rows();

接下來便可以通過方才所述三種方式讀取像素?cái)?shù)據(jù)价淌、修改、寫入比較它們的執(zhí)行時(shí)間瞒津。

1.1.從Mat中每次讀取一個(gè)像素點(diǎn)數(shù)據(jù)

對(duì)于CV_8UC3Mat類型來說蝉衣,對(duì)應(yīng)的數(shù)據(jù)類型byte
則先初始化byte數(shù)組data仲智,用來存取每次讀取出來的一個(gè)像素點(diǎn)的所有通道值买乃,
數(shù)組的長度取決于圖像通道數(shù)目

完整代碼如下:

byte[] data = new byte[channels];
int b=0, g=0, r=0;
for(int row=0; row<height; row++) {
  for(int col=0; col<width; col++) {
      // 讀取
      src.get(row, col, data);//!!!!!!!!!!!!!!!!!!!!!!!讀取一個(gè)px
      b = data[0]&0xff;
      g = data[1]&0xff;
      r = data[2]&0xff;
      // 修改
      b = 255 - b;
      g = 255 - g;
      r = 255 - r;
      // 寫入
      data[0] = (byte)b;
      data[1] = (byte)g;
      data[2] = (byte)r;
      src.put(row, col, data);
  }
}

補(bǔ)充詮釋

  • 一個(gè)px有多個(gè)通道钓辆;
  • 一個(gè)通道配給它一個(gè)數(shù)組元素剪验;
  • 1.2中逐行讀取時(shí)的一個(gè)列(某行中的某個(gè)列其實(shí)就是一個(gè)數(shù)組元素而已)不是px,
    而只是某個(gè)px的一個(gè)channel而已前联;
  • 1.3 同理
  • 即1.2 以及1.3 中功戚,data的一個(gè)元素,不是px似嗤,而只是某個(gè)px的一個(gè)channel而已啸臀;
1.2 從Mat中每次讀取一行像素?cái)?shù)據(jù)

首先需要定義每一行像素?cái)?shù)據(jù)數(shù)組的長度,這里為圖像寬度乘以每個(gè)像素的通道數(shù)目
接著循環(huán)修改每一行的數(shù)據(jù)乘粒;
這里get方法第二個(gè)參數(shù) col = 0的意思是從每一行的第一列開始獲取像素?cái)?shù)據(jù)豌注。

完整代碼如下:

       // each row data
        byte[] data = new byte[channels*width];//channels 是一個(gè)px的通道數(shù);width是一個(gè)行的px的個(gè)數(shù)灯萍;
        // loop
        int b=0, g=0, r=0;
        int pv = 0;
        for(int row=0; row<height; row++) {
            src.get(row, 0, data);
            /*get一整行的px數(shù)據(jù)轧铁,存進(jìn)data;形象地說旦棉,是以 位置是(row齿风, 0)的第一個(gè)px的第一個(gè)channel為起始元素,獲取一個(gè)data長度的數(shù)據(jù)绑洛;
            數(shù)據(jù)一個(gè)元素(channel)一個(gè)元素(channel)地存進(jìn)數(shù)組data救斑, 每個(gè)元素是某個(gè)px的一個(gè)channel;*/
            for(int col=0; col<data.length; col++) {//行中循環(huán)列真屯,處理內(nèi)容:修改一整行的數(shù)據(jù)
                // 讀取
                pv = data[col]&0xff;
                // 修改
                pv = 255 - pv;
                data[col] = (byte)pv;
            }
            // 至此脸候,data蓄滿一行修改好的px(channel)數(shù)據(jù)
            // 寫入
            src.put(row, 0, data);
        }

關(guān)于代碼的補(bǔ)充詮釋

  • byte[] data = new byte[channels*width];中:
    channels 是一個(gè)px的通道數(shù);
    width是一個(gè)行的px的個(gè)數(shù)绑蔫;
  • for(int row=0; row<height; row++):外層 for 循環(huán)行纪他;
  • src.get(row, 0, data);get一整行的px數(shù)據(jù),存進(jìn)data晾匠;
    形象地說,
    是以 位置是(row梯刚, 0)第一個(gè)px第一個(gè)channel起始元素凉馆,
    獲取一個(gè)data長度的數(shù)據(jù);
    數(shù)據(jù)一個(gè)元素(channel)一個(gè)元素(channel)地存進(jìn)數(shù)組data亡资,
    每個(gè)元素是某個(gè)px的一個(gè)channel澜共;
  • for(int col=0; col<data.length; col++)次層 for ,
    行中循環(huán)列锥腻,處理內(nèi)容:修改一整行的數(shù)據(jù)嗦董;
  • 次層for執(zhí)行完畢,data蓄滿一行修改好的px(channel)數(shù)據(jù)瘦黑;
  • src.put(row, 0, data):數(shù)組對(duì)象引用賦給行首京革,交付整行數(shù)據(jù);
    形象地說幸斥,
    是以 位置是(row匹摇, 0)第一個(gè)px第一個(gè)channel起始元素
    提交一個(gè)data長度的數(shù)據(jù)甲葬,即一整行廊勃;
1.3 從Mat中一次讀取全部像素?cái)?shù)據(jù)
  • 首先定義數(shù)組長度,這里為圖像寬度×圖像高度×通道數(shù)目经窖,
    然后一次性獲取全部像素?cái)?shù)據(jù)坡垫,
    get的前面兩個(gè)參數(shù)row=0梭灿、col=0,表示從第一個(gè)像素的第一個(gè)channel開始讀取冰悠。

完整代碼如下:

// all pixels
int pv = 0;
byte[] data = new byte[channels*width*height];
src.get(0, 0, data);
for(int i=0; i<data.length; i++) {
  pv = data[i]&0xff;
  pv = 255-pv;
  data[i] = (byte)pv;
}
src.put(0, 0, data);

關(guān)于代碼的補(bǔ)充詮釋(參考1.2的補(bǔ)充堡妒,不難理解)

  • src.get(0, 0, data);get全部的px數(shù)據(jù),存進(jìn)data屿脐;
    形象地說涕蚤,
    是以 位置是(0, 0)第一個(gè)px第一個(gè)channel起始元素的诵,
    獲取一個(gè)data長度的數(shù)據(jù)万栅;
    數(shù)據(jù)一個(gè)元素(channel)一個(gè)元素(channel)地存進(jìn)數(shù)組data
    每個(gè)元素是某個(gè)px的一個(gè)channel西疤;
  • src.put(0, 0, data):數(shù)組對(duì)象引用賦給行首烦粒,交付全部數(shù)據(jù);
    形象地說代赁,
    是以 位置是(0扰她, 0)第一個(gè)px第一個(gè)channel起始元素
    提交一個(gè)data長度的數(shù)據(jù)芭碍,即全部px的全部channel徒役;

上述三種方法

  • 第一種方法因?yàn)轭l繁訪問JNI調(diào)用(*!=押尽忧勿!* |get())而效率低下,但是內(nèi)存(*U胺怼T!* |局部變量data的長度)需求最兴儆隆晌砾;
  • 第二種方法每次讀取一行,相比第一種方法速度有所提高烦磁,但是內(nèi)存使用增加养匈;
  • 第三種方法一次讀取Mat中的全部像素?cái)?shù)據(jù),在內(nèi)存中循環(huán)修改速度最快都伪,通過JNI調(diào)用OpenCV底層C++方法次數(shù)最少乖寒,因而效率也是最高的,但是對(duì)于高分辨率圖像院溺,這種方式顯然內(nèi)存消耗過多楣嘁,容易導(dǎo)致OOM問題

所以Android開發(fā)者在使用OpenCV的時(shí)候,
需要注意應(yīng)根據(jù)項(xiàng)目需求逐虚,
選擇第二種或者第三種方法實(shí)現(xiàn)像素讀寫聋溜,
第一種方法只適用于隨機(jī)少量像素讀寫的場合。
三種方法在實(shí)例項(xiàng)目中調(diào)試時(shí):

    public void readAndWritePixels() {
        Mat src = Imgcodecs.imread(fileUri.getPath());
        if(src.empty()){
            return;
        }
        int channels = src.channels();
        int width = src.cols();
        int height = src.rows();

////        // each row data
//        byte[] data = new byte[channels*width];//channels 是一個(gè)px的通道數(shù)叭爱;width是一個(gè)行的px的個(gè)數(shù)撮躁;
//        // loop
//        int b=0, g=0, r=0;
//        int pv = 0;
//        for(int row=0; row<height; row++) {
//            src.get(row, 0, data);
//            /*get一整行的px數(shù)據(jù),存進(jìn)data买雾;形象地說把曼,是以 位置是(row, 0)的第一個(gè)px的第一個(gè)channel為起始元素漓穿,獲取一個(gè)data長度的數(shù)據(jù)嗤军;
//            數(shù)據(jù)一個(gè)元素(channel)一個(gè)元素(channel)地存進(jìn)數(shù)組data, 每個(gè)元素是某個(gè)px的一個(gè)channel晃危;*/
//            for(int col=0; col<data.length; col++) {//行中循環(huán)列叙赚,處理內(nèi)容:修改一整行的數(shù)據(jù)
//                // 讀取
//                pv = data[col]&0xff;
//                // 修改
//                pv = 255 - pv;
//                data[col] = (byte)pv;
//            }
//            // 至此,data蓄滿一行修改好的px(channel)數(shù)據(jù)
//            // 寫入
//            src.put(row, 0, data);
//        }

        // all pixels
        int pv = 0;
        byte[] data = new byte[channels*width*height];
        src.get(0, 0, data);
        for(int i=0; i<data.length; i++) {
            pv = data[i]&0xff;
            pv = 255-pv;
            data[i] = (byte)pv;
        }
        src.put(0, 0, data);

        Bitmap bm = Bitmap.createBitmap(src.cols(), src.rows(), Bitmap.Config.ARGB_8888);
        Mat dst = new Mat();
        Imgproc.cvtColor(src, dst, Imgproc.COLOR_BGR2RGBA);
        Utils.matToBitmap(dst, bm);
        ImageView iv = (ImageView)this.findViewById(R.id.chapter3_imageView);
        iv.setImageBitmap(bm);
    }





2. 圖像通道與均值方差計(jì)算

  • 圖像中通道數(shù)目的多少可以通過Mat對(duì)象channels()進(jìn)行查詢獲取僚饭。
  • 對(duì)于多通道的圖像震叮,Mat提供的API方法可以把它分為多個(gè)單通道的圖像;
    同樣對(duì)于多個(gè)單通道的圖像鳍鸵,也可以組合成一個(gè)多通道的圖像苇瓣。
  • OpenCV還提供了計(jì)算圖像每個(gè)通道像素平均值標(biāo)準(zhǔn)方差的API方法,
    通過它們可以計(jì)算得到圖像的像素平均值與方差偿乖,
    根據(jù)平均值可以實(shí)現(xiàn)基于平均值的二值圖像分割钓简,
    根據(jù)標(biāo)準(zhǔn)方差可以找到空白圖像或者無效圖像
2.1 圖像通道分離與合并
  • 圖像通道數(shù)通過Mat的channels()獲取之后汹想,
    如果通道數(shù)目大于1,
    那么根據(jù)需要調(diào)用split方法就可以實(shí)現(xiàn)通道分離撤蚊,
    通過merge方法就可以實(shí)現(xiàn)通道合并古掏,

這兩個(gè)方法的詳細(xì)解釋具體如下:

  • split(Mat m, List<Mat> mv) // 通道分離
    m:表示輸入多通道圖像。
    mv:表示分離之后個(gè)單通道圖像侦啸,mv的長度與m的通道數(shù)目一致槽唾。

  • merge(List<Mat> mv, Mat dst) // 通道合并
    mv:表示多個(gè)待合并單通道圖像。
    dst:表示合并之后生成的多通道圖像光涂。

上面兩個(gè)方法都來自Core模塊庞萍,Core模塊主要包含一些Mat操作基礎(chǔ)矩陣數(shù)學(xué)功能

一個(gè)簡單的多通道的Mat對(duì)象其分離與合并的代碼演示如下:

public void channelsAndPixels() {
//        Mat src = Imgcodecs.imread(fileUri.getPath());
//        if(src.empty()){
//            return;
//        }

        //*******
        Bitmap bitmap = BitmapFactory.decodeResource(this.getResources(), R.drawable.lena);
        Mat ori = new Mat();
        Mat src = new Mat();
        Utils.bitmapToMat(bitmap, ori);
        Imgproc.cvtColor(ori, src, Imgproc.COLOR_RGBA2BGR);
        //*******

        List<Mat> mv = new ArrayList<>();
        Core.split(src, mv);
        for(Mat m : mv) {
            int pv = 0;
            int channels = m.channels();//channels = 1,畢竟都調(diào)用了split()了
//            //下面這行用來測試channels的值
//            Toast.makeText(this,"The m.channels is" + channels,Toast.LENGTH_SHORT).show();

            int width = m.cols();
            int height = m.rows();
            byte[] data = new byte[channels*width*height];
            m.get(0, 0, data);
            for(int i=0; i<data.length; i++) {
                pv = data[i]&0xff;
                pv = 255-pv;
                data[i] = (byte)pv;
            }
            m.put(0, 0, data);
        }
        Core.merge(mv, src);

        Bitmap bm = Bitmap.createBitmap(src.cols(), src.rows(), Bitmap.Config.ARGB_8888);
        Mat dst = new Mat();
        Imgproc.cvtColor(src, dst, Imgproc.COLOR_BGR2RGBA);
        Utils.matToBitmap(dst, bm);

        ImageView iv = (ImageView)this.findViewById(R.id.chapter3_imageView);
        iv.setImageBitmap(bm);

        dst.release();
        src.release();
    }

上面的代碼實(shí)現(xiàn)了對(duì)多通道圖像分離之后取反忘闻,
然后再合并钝计,
最后通過Android ImageView組件顯示結(jié)果
如此便是圖像通道分離與合并基本用法

2.2 .均值與標(biāo)準(zhǔn)方差計(jì)算與應(yīng)用

接下來的內(nèi)容是關(guān)于圖像Mat像素?cái)?shù)據(jù)的簡單統(tǒng)計(jì)私恬,計(jì)算均值與方差债沮。

  • 對(duì)給定的一組數(shù)據(jù)計(jì)算其均值μ標(biāo)準(zhǔn)方差stddev的公式如下:

    其中,n表示數(shù)組的長度本鸣、xi表示數(shù)組第i個(gè)元素的值疫衩。
    其中,n表示數(shù)組長度荣德、μ表示均值闷煤、1表示自由度

  • 根據(jù)上述公式涮瞻,
    可以讀取每個(gè)像素點(diǎn)的值鲤拿,
    計(jì)算每個(gè)通道像素的均值與標(biāo)準(zhǔn)方差

OpenCV Core模塊中已經(jīng)實(shí)現(xiàn)了這類API饲宛,具體解釋如下:

  • meanStdDev(Mat src, MatOfDouble mean, MatOfDouble stddev)
    src:表示輸入Mat圖像皆愉。
    mean:表示計(jì)算出各個(gè)通道的均值,數(shù)組長度與通道數(shù)目一致艇抠。
    stddev:表示計(jì)算出各個(gè)通道的標(biāo)準(zhǔn)方差幕庐,數(shù)組長度與通道數(shù)目一致。

  • meanStdDev(Mat src, MatOfDouble mean, MatOfDouble stddev, Mat mask)
    本方法實(shí)現(xiàn)的功能同上家淤,
    不同的是這里多了一個(gè)Mat型參數(shù) mask异剥;
    表示只有當(dāng)mask中對(duì)應(yīng)位置的像素值不等于零時(shí),src中相同位置的像素點(diǎn)才參與計(jì)算均值與標(biāo)準(zhǔn)方差絮重。

完整的基于均值實(shí)現(xiàn)圖像二值分割的代碼如下:

    public void meanAndDev() {
        // 加載圖像
        Mat src = Imgcodecs.imread(fileUri.getPath());
        if(src.empty()){
            return;
        }
        // 轉(zhuǎn)為灰度圖像
        Mat gray = new Mat();
        Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);

        // 計(jì)算均值與標(biāo)準(zhǔn)方差
        MatOfDouble means = new MatOfDouble();
        MatOfDouble stddevs = new MatOfDouble();
        Core.meanStdDev(gray, means, stddevs);

        // 顯示均值與標(biāo)準(zhǔn)方差
        double[] mean = means.toArray();
        double[] stddev = stddevs.toArray();
        Log.i(TAG, "gray image means : " + mean[0]);
        Log.i(TAG, "gray image stddev : " + stddev[0]);

        // 讀取像素?cái)?shù)組
        int width = gray.cols();
        int height = gray.rows();
        byte[] data = new byte[width*height];
        gray.get(0, 0, data);
        int pv = 0;

        // 根據(jù)均值冤寿,二值分割
        int t = (int)mean[0];
        for(int i=0; i<data.length; i++) {
            pv = data[i]&0xff;
            if(pv > t) {
                data[i] = (byte)255;
            } else {
                data[i] = (byte)0;
            }
        }
        gray.put(0, 0, data);

        Bitmap bm = Bitmap.createBitmap(gray.cols(), gray.rows(), Bitmap.Config.ARGB_8888);

        Mat dst = new Mat();
        Imgproc.cvtColor(gray, dst, Imgproc.COLOR_GRAY2RGBA);
        Utils.matToBitmap(dst, bm);

        ImageView iv = (ImageView)this.findViewById(R.id.chapter3_imageView);
        iv.setImageBitmap(bm);

        dst.release();
        gray.release();
        src.release();
    }

最終得到的gray就是二值圖像,轉(zhuǎn)換為Bitmap對(duì)象之后青伤,通過ImageView顯示即可督怜。

  • 另外,
    關(guān)于計(jì)算得到的標(biāo)準(zhǔn)方差狠角,如上面的代碼中假設(shè)stddev[0]的值小于5号杠,那么基本上圖像可以看成是無效圖像或者空白圖像
    因?yàn)?code>標(biāo)準(zhǔn)方差越小則說明圖像各個(gè)像素的差異越小丰歌,圖像本身攜帶的有效信息越少姨蟋;
  • 在圖像處理中,可以利用這個(gè)結(jié)論來提取和過濾質(zhì)量不高的掃描或者打印圖像立帖。





3. 算術(shù)操作與調(diào)整圖像的亮度和對(duì)比度

  • OpenCV的Core模塊支持Mat對(duì)象的加眼溶、減、乘晓勇、除算術(shù)運(yùn)算堂飞,
    這些算術(shù)運(yùn)算都處于Mat對(duì)象層次灌旧,
    可以在任意兩個(gè)Mat之間實(shí)現(xiàn)上述算術(shù)操作,以得到結(jié)果酝静。
3.1 算術(shù)操作API的介紹
  • OpenCV中Mat的加节榜、減、乘别智、除運(yùn)算宗苍,
    既可以在兩個(gè)Mat對(duì)象之間
    也可以在Mat對(duì)象與Scalar之間進(jìn)行薄榛。

  • Mat對(duì)象之間的加讳窟、減、乘敞恋、除運(yùn)算最常用的方法如下:
    add(Mat src1, Mat src2, Mat dst)
    subtract(Mat src1, Mat src2, Mat dst)
    multiply(Mat src1, Mat src2, Mat dst)
    divide(Mat src1, Mat src2, Mat dst)

  • 上述方法的參數(shù)個(gè)數(shù)與意義相同丽啡,具體解釋如下;
    src1:表示輸入的第一個(gè)Mat圖像對(duì)象。
    src2:表示輸入的第二個(gè)Mat圖像對(duì)象硬猫。
    dst:表示算術(shù)操作輸出的Mat對(duì)象补箍。

  • 此外,src2的類型還可以是Scalar類型啸蜜,
    這個(gè)時(shí)候表示圖像的每個(gè)像素點(diǎn)都與Scalar中的每個(gè)向量完成指定的算術(shù)運(yùn)算坑雅。

  • 注意在使用算術(shù)運(yùn)算時(shí)候,
    當(dāng)src1衬横、src2均為Mat對(duì)象的時(shí)候裹粤,
    它們的大小與類型必須一致
    默認(rèn)的輸出圖像類型與輸入圖像類型一致蜂林。

下面是一個(gè)簡單的算術(shù)運(yùn)算的例子遥诉,使用加法,將兩個(gè)Mat對(duì)象的疊加結(jié)果輸出:

    public void matArithmeticDemo() {
        // 輸入圖像src1
        Mat src = Imgcodecs.imread(fileUri.getPath());
        if(src.empty()){
            return;
        }
        // 輸入圖像src2
        Mat moon = Mat.zeros(src.rows(), src.cols(), src.type());
        int cx = src.cols() - 60;
        int cy = 60;
        Imgproc.circle(moon, new Point(cx, cy), 50, new Scalar(90,95,234), -1, 8, 0);

        // 加法運(yùn)算
        Mat dst = new Mat();
        Core.add(src, moon, dst);

        Bitmap bm = Bitmap.createBitmap(src.cols(), src.rows(), Bitmap.Config.ARGB_8888);
        Mat result = new Mat();
        Imgproc.cvtColor(dst, result, Imgproc.COLOR_BGR2RGBA);
        Utils.matToBitmap(result, bm);
        ImageView iv = (ImageView)this.findViewById(R.id.chapter3_imageView);
        iv.setImageBitmap(bm);
    }
3.2 調(diào)整圖像的亮度和對(duì)比度
  • 圖像的亮度和對(duì)比度圖像的兩個(gè)基本屬性噪叙,
    對(duì)RGB色彩圖像來說矮锈,
    亮度越高像素點(diǎn)對(duì)應(yīng)的RGB值應(yīng)該越大睁蕾,越接近255苞笨;
    反之亮度越低,其像素點(diǎn)對(duì)應(yīng)的RGB值應(yīng)該越小惫霸,越接近0
    所以在RGB色彩空間中葱弟,調(diào)整圖像亮度可以簡單地通過對(duì)圖像進(jìn)行加法與減法操作來實(shí)現(xiàn)壹店。

  • 圖像對(duì)比度主要是用來描述圖像顏色與亮度之間的差異感知,
    對(duì)比度越大芝加,圖像的每個(gè)像素與周圍的差異性也就越大硅卢,整個(gè)圖像的細(xì)節(jié)就越顯著射窒;
    反之亦然。
    通過對(duì)圖像進(jìn)行乘法或者除法操作擴(kuò)大或者縮小圖像像素之間的差值将塑,便可調(diào)整圖像對(duì)比度脉顿。

加減法只能使各個(gè)通道值保持差值(差距)去變大變小点寥;
乘除法能放大縮小差值艾疟;

基于Mat與Scalar算術(shù)操作,實(shí)現(xiàn)圖像亮度或者對(duì)比度調(diào)整的代碼實(shí)現(xiàn)如下:

    public void adjustBrightAndContrast(int b, float c) {
        // 輸入圖像src1
        Mat src = Imgcodecs.imread(fileUri.getPath());
        if(src.empty()){
            return;
        }

        // 調(diào)整亮度
        Mat dst1 = new Mat();
        Core.add(src, new Scalar(b, b, b), dst1);

        // 調(diào)整對(duì)比度
        Mat dst2 = new Mat();
        Core.multiply(dst1, new Scalar(c, c, c), dst2);
        //至dst2敢辩,圖像的兩個(gè)度已經(jīng)調(diào)整完畢蔽莱,就差個(gè)轉(zhuǎn)化類型而已

        // 轉(zhuǎn)換為Bitmap,顯示
        Bitmap bm = Bitmap.createBitmap(src.cols(), src.rows(), Bitmap.Config.ARGB_8888);
        Mat result = new Mat();
        Imgproc.cvtColor(dst2, result, Imgproc.COLOR_BGR2RGBA);
        Utils.matToBitmap(result, bm);

        // show
        ImageView iv = (ImageView)this.findViewById(R.id.chapter3_imageView);
        iv.setImageBitmap(bm);
    }
  • 上述代碼中戚长,b表示亮度參數(shù)盗冷,c表示對(duì)比度參數(shù)
    其中同廉,
    b的取值為負(fù)數(shù)時(shí)仪糖,表示調(diào)低亮度;為正數(shù)時(shí)迫肖,表示調(diào)高亮度锅劝;
    c的取值是浮點(diǎn)數(shù),使用經(jīng)驗(yàn)值范圍一般為0~3.0咒程,
    c的取值小于1時(shí)鸠天,表示降低對(duì)比度大于1時(shí)表示提升對(duì)比度帐姻。





4. 基于權(quán)重的圖像疊加

  • 對(duì)圖像進(jìn)行簡單的相加方法有時(shí)候并不能滿足需要稠集,
    這時(shí)可以通過參數(shù)調(diào)整輸入圖像最終疊加之后的圖像中所占的權(quán)重比
    以實(shí)現(xiàn)基于權(quán)重方式的饥瓷、更加靈活的圖像調(diào)整方法剥纷。

Core模塊中已經(jīng)實(shí)現(xiàn)了這樣的API函數(shù),方法名稱與各個(gè)參數(shù)的解釋具體如下:

  • addWeighted(Mat src1, double alpha, Mat src2, double beta, double gamma, Mat dst)
    src1:表示輸入第一個(gè)Mat對(duì)象呢铆。
    alpha:表示混合時(shí)候第一個(gè)Mat對(duì)象所占的權(quán)重大小晦鞋。
    src2:表示輸入第二個(gè)Mat對(duì)象。
    beta:表示混合時(shí)候第二個(gè)Mat對(duì)象所占的權(quán)重大小棺克。
    gamma:表示混合之后是否進(jìn)行亮度校正(提升或降低)悠垛。
    dst:表示輸出權(quán)重疊加之后的Mat對(duì)象。

  • 最常見的情況下娜谊,
    在進(jìn)行兩個(gè)圖像疊加的時(shí)候确买,權(quán)重調(diào)整需要滿足的條件為alpha + beta = 1.0,通常alpha = beta = 0.5纱皆,
    表示混合疊加后的圖像中原來兩副圖像的像素比值各占一半湾趾,這些都是對(duì)于正常圖像來說的芭商。

  • 假設(shè)src2全黑色背景圖像
    那么這種疊加效果就是讓圖像src1變得更加搀缠,對(duì)比度變得更加铛楣;
    在src2為黑色背景圖像時(shí),我們把alpha值調(diào)整為1.5艺普,beta值為-0.5簸州,
    這樣最終的疊加結(jié)果就是圖像的對(duì)比度得到了提升
    當(dāng)alpha=1時(shí)候衷敌,則輸出原圖勿侯。

  • 如果gamma不是默認(rèn)值0,而是一個(gè)正整數(shù)的時(shí)候缴罗,那么這時(shí)就會(huì)提升圖像的亮度助琐,
    所以這種方式就成為調(diào)整圖像亮度與對(duì)比度另外一種方式,而且它比上一節(jié)中提到的方法更簡潔面氓、實(shí)用兵钮,只需一次調(diào)用就可以得到圖像亮度與對(duì)比度調(diào)整后輸出的圖像
    這種方法的公式化描述如下:

dst=src1*alpha+src2*beta+gamma

其中舌界,
如果src2是純黑色的背景圖像掘譬,
gamma大小決定了圖像的亮度
alpha大小決定了圖像的對(duì)比度(因?yàn)閟rc2純黑色背景則基本無對(duì)比度呻拌,所以該由src1決定得多)戒洼,
alpha+beta=1狂秦。

基于權(quán)重疊加的圖像亮度與對(duì)比度調(diào)整的完整代碼實(shí)現(xiàn)如下:

    public void blendMat(double alpha, double gamma) {
        // 加載圖像
        Mat src = Imgcodecs.imread(fileUri.getPath());
        if(src.empty()){
            return;
        }

        // create black image
        Mat black = Mat.zeros(src.size(), src.type());
        Mat dst = new Mat();

        // 像素混合 - 基于權(quán)重
        Core.addWeighted(src, alpha, black, 1.0-alpha, gamma, dst);

        // 轉(zhuǎn)換為Bitmap,顯示
        Bitmap bm = Bitmap.createBitmap(src.cols(), src.rows(), Bitmap.Config.ARGB_8888);
        Mat result = new Mat();
        Imgproc.cvtColor(dst, result, Imgproc.COLOR_BGR2RGBA);
        Utils.matToBitmap(result, bm);

        // show
        ImageView iv = (ImageView)this.findViewById(R.id.chapter3_imageView);
        iv.setImageBitmap(bm);

    }

其中,
兩個(gè)參數(shù)alpha和gamma分別表示對(duì)比度與亮度調(diào)整的幅度污它,這里的默認(rèn)值分別為1.530绎速。
完整代碼可以參考文末作者的GitHub虾啦;





5. Mat的其他各種像素操作

OpenCV除了支持圖像的算術(shù)操作之外锰扶,還支持圖像的邏輯操作、平方初家、取LOG偎窘、歸一化值范圍等操作,
這些操作在處理復(fù)雜場景的圖像二值或者灰度圖像分析的時(shí)候非常有用溜在。

圖像邏輯操作相關(guān)的API與參數(shù)說明具體如下:

  • bitwise_not(Mat src, Mat dst) // 取反操作
    src:輸入圖像陌知。
    dst:取反之后的圖像掖肋。
    取反操作對(duì)二值圖像來說是一個(gè)常見操作
    有時(shí)候我們需要先進(jìn)行取反操作浙芙,然后再對(duì)圖像進(jìn)行更好地分析籽腕。

  • bitwise_and(Mat src1, Mat src2, Mat dst) // 與操作
    src:輸入圖像一。
    src2:輸入圖像二南窗。
    dst:與操作結(jié)果万伤。
    與操作對(duì)兩張圖像混合之后的輸出圖像降低混合圖像亮度的效果呜袁,
    會(huì)讓輸出的像素小于等于對(duì)應(yīng)位置的任意一張輸入圖像的像素值敌买。

(因唯兩個(gè)高值像素相與得高值像素,
高值與低值阶界、低值與低值的結(jié)果都是低值虹钮,
于是三分之二的運(yùn)算都是降低亮度的操作)

  • bitwise_or(Mat src1, Mat src2, Mat dst) // 或操作
    src1:輸入圖像一。
    src2:輸入圖像二膘融。
    dst:或操作結(jié)果芙粱。
    或操作對(duì)兩張圖像混合之后的輸出圖像強(qiáng)化混合圖像亮度的效果,
    會(huì)讓輸出的像素大于等于對(duì)應(yīng)位置的任意一張輸入圖像的像素值氧映。

(其理解同與操作相反)

  • bitwise_xor(Mat src1, Mat src2, Mat dst) // 異或操作
    src1:輸入圖像一春畔。
    src2:輸入圖像二。
    dst:或操作結(jié)果岛都。

異或操作可以看作是對(duì)輸入圖像的疊加取反效果律姨。

下面創(chuàng)建兩個(gè)Mat對(duì)象,
然后對(duì)它們完成位運(yùn)算——邏輯與疗绣、或线召、非,
得到的結(jié)果將拼接為一張大Mat對(duì)象顯示多矮,
完整的代碼演示如下:

// 創(chuàng)建圖像
Mat src1 = Mat.zeros(400, 400, CvType.CV_8UC3);
Mat src2 = new Mat(400, 400, CvType.CV_8UC3);
src2.setTo(new Scalar(255, 255, 255));

// ROI區(qū)域定義
Rect rect = new Rect();
rect.x=100;
rect.y=100;
rect.width = 200;
rect.height = 200;

// 繪制矩形
Imgproc.rectangle(src1, rect.tl(), rect.br(), new Scalar(0, 255, 0), -1);
rect.x=10;
rect.y=10;
Imgproc.rectangle(src2, rect.tl(), rect.br(), new Scalar(255, 255, 0), -1);

// 邏輯運(yùn)算
Mat dst1 = new Mat();
Mat dst2 = new Mat();
Mat dst3 = new Mat();
Core.bitwise_and(src1, src2, dst1);
Core.bitwise_or(src1, src2, dst2);
Core.bitwise_xor(src1, src2, dst3);

// 輸出結(jié)果
Mat dst = Mat.zeros(400, 1200, CvType.CV_8UC3);
rect.x=0;
rect.y=0;
rect.width=400;
rect.height=400;
dst1.copyTo(dst.submat(rect));
rect.x=400;
dst2.copyTo(dst.submat(rect));
rect.x=800;
dst3.copyTo(dst.submat(rect));

// 釋放內(nèi)存
dst1.release();
dst2.release();
dst3.release();

// 轉(zhuǎn)換為Bitmap缓淹,顯示
Bitmap bm = Bitmap.createBitmap(dst.cols(), dst.rows(), Bitmap.Config.ARGB_8888);
Mat result = new Mat();
Imgproc.cvtColor(dst, result, Imgproc.COLOR_BGR2RGBA);
Utils.matToBitmap(result, bm);

// show
ImageView iv = (ImageView)this.findViewById(R.id.chapter3_imageView);
iv.setImageBitmap(bm);

如上代碼前文字所述,
三個(gè)輸出圖像分別以x = 0, 400, 800為Mat矩陣左上角點(diǎn)拼接到結(jié)果Mat矩陣dst中:

  • 除了邏輯操作之外塔逃,
    還有兩個(gè)重要且常見的像素操作是歸一化線性絕對(duì)值放縮變換讯壶,
    其中歸一化是把數(shù)據(jù)re-scale到指定的范圍內(nèi),
    線性絕對(duì)值放縮是把任意范圍的像素值變化到0~255的CV_8U的圖像像素值湾盗。

相關(guān)API解釋如下:

  • convertScaleAbs(Mat src, Mat dst) //線性絕對(duì)值放縮變換
    src:表示輸入圖像伏蚊。
    dst:表示輸出圖像躏吊。
    默認(rèn)情況下會(huì)對(duì)輸入Mat對(duì)象數(shù)據(jù)求得絕對(duì)值胜卤,并將其轉(zhuǎn)換為CV_8UC1類型輸出數(shù)據(jù)dst葛躏。
  • normalize(Mat src, Mat dst, double alpha, double beta, int norm_type, int dtype, Mat mask)
    src:表示輸入圖像。
    dst:表示輸出圖像摩窃。
    alpha:表示歸一化到指定范圍的低值。
    beta:表示歸一化到指定范圍的高值匪蟀。
    dtype:表示輸出的dst圖像類型材彪,默認(rèn)為-1,表示類型與輸入圖像src相同显熏。
    mask:表示遮罩層喘蟆,默認(rèn)為Mat類型。
  • 歸一化在圖像處理中是經(jīng)常需要用到的方法橙弱,
    比如對(duì)浮點(diǎn)數(shù)進(jìn)行計(jì)算得到輸出數(shù)據(jù)斜筐,
    將數(shù)據(jù)歸一化到0~255后就可以作為彩色圖像輸出,得到輸出結(jié)果。

(數(shù)據(jù)   只要經(jīng)過   歸一化   就可以變成   彩色圖像  輸出俘闯,劃重點(diǎn)U胬省!F炱恕M畏馈!V略铩)

下面簡單演示一下如何創(chuàng)建一個(gè)0~1的浮點(diǎn)數(shù)圖像嫌蚤,
然后將其歸一化到0~255搬葬,
代碼實(shí)現(xiàn)如下:

    public void normAndAbs() {
        // 創(chuàng)建隨機(jī)浮點(diǎn)數(shù)圖像
        Mat src = Mat.zeros(400, 400, CvType.CV_32FC3);
        float[] data = new float[400*400*3];
        Random random = new Random();
        for(int i=0; i<data.length; i++) {
            data[i] = (float)random.nextGaussian();
        }
        src.put(0, 0, data);

        // 歸一化值到0~255之間
        Mat dst = new Mat();
        Core.normalize(src, dst, 0, 255, Core.NORM_MINMAX, -1, new Mat());


        // 類型轉(zhuǎn)換
        Mat dst8u = new Mat();
        dst.convertTo(dst8u, CvType.CV_8UC3);

        // 轉(zhuǎn)換為Bitmap,顯示
        Bitmap bm = Bitmap.createBitmap(dst.cols(), dst.rows(), Bitmap.Config.ARGB_8888);
        Mat result = new Mat();
        Imgproc.cvtColor(dst8u, result, Imgproc.COLOR_BGR2RGBA);
        Utils.matToBitmap(result, bm);

        // show
        ImageView iv = (ImageView)this.findViewById(R.id.chapter3_imageView);
        iv.setImageBitmap(bm);
    }

上述代碼將創(chuàng)建一張大小為400×400高斯噪聲圖像疾忍,
其中歸一化方法選擇的是最小與最大值歸一化方法(NORM_MINMAX=32)一罩,
這種方法的數(shù)學(xué)表示如下:

  • 圖解:
    如圖所示四瘫,( x - min / max - min )必然是一個(gè)[0,1]的實(shí)數(shù)找蜜!

  • 另外弓叛,
    你會(huì)發(fā)現(xiàn)公式中撰筷,不加alpha對(duì)( 0 , 255 )這個(gè)范圍的歸一(即以上題境)沒有什么影響,
    這是因?yàn)?code>( x - min / max - min )光乘以(beta - alpha)不加最后的alpha只能歸一到范圍( 0 影钉, beta )平委;
    加上 最后的alpha才能歸一到( alpha , beta )蜡塌;

其中馏艾,
x表示src的像素值,
min房资、max表示src中像素的最小值與最大值岖沛,
對(duì) src 各個(gè)通道完成上述計(jì)算即可得到最終的歸一化結(jié)果婴削。

計(jì)算圖像的結(jié)果有正負(fù)值期升,那么在顯示之前會(huì)調(diào)用convertScaleAbs()對(duì)負(fù)值求取絕對(duì)值圖像播赁,
在后面的圖像濾波與梯度計(jì)算中會(huì)用到該方法乓序。

此外,Core中圖像常見的操作還有對(duì)Mat做平方與取對(duì)數(shù)陨献,這些操作都與實(shí)際應(yīng)用場合有一定的關(guān)系,而且使用與參數(shù)都比較簡單龄捡,書中這里沒再做過多的說明聘殖。

關(guān)于相關(guān)API的更多說明,我們可以查看對(duì)應(yīng)的OpenCV幫助文檔坠宴。

參考資料
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子角骤,更是在濱河造成了極大的恐慌,老刑警劉巖蝉揍,帶你破解...
    沈念sama閱讀 216,843評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異励饵,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)禾嫉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門孽椰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來黍匾,“玉大人磕诊,你說我怎么就攤上這事∩恚” “怎么了莱褒?”我有些...
    開封第一講書人閱讀 163,187評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長涎劈。 經(jīng)常有香客問我广凸,道長,這世上最難降的妖魔是什么蛛枚? 我笑而不...
    開封第一講書人閱讀 58,264評(píng)論 1 292
  • 正文 為了忘掉前任谅海,我火速辦了婚禮坤候,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己植影,他們只是感情好惶我,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,289評(píng)論 6 390
  • 文/花漫 我一把揭開白布叉跛。 她就那樣靜靜地躺著爬骤,像睡著了一般惰爬。 火紅的嫁衣襯著肌膚如雪丛版。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,231評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼信柿。 笑死霞幅,一個(gè)胖子當(dāng)著我的面吹牛铅鲤,可吹牛的內(nèi)容都是我干的款违。 我是一名探鬼主播当窗,決...
    沈念sama閱讀 40,116評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼戳寸,長吁一口氣:“原來是場噩夢啊……” “哼捞奕!你這毒婦竟也來了墩邀?” 一聲冷哼從身側(cè)響起竹海,我...
    開封第一講書人閱讀 38,945評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體财搁,經(jīng)...
    沈念sama閱讀 45,367評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡隆檀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,581評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了粹湃。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恐仑。...
    茶點(diǎn)故事閱讀 39,754評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖为鳄,靈堂內(nèi)的尸體忽然破棺而出裳仆,到底是詐尸還是另有隱情,我是刑警寧澤孤钦,帶...
    沈念sama閱讀 35,458評(píng)論 5 344
  • 正文 年R本政府宣布歧斟,位于F島的核電站,受9級(jí)特大地震影響偏形,放射性物質(zhì)發(fā)生泄漏静袖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,068評(píng)論 3 327
  • 文/蒙蒙 一俊扭、第九天 我趴在偏房一處隱蔽的房頂上張望队橙。 院中可真熱鬧,春花似錦、人聲如沸捐康。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽解总。三九已至贮匕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間花枫,已是汗流浹背刻盐。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留劳翰,地道東北人隙疚。 一個(gè)月前我還...
    沈念sama閱讀 47,797評(píng)論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像磕道,于是被迫代替她去往敵國和親供屉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,654評(píng)論 2 354

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