簡單圖像疊加[轉載]

1.圖像本質上面是由數值組成的矩陣。矩陣中的一個元素對應一個像素。
2.對於灰度圖像(黑白圖像),像素是8位無符號數(CV_8U)。0表示黑色,255表示白色。對於彩色圖像,是用三原色數據合成彩色。3個8位(CV_8UC3)的數值組成矩陣的一個元素。而且順序是BGR
3.一般來說8位的通道夠用了。但是有些特殊的需要16位。
4.經驗之談:矩陣可以有很多種類型,但是大部分操作可以使用任何類型的矩陣來完成。但是還是有一些操作必須使用特性的類型或者特定的通道數量。有時候留個心積累那些圖用什麼矩陣來處理。

下面廢話不多說,先來一個啓發性的例子(可以暫時不用知道其中全部的細節,稍後會講到這些東西)
代碼:
這裏寫圖片描述
結果:
在原來的圖片上面生成了很多很多的點。
這裏寫圖片描述
上面這段代碼的原理很簡單:就是隨機生成座標值,然後把這些座標處的灰度值改爲255(白色).
大概有這些就已經算是一個訪問像素的完整過程了.下面就詳細講一些訪問像素的細節.

二.訪問像素的三種方法

1.at方法(cv::Mat::at(…..)).

at方法顧名思義,就是在某個位置.其實用at可以直接訪問到某個位置的像素.在OpenCV中,at方法爲一個模板方法且有很多的變種,下面只講最基本常用的兩種方法.(分別是傳入座標傳入點的方法)
at方法是一個模板函數,在官方文檔中抽取最簡單的寫法:

at <類型> (行,列) [通道(如果有通道的話)]
  • 1

at<類型>(行,列)就能夠訪問到一幅圖片中的一個像素了,每個像素的chanel用[]來提取.是不是很簡單.
因爲這是模板方法,選擇類型成了重要的一步,而且類型的選擇是與圖片元素的類型要對應起來,at方法不負責轉化類型.下面給出一個詳細的類型對應表.(要是現在不知道什麼是圖像元素的類型.那麼點擊下面的鏈接轉到之前的core組件,有詳細的類型介紹.)
http://blog.csdn.net/xierhacker/article/details/52457907
首先,OpenCV中有一個基本的向量模板類,一些基本的”N個元素”向量可以由這個模板類來定義,簡單地可以寫爲cv::Vec

uchar類型(分別爲2元素,3元素,4元素):
typedef Vec<uchar, 2> cv::Vec2b     typedef Vec<uchar, 3> cv::Vec3b typedef Vec<uchar, 4> cv::Vec4b

Short類型
typedef Vec<short, 2> cv::Vec2s     typedef Vec<ushort, 2> cv::Vec2w
typedef Vec<short, 3> cv::Vec3s     typedef Vec<ushort, 3> cv::Vec3w
typedef Vec<short, 4> cv::Vec4s     typedef Vec<ushort, 4> cv::Vec4w

Int類型(同上):
typedef Vec<int, 2> cv::Vec2i       typedef Vec<int, 3> cv::Vec3i
typedef Vec<int, 4> cv::Vec4i       typedef Vec<int, 6> cv::Vec6i
typedef Vec<int, 8> cv::Vec8i

float類型:
typedef Vec<float, 2> cv::Vec2f     typedef Vec<float, 3> cv::Vec3f typedef Vec<float, 4> cv::Vec4f     typedef Vec<float, 6> cv::Vec6f


double類型:
typedef Vec<double, 2> cv::Vec2d        typedef Vec<double, 3> cv::Vec3d
typedef Vec<double, 4> cv::Vec4d        typedef Vec<double, 6> cv::Vec6d
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

由此,可以得到一個常用的訪問像素的時候模板中放類型的表:
像素類型(模板傳入關鍵字):

CV_8U(uchar)  
CV_8UC1 (uchar)  CV_8UC2 (Vec2b)  CV_8UC3 (Vec3b)  CV_8UC4(Vec4b) 

CV_8S(char)   
CV_8SC1 (1通道)  CV_8SC2 (2通道)   CV_8SC3 (3通道)  CV_8SC4 (4通道)   

