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;
//返回1行3列的地址(索引從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
效果:
上面的代碼很簡單,所以這裏不給出解釋,看註釋完完全全能夠懂.
還有內容,稍後更新……