創(chuàng)建 cv::Mat
OpenCV 中用數(shù)據(jù)格式 cv::Mat
存儲(chǔ)圖片睬棚。
有以下幾種生成 cv::Mat
的方式:
直接或間接指定圖片的行脚线、列維度
cv::Mat myMat(240, 320, CV_8U, cv::Scalar(255)); // 240 行 × 320 列碧浊,每個(gè)元素的類型為 8 位無符號(hào)整型欧漱,單通道库继,元素初值為 255
// 也可以將 Scalar(255) 簡(jiǎn)寫成 255退唠,但是注意 Scalar(0) 不能直接寫成 0
cv::Mat myMat(240, 320, CV_8U, 255);
// 圖片大小也可以用 Size() 的格式送入
cv::Mat myMat(cv::Size(320 240), CV_8U, cv::Scalar(255)); // 以 (width, height) 的形式送入鹃锈,Size(320, 240) 就是寬320,高 240瞧预,對(duì)應(yīng)了 240 行屎债,320 列。
// 也可以通過 size() 函數(shù)得到某個(gè)圖片的大小垢油,然后構(gòu)造同樣大小的圖片
cv::Mat myMat(image1.size(), CV_8U, cv::Scalar(255));
OpenCV 有一套自己的數(shù)據(jù)類型名稱盆驹,與標(biāo)準(zhǔn)的 C++ 數(shù)據(jù)類型對(duì)應(yīng)關(guān)系如下:
- CV_8U -> unsigned char (min = 0, max = 255)
- CV_8S -> char (min = -128, max = 127)
- CV_16U -> unsigned short (min = 0, max = 65535)
- CV_16S -> short (min = -32768, max = 32767)
- CV_32S -> int (min = -2147483648, max = 2147483647)
- CV_32F -> float
- CV_64F -> double
另外,在數(shù)據(jù)類型中可以添加通道數(shù)滩愁,例如 CV_8UC3 表示 3 通道的 8 位無符號(hào)整型躯喇。
用 create( ) 函數(shù)對(duì)原 Mat 重新賦值
image1.create(200, 200, CV_8U);
image1 = 255;
出于性能方面的考慮,如果新的尺寸和類型與原來相同硝枉,則不會(huì)重新分配內(nèi)存空間廉丽。
Mat 類型數(shù)據(jù)復(fù)制: copyTo( ) 和 clone( )
在復(fù)制 Mat 類型的數(shù)據(jù)時(shí),要注意復(fù)制前后變量指向同一數(shù)據(jù)還是指向完全分離的數(shù)據(jù)妻味。
cv::Mat image2(image1); // 方式 1
cv::Mat image3 = image1; // 方式 2
上述兩種方式復(fù)制得到變量 image2 和 image3 與 image1 指向相同的數(shù)據(jù)正压。這種形式的復(fù)制也被稱為淺復(fù)制 shallow copy,可以節(jié)省內(nèi)存空間责球,但這種數(shù)據(jù)關(guān)聯(lián)也可能導(dǎo)致意想不到的錯(cuò)誤焦履,修改其中一個(gè),其他的也都會(huì)改變雏逾。例如嘉裤,下邊這個(gè) Test 類,其中的 method() 函數(shù)返回一個(gè) Mat 數(shù)據(jù)類型變量 img栖博,由于內(nèi)存共享的問題价脾,如果一個(gè)對(duì)象修改了 img 數(shù)據(jù),那么Test 類和它生成的所有對(duì)象也會(huì)被修改笛匙,這顯然違背了面向?qū)ο缶幊讨械姆庋b原則侨把。因此,不建議在類中返回 Mat 類型變量妹孙。如果需要的話秋柄,可以用后邊的深復(fù)制函數(shù)返回變量的一個(gè)獨(dú)立的復(fù)制。
class Test{
cv::Mat img;
public:
Test(): img(240, 320, CV_8U, 100){ }
cv::Mat method() {return img;}
}
如果要得到完全分離的數(shù)據(jù)蠢正,應(yīng)該用 copyTo() 或 clone() 這兩個(gè)深復(fù)制函數(shù)明確指出:
image1.copyTo(image2); // 方式 1
cv::Mat image2 = image1.clone(); // 方式 2
Mat 數(shù)據(jù)轉(zhuǎn)換: convertTo( )
image1.convertTo(image4,
CV_32F, // 數(shù)據(jù)類型轉(zhuǎn)化成浮點(diǎn)型
1/255.0, // 縮放因子
1.0); // 偏移量骇笔。最終結(jié)果就是 x/255.0 + 1.0
OpenCV 在顯示圖像的時(shí)候,可以接受整型的數(shù)據(jù)類型,取值范圍 0 ~ 255笨触,也可以接受浮點(diǎn)型的數(shù)據(jù)類型懦傍,取值范圍 0.0 ~ 1.0. 因此當(dāng)把整型數(shù)據(jù)轉(zhuǎn)成浮點(diǎn)型時(shí),要考慮是否需要數(shù)據(jù)范圍的縮放芦劣。例如粗俱,下面左側(cè)原圖為 CV_8UC3 類型,用 convertTo() 函數(shù)轉(zhuǎn)換成浮點(diǎn)型 CV_32FC3虚吟,如果數(shù)值縮放因子設(shè)置為 1寸认,即不縮放,那么轉(zhuǎn)化之后超過 1.0 的數(shù)據(jù)均被截?cái)啻浚D(zhuǎn)化后的圖像呈現(xiàn)很多白色區(qū)域偏塞,這顯然不是我們想要的效果。
如果 Mat 元素很少且已知邦鲫,例如輸入相機(jī)的內(nèi)參矩陣灸叼,就可以在初始化時(shí)直接輸入
cv::Mat K = (cv::Mat_<double>(3,3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
讀入圖片
- 原樣讀取,不特別設(shè)定為灰度圖或者彩色圖像庆捺,把第二個(gè)參數(shù)設(shè)定為負(fù)數(shù)即可古今,例如:
cv::Mat image = cv::imread(“image.jpg”, -1);
- 讀取為灰度圖:
cv::Mat image = cv::imread("image.jpg", cv::IMREAD_GRAYSCALE);
// 或者寫對(duì)應(yīng)的編號(hào)
cv::Mat image = cv::imread("image.jpg", 0);
- 讀取為彩色圖:第二個(gè)參數(shù)設(shè)定為正數(shù),或者設(shè)定為
cv::IMREAD_COLOR
第二個(gè)參數(shù)取值總結(jié):
waitKey( ) 等待
一般與 imshow() 連用蚁堤,效果是暫停處理醉者,讓圖片顯示一段時(shí)間。如果不加 waitKey()披诗,就不會(huì)顯示圖片撬即。
cv::waitKey( int delay=0)
- delay <= 0,無限等待
- delay >0 呈队,等待 delay 時(shí)間剥槐,單位為 ms
如果在等待期間有按鍵輸入,則返回值為按鍵對(duì)應(yīng)的編碼宪摧;如果等待期間沒有按鍵粒竖,則返回 -1.
整行、整列賦值
image.row(2).setTo(cv::Scalar(100));
image.col(2).setTo(cv::Scalar(100)); // 以上兩個(gè)命令針對(duì)灰度圖/單通道
image.col(2).setTo(cv::Scalar(0, 20, 34)); //彩色几于,OpenCV 中顏色通道默認(rèn)順序?yàn)?B蕊苗、G、R
// 把 src 矩陣的第 i 行 賦值給 dst 矩陣的第 j 行
src.row(i).copyTo(dst.row(j));
查找最小值沿彭、最大值及其位置
cv::minMaxLoc(src, double* minVal, double* maxVal=0,
Point* minLoc=0, Point* maxLoc=0,
InputArray mask=noArray())
- src:輸入的圖像朽砰。
- minVal:最小值,可輸入NULL表示不需要。
- maxVal :最大值瞧柔,可輸入NULL表示不需要漆弄。
- minLoc:最小值的位置,可輸入NULL表示不需要造锅,Point類型撼唾。
- maxLoc:最大值的位置,可輸入NULL表示不需要备绽,Point類型券坞。
- mask:選定在哪些區(qū)域進(jìn)行搜索。noArray() 表示沒有設(shè)置 mask
如果只需要得到最小值和最大值肺素,而不需要位置:
double my_min, my_max;
cv::minMaxLoc(input, &my_min, &my_max); // 只需要送入變量地址即可
像素運(yùn)算之后截?cái)?/h5>
當(dāng)涉及到像素值之間的加恨锚、減時(shí),為了得到有意義的結(jié)果倍靡,一般要進(jìn)行截?cái)嗖僮鳌?br> 比如取值在 0~255 的像素猴伶,當(dāng)運(yùn)算結(jié)果 > 255 時(shí),取為 255塌西;當(dāng)結(jié)果 < 0 時(shí)他挎,取 0.
result = cv::saturate_cast<uchar>(some expression)
實(shí)際上,很多 cv 的函數(shù)默認(rèn)內(nèi)置了 saturate_cast 操作捡需,比如下邊提到的 cv::add
和重載的 +
運(yùn)算办桨。
重載運(yùn)算符
比較常用的重載有
- 加、減
- 函數(shù) min, max, abs
- 比較運(yùn)算符 <, <=, ==, !=, >, >=站辉,返回 8 位的二進(jìn)制值呢撞。
- 矩陣運(yùn)算:乘法、求逆 image.inv()饰剥、轉(zhuǎn)置 image.t()殊霞、行列式 image.determinant()、范數(shù) image.norm()汰蓉、叉乘 v1.cross(v2)绷蹲、點(diǎn)乘 v1.dot(v2)。 這些運(yùn)算都不是 in place 的顾孽,即不改變?cè)瓟?shù)據(jù)祝钢。
- 組合運(yùn)算符,如 +=
opencv 中的隨機(jī)數(shù)
cv::RNG rng;
rng.uniform(0,255); // 生成范圍內(nèi)的隨機(jī)數(shù)
兩圖像疊加
cv::add(image1, image2, result)
//或者加權(quán)形式
cv::addWeighted(image1, weight1, image2, weight2, const, result)
OpenCV 中已經(jīng)重載了 +
運(yùn)算符若厚,因此可以直接這樣:
result = weight1 * image1 + weight2 * image2 + const
圖像通道拆分與合并
將三通道拆分出來太颤,存到 vector 中
std::vector<cv::Mat> planes;
cv::split(image1, planes);
這里用的是 c++ 模板中的 vector 數(shù)據(jù)類型。如果通道數(shù)量事先已知盹沈,也可以用數(shù)組類型龄章,效率更高一些吃谣。
cv::Mat planes[3];
cv::split(image1, planes);
合并回去
cv::Mat result;
cv::merge(planes, result);
閾值操作
cv::threshold(input, // 輸入的圖片
output, // 輸出的圖片
theshold, // 設(shè)定的閾值
255, // 超過閾值的像素值取為 255,其余為 0
cv::THRESH_BINARY_INV); // 但這里用了 INV 反向操作做裙,小于等于閾值的取 255,其余為 0
圖像重映射
這里的重映射是指將原圖中的元素經(jīng)過某種位置變換岗憋,得到新圖。這里只是改變了元素的位置锚贱,沒有修改元素的值仔戈。
通過 cv::remap
函數(shù)實(shí)現(xiàn)。
首先定義重映射參數(shù)矩陣
cv::Mat srcX(image.rows, image.cols, CV_32F);
cv::Mat srcY(image.rows, image.cols, CV_32F);
目標(biāo)圖像在 處的像素來自原圖的下列位置
(srcX.at<float>(i,j), srcY.at<float>(i,j))
然后在這兩個(gè)參數(shù)矩陣中定義具體的位置映射關(guān)系
for (int i=0; i<image.rows; i++){
for (int j=0; j<image.cols; j++){
srcX.at<float>(i,j) = j; // 來自原圖的 j 列
srcY.at<float>(i,j) = i+5*sin(j/10.0); // 來自原圖的 i+5*sin(j/10) 行拧廊,即正弦扭曲
}
}
最后調(diào)用 cv::remap
函數(shù)
cv::remap(image, result, srcX, srcY, cv::INTER_LINEAR);
反色
對(duì)于灰度圖监徘,直接用 255 減去就可以了!
cv::Mat result1_inv = 255 - result1;
獲取圖像元素的常用方法
- 用
cv::Mat
類中的at()
方法:
image.at<uchar>(i,j);
image.at<cv::Vec3b>(i,j)[0]
上邊尖括號(hào)中的數(shù)據(jù)類型就是灰度圖和彩圖中常用的數(shù)據(jù)類型:uchar
和 cv::Vec3b
吧碾。
在使用 at()
時(shí)有一個(gè)需要注意的問題:
at()
函數(shù)有多個(gè)重載的形式凰盔,例如既有 at(int row, int col)
,也有 at(Point2f())
倦春。Point2f 格式對(duì)應(yīng)的是 (x,y) 坐標(biāo)户敬,x 軸向右,y 軸向下睁本,與 (row, col) 正好顛倒尿庐。因此在調(diào)用時(shí)一定要注意參數(shù)是 (row, col) 形式還是 Point2f(x,y) 坐標(biāo)形式。
- 每次用 at 必須指明元素的數(shù)據(jù)類型呢堰,比較麻煩抄瑟。可以用另一種方式:
cv::Mat_<uchar> img(image);
img(50, 10)=200;
即在定義變量時(shí)就已經(jīng)指明了元素類型枉疼,后邊直接取用就可以了皮假。
數(shù)據(jù)類型的編號(hào)
cv::Mat test = cv::Mat::zeros(3,3,CV_64F);
test.type(); // 返回 6
用 .type()
函數(shù)即可返回 Mat 的數(shù)據(jù)類型,但是返回的是一個(gè)整數(shù)往衷,需要查看下表確定到底是什么類型:
C1 | C2 | C3 | C4 | |
---|---|---|---|---|
CV_8U | 0 | 8 | 16 | 24 |
CV_8S | 1 | 9 | 17 | 25 |
CV_16U | 2 | 10 | 18 | 26 |
CV_16S | 3 | 11 | 19 | 27 |
CV_32S | 4 | 12 | 20 | 28 |
CV_32F | 5 | 13 | 21 | 29 |
CV_64F | 6 | 14 | 22 | 30 |
列代表通道數(shù)目钞翔,例如灰度圖就是 C1, jpg 彩圖就是 BGR C3严卖,PNG 彩圖除了 BGR 還有一個(gè)透明度通道席舍,所以是 C4。
在用.at<>
獲取 Mat 中的數(shù)據(jù)時(shí)哮笆,需要指明元素的數(shù)據(jù)類型来颤,比如灰度圖是 uchar 而不是 CV_8U,查看下表:
C1 | C2 | C3 | C4 | C6 | |
---|---|---|---|---|---|
uchar | uchar |
cv::Vec2b |
cv::Vec3b |
cv::Vec4b |
|
short | short |
cv::Vec2s |
cv::Vec3s |
cv::Vec4s |
|
int | int |
cv::Vec2i |
cv::Vec3i |
cv::Vec4i |
|
float | float |
cv::Vec2f |
cv::Vec3f |
cv::Vec4f |
cv::Vec6f |
double | double |
cv::Vec2d |
cv::Vec3d |
cv::Vec4d |
cv::Vec6d |
這里的 cv::Vec3b
中 b
是 byte 的意思稠肘,表示占用一個(gè)字節(jié)福铅。
顏色空間的轉(zhuǎn)換
用 cv::cvtColor
轉(zhuǎn)換前后,圖像的數(shù)據(jù)類型是相同的项阴。
- 彩圖到灰度:
if (image.channels()==3){
cv::cvtColor(image, result, cv::COLOR_BGR2GRAY);
// 老版本的 opencv 用這個(gè) flag
// cv::cvtColor(color, gray, CV_BGR2Gray)
}
- 灰度到彩圖:
cv::cvtColor(image, result, cv::COLOR_Gray2BGR) // 三個(gè)通道數(shù)值相同
cv::cvtColor( )
函數(shù)可以實(shí)現(xiàn)很多顏色空間的轉(zhuǎn)換滑黔,除了 BGR、灰度圖,還有 HSV略荡, HLS庵佣,Lab, Luv, YCrCb 等。
感知均勻的色彩空間
原始的 BGR 顏色表示方法有個(gè)缺陷:在顏色空間中的差距并不能很好的反映人視覺感知的差距汛兜。
兩種在空間中距離很遠(yuǎn)的顏色可能肉眼看起來很接近巴粪;而有些在空間距離很近的顏色,看起來差別卻很大粥谬。因此肛根,BGR 不是感知均勻的色彩空間。
下邊是兩種感知均勻的色彩空間:
- CIE L * a * b *
L 通道:表示亮度漏策,取值 0~100派哲。在使用 8 位圖像時(shí),取值 0~255
a 通道和 b 通道:表示色度部分哟玷,與亮度完全無關(guān)狮辽,取值 -127~127,對(duì)于 8 位圖巢寡,取值 0~255.cv::cvtColor(image, converted, cv::COLOR_BGR2Lab)
- CIE L * u * v *
對(duì)亮度通道采用相同的轉(zhuǎn)換公式喉脖,但對(duì)色度通道使用不同的方法。cv::cvtColor(image, converted, cv::COLOR_BGR2Luv)
色調(diào)抑月、飽和度树叽、亮度空間
人類在描述顏色時(shí),經(jīng)常說色調(diào)谦絮、飽和度题诵、亮度等詞語(yǔ),下邊的兩種色彩空間就是這樣設(shè)計(jì)的:
-
HSV
H: 色調(diào) hue
S: 飽和度 saturation
V: 明度 valuecv::cvtColor(image, hsv, cv::COLOR_BGR2HSV)
可以把三通道分割開:
std::vector<cv::Mat> channels; cv::split(hsv, channels); // channels[0] 表示色調(diào) 0~180 // channels[1] 表示飽和度 0~255 // channels[2] 表示亮度 0~255
HLS
H: 色調(diào) hue
L: 亮度 lightness
S: 飽和度 saturation
HSV 和 HLS 有時(shí)也統(tǒng)稱為 HSB 层皱,最后的 B 表示 brightness 亮度性锭。
可以用圓錐體直觀地表示 HSB 色彩空間:
- 角度表示色調(diào) H,本來應(yīng)該用 0~360 表示色調(diào)叫胖,但是為了適應(yīng) 8 位圖草冈,采用了 0~180,0 度對(duì)應(yīng)紅色瓮增。
- 距中軸線的距離表示飽和度 S
- 高度表示亮度 B
如果要進(jìn)行圖片的 HSB 分析怎棱,尤其是涉及到色調(diào)(第一個(gè)分量)的時(shí)候,由于飽和度(第二個(gè)分量)低的區(qū)域色調(diào)信息不準(zhǔn)確绷跑,因此往往先對(duì)飽和度做一些過濾拳恋,只分析那些飽和度比較高的區(qū)域的色調(diào)。
區(qū)域圖像提取
cv::Mat imageROI = image(cv::Rect(216, 33, 24, 30)); // 從原始圖片 image 中提取了一個(gè)小方塊
// 或者
cv::Mat imageROI(image2, cv::Rect(0,0,image2.cols,image2.rows/2));
隨后就可以把提取出來的 imageROI 當(dāng)成普通圖片處理了砸捏。
添加幾何圖形和文字
外加圖形和文字的顏色維度取決于原始圖像的維度谬运。
若原始為灰度圖隙赁,則設(shè)定外加顏色時(shí),即使寫出三維像素值 cv::Scalar(255, 0, 128)
也只看第一維的數(shù)值梆暖;
若原始為彩圖鸳谜,則設(shè)定外加顏色時(shí),只寫 0 或者 255 表示只設(shè)定了第一個(gè)顏色通道的值式廷,例如 255 相當(dāng)于 cv::Scalar(255, 0, 0)
-
線段
cv::line(image, cv::Point(5, 5), cv::Point(210, 200), // 線段的兩個(gè)端點(diǎn) cv::Scalar(255), // 顏色 1); // 粗細(xì)
-
矩形方框
cv::Rect rect(110, 45, 35, 45); // 設(shè)定一個(gè)方框咐扭,左上角坐標(biāo)+寬+長(zhǎng) cv::rectangle(image, rect, cv::Scalar(0,0,255)); // 在 image 圖像上添加 rect 方框,紅色 //或者直接把矩形放在內(nèi)部定義 cv::rectangle(image, cv::Rect(110, 45, 35, 45), 0); // 或者以兩對(duì)點(diǎn)集來表示滑废,分別為左上角坐標(biāo)和右下角坐標(biāo) cv::rectangle(image, cv::Point(5,5), // 左上角坐標(biāo) cv::Point(210, 200), // 右下角坐標(biāo) cv::Scalar(255), // 顏色 3); // 線條粗細(xì)
-
圓
cv::circle(image, // 原始圖像 cv::Point(155, 110), // 圓心, 以 cv::Point(x,y) 表示圖像中的坐標(biāo) 65, // 半徑蝗肪,必須為 int 類型 0, // 顏色 3); // 線條寬度,若為負(fù)數(shù)蠕趁,則畫實(shí)心圓
-
加重心
假設(shè)圖像中物體輪廓已經(jīng)找到了薛闪,要依據(jù)輪廓添加物體的重心位置。
一般計(jì)算重心的時(shí)候俺陋,首先要計(jì)算圖像的矩 (moment)豁延,再基于各階矩計(jì)算出重心:itc = contours.begin(); // 已經(jīng)有了若干輪廓線 while (itc != contours.end()){ cv::Moments mom = cv::moments(cv::Mat(*itc++)); // 求每條輪廓的各階矩 cv::circle(result, cv::Point(mom.m10/mom.m00, mom.m01/mom.m00), // 通過這種方式計(jì)算重心 2, cv::Scalar(0), 2 ); }
原理:
對(duì)于二元連續(xù)函數(shù) ,它的 階矩為
對(duì)于離散的圖片 腊状,對(duì)應(yīng)的 階矩為
因此诱咏,圖片 的重心為
-
橢圓
可以通過外包矩形的方式畫出橢圓std::vector<cv::Point> points; cv::RotatedRect rr = cv::minAreaRect(points); // 由點(diǎn)集得到最小矩形外包,可以傾斜 cv::ellipse(image, rr, 255, 2);
也可以直接指定橢圓的形狀參數(shù)
cv::ellipse(image, cv::Point(WINDOW_WIDTH / 2, WINDOW_WIDTH / 2), // 橢圓中心點(diǎn) cv::Size(WINDOW_WIDTH / 4, WINDOW_WIDTH / 16), // 兩個(gè)軸的長(zhǎng)度 angle, // 橢圓傾斜的角度 0, 360, // 橢圓包圍的起始和終止角度 Scalar(255, 129, 0), thickness);
-
多邊形
std::vector<cv::Point> poly; // 存放多邊形的頂點(diǎn)坐標(biāo) cv::polylines(image, poly, true, // 是否閉合缴挖,若閉合袋狞,則最后一個(gè)點(diǎn)將于第一個(gè)點(diǎn)相連 0, // 顏色 2); // 寬度
-
加文字
cv::putText(image, "This is a dog.", cv::Point(40, 200), // 文本位置,文本的左下角對(duì)應(yīng)的坐標(biāo) cv::FONT_HERSHEY_PLAIN, // 字體類型 2.0, // 字體大小 255, // 字體顏色映屋,這里相當(dāng)于 cv::Scalar(255, 0, 0) 2); // 字體粗細(xì)
直線擬合
由點(diǎn)集擬合出一條直線苟鸯。一般的擬合目標(biāo)是使所有點(diǎn)到直線的距離之和最小。
在計(jì)算距離的函數(shù)中棚点,歐幾里得距離計(jì)算的最快早处,對(duì)應(yīng)參數(shù) cv::DIST_L2
,實(shí)際上也就是基于最小二乘算法的擬合瘫析。
std::vector<cv::Point> points; // 假設(shè)我們已經(jīng)獲取了節(jié)點(diǎn)集合
cv::Vec4f line; // 待擬合的直線
cv::fitLine(points, // 點(diǎn)集
line, // 擬合出的直線
cv::DIST_L2, // 采用的距離類型
0, // L2 距離不需要這個(gè)
0.01,
0.01); // 擬合出的直線參數(shù)精度
直線是 cv::Vec4f
的格式砌梆,包含四個(gè)數(shù),前兩個(gè)數(shù)表示單位向量方向颁股,后兩個(gè)數(shù)表示直線上的一個(gè)坐標(biāo)么库。
也可以在三維空間中擬合直線傻丝,只需要將點(diǎn)集和直線的數(shù)據(jù)類型相應(yīng)地修改一下:
std::vector<cv::Point3i> points;
// 或者
std::vector<cv::Point3f> points;
cv::Vec6f line;
改變圖片大小 resize
cv::resize(image,
result,
cv::Size(), // 指定改變之后的大小
4, 4, // 指定 x,y 方向(即 width, height)縮放比例甘有。本行與上一行至少要設(shè)定一個(gè)
cv::INTER_NEAREST); // 插值方式
圖片大小改變后,像素值要重新計(jì)算葡缰,有以下插值方法:
- INTER_NEAREST - 最鄰近插值亏掀,對(duì)于放大圖像的情況忱反,就是簡(jiǎn)單的把每個(gè)像素的尺寸放大。
-
INTER_LINEAR - 雙線性插值滤愕,如果最后一個(gè)參數(shù)不指定温算,默認(rèn)使用這種方法。
所謂雙線性间影,就是先在插入點(diǎn)的兩側(cè)線性插入新像素值注竿,再以此為基礎(chǔ),再次線性的插入像素值魂贬。整個(gè)過程中用到 2x2 = 4 個(gè)原始像素巩割。
- INTER_CUBIC - 4x4像素鄰域內(nèi)的雙立方插值
- INTER_LANCZOS4 - 8x8像素鄰域內(nèi)的Lanczos插值
以上插值算法計(jì)算復(fù)雜度依次增加,效果也是依次提升的付燥。
掃描圖像元素的方法
- 用
.at
函數(shù)// 減色運(yùn)算操作 for(int j=0; j<nl; j++){ // nl 為行數(shù) for(int i=0; i<nc; i++){ // nc 為列數(shù) image.at<cv::Vec3b>(j,i)[0] = image.at<cv::Vec3b>(j,i)[0]/div * div + div/2; image.at<cv::Vec3b>(j,i)[1] = image.at<cv::Vec3b>(j,i)[0]/div * div + div/2; image.at<cv::Vec3b>(j,i)[2] = image.at<cv::Vec3b>(j,i)[0]/div * div + div/2; } }
- 用指針
如果要按順序依次獲取圖像像素信息宣谈,盡量不要用上述 image.at 方法,而要用指針键科,指針在移動(dòng)掃描操作時(shí)效率更高闻丑。int nc = image.cols * image.channels(); for (int j=0; j<nl; j++){ uchar *data = image.ptr<uchar>(j); // 每行首元素對(duì)應(yīng)的地址 for (int i=0; i<nc; i++){ data[i] = data[i]/div*div + div/2; } }
另外,少層循環(huán) + 每個(gè)循環(huán)中較多操作 好于 多層循環(huán) + 每個(gè)循環(huán)中較少操作勋颖。
例如要對(duì)一個(gè)像素執(zhí)行 N 個(gè)不同的操作嗦嗡,就應(yīng)該在單個(gè)循環(huán)中執(zhí)行全部操作,而不是寫 N 個(gè)循環(huán)饭玲,每個(gè)循環(huán)執(zhí)行一個(gè)操作酸钦。
- 用迭代器 (iterator)
cv::Mat_<cv::Vec3b>::iterator it = image.begin<cv::Vec3b>(); cv::Mat_<cv::Vec3b>::iterator itend = image.end<cv::Vec3b>(); for (;it!= itend;++it){ (*it)[0] = ... (*it)[1] = ... (*it)[2] = ... } //或者用 while 循環(huán) while (it!=itend){ ... ++it; }
減色算法
默認(rèn) BGR 三色都是采用 8 位二進(jìn)制數(shù),即有 0~255 種選擇咱枉,三通道互相搭配總共的顏色數(shù)目為 256 * 256 * 256卑硫。 為了降低分析的復(fù)雜度秕硝,可以先對(duì)原圖做減色處理堕担,將每個(gè)通道顏色的數(shù)量降低為原來的 1/n,例如 n=8嚣州,則新的圖片總共的顏色數(shù)目只有 32 * 32 * 32 亿乳。
算法步驟:
- 假設(shè) N 為減色因子硝拧,將圖像中每個(gè)像素的值除以 N,去掉余數(shù)部分
- 將上述結(jié)果乘以 N葛假,得到原始像素值相對(duì)于 N 的倍數(shù)(相當(dāng)于向下取整)
- 再加上 N/2障陶,這樣相對(duì)于原始像素值的誤差就從 -N ~ 0 變成了 -N/2 ~ N/2 ,近似效果更好
- 減色之后共有 (256/N) * (256/N) * (256/N) 種顏色聊训。
銳化圖像
目的是放大圖像邊緣抱究,使圖像看起來更加尖銳
對(duì)于每個(gè)像素,令本身像素值乘以5带斑,然后減去周圍的 4 個(gè)像素值鼓寺,即
這種操作實(shí)際上就是一種卷積
用上述 kernel matrix 依次掃過圖像勋拟,即完成了上述的銳化操作。
cv::Mat 數(shù)據(jù)類型作為參數(shù)傳遞
cv::Mat 數(shù)據(jù)結(jié)構(gòu)包括數(shù)據(jù)頭部和數(shù)據(jù)塊:
- 數(shù)據(jù)頭部包含了屬性信息妈候,例如數(shù)據(jù)的大小敢靡、行列、通道數(shù)苦银、元素類型等啸胧,可以通過
.cols
,.rows
,channels()
等方式獲取數(shù)據(jù)頭部包含的信息; - 數(shù)據(jù)塊存儲(chǔ)了所有的像素信息幔虏,數(shù)據(jù)頭部中包含了
.data
指針吓揪,可以訪問數(shù)據(jù)塊。
cv::Mat 數(shù)據(jù)結(jié)構(gòu)的一個(gè)關(guān)鍵特點(diǎn)時(shí)所计,當(dāng)復(fù)制一個(gè) cv::Mat 數(shù)據(jù)時(shí)柠辞,僅復(fù)制了數(shù)據(jù)頭部,因此復(fù)制前后的變量都指向同一個(gè)數(shù)據(jù)塊(除非特別指明數(shù)據(jù)頭部和數(shù)據(jù)塊全都復(fù)制)主胧。前述內(nèi)容中提到叭首,copyTo()
, clone()
函數(shù)才會(huì)復(fù)制得到全新的函數(shù),而用賦值操作僅僅復(fù)制了數(shù)據(jù)頭部踪栋,數(shù)據(jù)塊仍然是共享的焙格。
當(dāng)向函數(shù)中傳遞 cv::Mat 類型數(shù)據(jù)時(shí),比較常見的參數(shù)傳遞形式包括 cv::Mat
, const cv::Mat
, const cv::Mat &
, cv::Mat &
.
-
func(cv::Mat input)
僅僅復(fù)制了 input 對(duì)應(yīng)實(shí)參變量的數(shù)據(jù)頭部夷都,對(duì) input 的操作會(huì)改變實(shí)參變量眷唉。但是如果對(duì) input 重新賦值,例如input = cv::Mat::ones(...)
囤官,此時(shí)對(duì) input 的操作不會(huì)影響到外部的實(shí)參冬阳,因?yàn)樾碌?input 不再指向?qū)崊⒆兞康臄?shù)據(jù)塊。 -
func(const cv::Mat input)
也是僅僅復(fù)制了 input 對(duì)應(yīng)實(shí)參的數(shù)據(jù)頭部党饮,只不過由于 const 的限制肝陪,不能在函數(shù)內(nèi)部修改該數(shù)據(jù)頭部。 -
func(const cv::Mat &input)
以引用的形式傳遞了實(shí)參變量的數(shù)據(jù)頭部刑顺,同樣由于 const 的限制氯窍,不能修改該頭部。 -
func(cv::Mat &)
以引用的形式傳遞了實(shí)參變量的數(shù)據(jù)頭部蹲堂,并且可以修改該頭部狼讨。
注意:上述參數(shù)傳遞方式僅僅對(duì)數(shù)據(jù)頭部做了限制,而不限制頭部指向的數(shù)據(jù)塊柒竞,因此情況 2 和 3 中政供,即使由于 const 限制不能修改頭部,仍然可以修改頭部指向的數(shù)據(jù)塊: input.data[0] = 5