CV_16U  (ushort)
CV_16UC1 (ushort)  CV_16UC2 (Vec2w)   CV_16UC3 (Vec3w)   CV_16UC4 (Vec4w)   

CV_16S  (short)
CV_16SC1(short)   CV_16SC2(Vec2s)   CV_16SC3(Vec3s)   CV_16SC4(Vec4s)   

CV_32S (int)
CV_32SC1(int)   CV_32SC2(Vec2i)    CV_32SC3(Vec3i)  CV_32SC4(Vec4i)   

CV_32F (float)
CV_32FC1(float)   CV_32FC2(Vec2f)  CV_32FC3(Vec3f)   CV_32FC4(Vec4f)  

CV_64F(double)  
CV_64FC1(double)   CV_64FC2(Vec2d)  CV_64FC3(Vec3d)  CV_64FC4(Vec4d) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

現在再來看上面的那個加入白色噪點的例子,是不是豁然開朗,知道是怎麼用的了.
一個改變某點像素來畫線的例子:
代碼:

#include <iostream>
#include <cmath>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>

int main()
{
    cv::Mat pic1(300, 300, CV_8UC3, cv::Scalar(255, 0, 0));
    cv::Mat pic2(10, 3, CV_32F, 20.3);

    //訪問像素
    //pic1上面畫出一條直線
    for (int i = 0; i <300; i++)
    {
        int j = i; 
        //訪問像素改變顏色CV_8UC3對應的就是Vec3b.
        pic1.at<cv::Vec3b>(i, j)[0] = 0;
        pic1.at<cv::Vec3b>(i, j)[2] = 255;

    }
    cv::imshow("test", pic1);
    cv::waitKey(0);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

結果:
這裏寫圖片描述
代碼的意義很容易懂,就是建立一個80*80的圖片,初始化爲藍色,然後根據一個直線方程把某點的顏色改爲紅色,那麼最終就得到了一條紅色的直線.

2.指針(cv::Mat::ptr(….))

http://docs.opencv.org/3.1.0/d3/d63/classcv_1_1Mat.html#a5a9ffc908ac90604f36a8b6a1038747d
用指針訪問的話,OpcnCV提供了一個方法,cv::Mat::ptr()。下面是這個方法的幾種常用的定義。
定義一:

_Tp* cv::Mat::ptr (int  i0 = 0)     
    返回mat的某行的一個地址,地址的類型與你之前在mat中選擇的類型有關(模板函數),因此,要非常注意選擇正確的返回以及模板參數的類型.
    i0代表0軸,或者通俗一點理解就是矩陣的一行.(索引是從0開始,要小心)
  • 1
  • 2
  • 3

定義二:

_Tp* cv::Mat::ptr ( int  i0,int  i1 )
    返回mat某個位置元素的地址,還是老話,地址的類型與你之前在mat中選擇的類型有關(模板函數),因此,要非常注意選擇正確的返回以及模板參數的類型.
    i0代表0軸,或者通俗一點理解就是矩陣的一行.(索引是從0開始,要小心)
    I1代表1軸,或者通俗一點理解就是矩陣的一列,(索引是從0開始,要小心)
  • 1
  • 2
  • 3
  • 4

例一(只有一個通道):
代碼:

#include <iostream>
#include <cmath>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>

int main()
{
    cv::Mat pic2(10, 3, CV_32F, 20.3);
    //每行元素數量
    int numOfRow = pic2.cols;

    //訪問像素
    for (int row = 0; row < pic2.rows; row++)
    {
        //獲得該行的地址
        float *data = pic2.ptr<float>(row);
        //訪問該行元素
        for (int col = 0; col < numOfRow; col++)
        {
            std::cout << data[col] << " ";
        }

        std::cout << std::endl;
    }
    cv::imshow("test", pic2);
    cv::waitKey(0);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

結果:
這裏寫圖片描述
這裏的Mat中的數據類型選擇的是CV_32F,是float的單通道類型.選擇這種類型就是要展示接下來的指針的模板中應該選擇的參數.

例二(多個通道):
代碼:

#include <iostream>
#include <cmath>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>

int main()
{
    cv::Mat pic1(10, 3, CV_8UC3, cv::Scalar(255, 0, 0));
//  cv::Mat pic2(10, 3, CV_32F, 20.3);
    //每行元素數量
    int numOfRow = pic1.cols;

    //訪問像素
    for (int row = 0; row < pic1.rows; row++)
    {
        //獲得該行的地址
        cv::Vec3b *data = pic1.ptr<cv::Vec3b>(row);
        //訪問該行元素
        for (int col = 0; col < numOfRow; col++)
        {
            data[col][0] = 0;
            data[col][2] = 255;
        }

        //std::cout << std::endl;
    }
    std::cout << cv::format(pic1, cv::Formatter::FMT_PYTHON) << std::endl;
    cv::imshow("test", pic1);
    cv::waitKey(0);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

結果:
指針的方式改變像素通道的值

例三(兩個索引):
代碼:

#include <iostream>
#include <cmath>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>

int main()
{
    cv::Mat pic1(10, 3, CV_8UC3, cv::Scalar(255, 0, 0));
//  cv::Mat pic2(10, 3, CV_32F, 20.3);
    //每行元素數量
    int numOfRow = pic1.cols;

    //返回13列的地址(索引從0開始)
    cv::Vec3b *pixel = pic1.ptr<cv::Vec3b>(0, 2);
    //0通道改爲128
    pixel[0] = 128;
    //輸出
    std::cout << *pixel << std::endl;
    cv::imshow("test", pic1);
    cv::waitKey(0);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

結果:
這裏寫圖片描述
也就是說,可以同時使用兩個索引來得到一個具體位置的地址.

三.感興趣區域(Region Of Interest,ROI)

有時候我們並不想在一整張圖片上面做文章..我們只想選擇某一個小區域上面完成一些操作.
OpenCV能夠讓我們僅僅選擇一些子區域,並且把這個子區域當做普通的圖像來操作.這就引出了感興趣區域(ROI)這個話題.
使用ROI通常可以減少處理時間,增加精度,因此是一個必須要掌握的”技能”.
定義ROI區域方式:

方式一:使用矩形(Rect)類

Mat  ROI;
ROI=image(Rect_ (_Tp _x, _Tp _y, _Tp _width, _Tp _height));
    x,y這兩個參數就是矩形區域左上角的座標
    width,height這兩個參數就是矩形局域的寬和高
  • 1
  • 2
  • 3
  • 4

(Rect類不熟悉的話:轉到之前的core組件:http://blog.csdn.net/xierhacker/article/details/52457907)

方式二:手動指定感興趣的行和列的範圍

Mat  ROI;
ROI=image(Rect_ (range(row_start,row_end),range(col_strat,col_end)));
    range(row_start,row_end):行的開始和結束
    range(col_strat,col_end):列的開始和結束
  • 1
  • 2
  • 3
  • 4

說了這麼多,就要講具體怎麼用了,下面圖像運算的第一個實例圖像疊加就使用了ROI的概念,可以看下實例是怎麼用的.

四.簡單圖像運算

首先,標題是簡單圖像運算,是因爲接下來的例子都是很簡單很基礎的.但是也是很綜合的,綜合使用了之前接觸到的一些編程以及理論知識.
這部分有必要消化,因爲這些實例中包含了一些很基本的概念,這些概念會在這些例子中很形象的展示出來.比死記硬背一些理論好多了.

Ⅰ圖像疊加

圖像疊加是一個很基本的例子,通過這個例子可以聯繫ROI的使用.
我們這裏有兩個圖片,一個是主圖片如下
這裏寫圖片描述
一個是很小的logo如下.
這裏寫圖片描述
任務就是要將logo疊加到主圖片上面去.不囉嗦了,上代碼

代碼:

#include <iostream>
#include <cmath>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>

int main()
{
    //讀主圖片
    cv::Mat image = cv::imread("1.jpg");
    //讀logo
    cv::Mat logo = cv::imread("logo.png");
    //在主圖片上面定義"感興趣"區域
    cv::Mat ROI = image(cv::Rect(200, 200, logo.cols, logo.rows));
    //logo複製到感興趣區域
    logo.copyTo(ROI);

    cv::imshow("test", image);
    cv::waitKey(0);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

效果:
這裏寫圖片描述
上面的代碼很簡單,所以這裏不給出解釋,看註釋完完全全能夠懂.

還有內容,稍後更新……

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章