OpenCV圖像處理視頻教程——入門篇(一)

OpenCV圖像處理視頻教程

---------------------------------------🤣 🤩 🤪 🧐 🤯 🥳 🤨 🥰 🤔 --------------------------------------------------
Author: XFFer_


01 概述 - OpenCV介紹與環境搭建

concept

  • HighGUI部分
  • Image Process
  • 2D Feature
  • Camera Calibration and 3D reconstruction
  • Video Analysis
  • Object Detection
  • Machine Learning
  • GPU加速

02 加載、修改、保存圖像

  • 加載圖像(用cv::imread)
  • 修改圖像(cv::cvtColor)
  • 保存圖像(cv::imwrite)
  • 代碼演示

(1)加載圖像(用cv::imread)

  • imread功能是加載圖像文件稱爲一個Mat對象(類對象)(源碼中Mat類有多個重載的構造函數[參數表不同])
  • cv::Mat cv::imread(const cv::String &filename, int flags = 1)
    • 其中第一個參數表示圖像文件名稱(文件的絕對地址)
    • 第二個參數,表示加載的圖像是什麼類型,常見的有三個參數值(都是enum枚舉類型)
      • IMREAD_UNCHANGED(<0)表示加載原圖,不做任何改變
      • IMREAD_GRAYSCALE(0)表示把原圖作爲灰度圖像加載進來
      • IMREAD_COLOR(>0)表示把原圖作爲RGB圖像加載進來

注意:OpenCV支持JPG、PNG、TIFF等常見格式圖像文件加載。

(2)顯示圖像(cv::namedWindow與cv::imshow)

  • namedWindow功能是創建一個OpenCV窗口,它是由OpenCV自動創建與釋放的無需自己銷燬(早年需要destroyWindow)
  • 常見用法namedWindow("Window Title", WINDOW_AUTOSIZE)
  • WINDOW_AUTOSIZE自動根據圖像大小,顯示窗口大小,不能人爲改變窗口大小
  • WINDOW_NORMAL,跟QT集成的時候會使用,允許修改窗口大小
  • imshow根據窗口名稱顯示圖像到指定的窗口上去,第一個參數是窗口名稱,第二個參數是Mat對象

(3)修改圖像(cv::cvtColor)

  • cvtColor的功能是把圖像從一個色彩空間轉換到另一個色彩空間。調整亮度/飽和度時先轉換空間,改變效果後,再轉換回來。有三個參數
    • 表示源圖像
    • 表示色彩空間轉換之後的圖像
    • 表示源和目標色彩空間
      • COLOR_BGR2HLS(L->Light[亮度],S->saturation[飽和度])
      • COLOR_BGR2GRAY(BGR=>RGB,原因是B通道在前)
      • COLOR_BGR2HSV(S->saturation[飽和度])
  • cvtColor(image, gray_image, COLOR_BGR2GRAY)
    • (image, gray_image都是Mat類對象)

(4)保存圖像(cv::imwrite)

  • 保存圖像到指定目標路徑(🤯路徑中一定要加文件名.後綴名)
  • 只有8位、16位的PNG、JPG、Tiff文件格式而且是單通道或者三通到的BGR圖像纔可以通過這種方式保存
  • 保存PNG格式的時候可以保存透明通道的圖片
  • 可以指定壓縮參數

03 矩陣的掩膜操作

1 獲取圖像像素指針
2 掩膜操作解釋
3 代碼演示

(1)獲取圖像像素指針

  • CV_Assert(myImage.depth() == CV_8U);測試位圖深度是否爲8位,如果False則停止運行
    • 位圖深度:在灰度圖像中8位代表從黑(0)到白(1)分爲256個顏色深度;在RGB圖像中代表每個通道(每個原色)都分爲256個子色,總體上爲256 * 256 * 256種顏色
  • Mat.ptr<uchar>(int i = 0)(uchar是unsigned char,一個字節0~ 255,像素值也是在0~255)獲取像素矩陣的指針,索引i表示第幾行,從0開始計行數
  • 獲得當前行指針const uchar* current = myImage.ptr<uchar>(row);
  • 獲取當前像素點P(row, col)的像素值p(row, col) = current[col];

(2)像素範圍處理saturate_cast

  • saturate_cast<uchar>(-100),返回0
  • saturate_cast<uchar>(288),返回255
  • saturate_cast<uchar>(124),返回124
  • 這個函數的功能是確保RGB值的範圍在0~255之間

(3)掩膜操作解釋

在這裏插入圖片描述

代碼示例:(RGB圖像可以理解爲一個像素佔三個字節(R/G/B))

在這裏插入圖片描述
這是一張3*3的(255, 0, 0)的純紅色圖片
在這裏插入圖片描述
(.ptr<uchar>(int)是Mat類的成員函數,uchar用於表示像素0~255)

//示例中使用
Mat resultImage;
myImage.copyTo(resultImage);	//將圖片clone

//視頻中
Mat resultImage;
resultImage = Mat::zeros(myImage.size(), myImage.type());
//clone了新對象,zeros產生一個純黑的圖像,拷貝圖像大小和類型

(4)函數調用filter2D功能

  • 定義掩膜:
    • Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
    • Mat_是一個類模版是對Mat的封裝沒有添加更多的屬性。char是typename,Mat_(int _rows, int _cols)是它的一個構造函數。目的是避免多處聲明數據類型導致出錯
  • filter2D(src, dst, src.depth(), kernel);
    • 其中src與dst是Mat類型變量、src.depth表示位圖深度,有32、24、8等。

在這裏插入圖片描述

04 Mat對象

1 Mat對象與IplImage對象
2 Mat對象使用
3 Mat定義數組

(1)Mat對象與IplImage對象

  • Mat對象OpenCV2.0之後引進的圖像數據結構、自動分配內存、不存在內存泄漏的問題,是面向對象的數據結構。分頭部數據部分
  • IplImage是從2001年OpenCV發佈之後就一直存在,是C語言風格的數據結構,需要開發者自己分配與管理內存,對大的程序使用它容易導致內存泄漏問題🧑‍

(2)Mat對象構造函數與常用方法

對象構造函數

  • Mat()
  • Mat(int rows, int cols, int type) 傳入長寬像素大小,和矩陣單位類型
    • 解釋一下type矩陣類型
      • 可以通過原對象.convertTo(目標Mat對象, 目標類型)來改變
      • CV_8UC1(灰度)、CV_8UC2CV_8UC3(彩色BGR)、CV_8UC4(帶透明色的BGR)都是Unsigned 8bits
      • CV_32FC1CV_32FC2CV_32FC3是float32位
      • CV_64FC1CV_64FC2CV_64FC3是double64位
  • Mat(Size size, int type) 傳入另一個Mat對象的.size()
  • Mat(int rows, int cols, int type, const Scalar &s) 傳入長寬,類型和Scalar像素值(Scalar(B, G, R))
    • Mat M(24, 24, CV_8UC3, Scalar(0, 0, 255))
      在這裏插入圖片描述
    • 其中前兩個參數分別爲行(row)列(column),第三個CV_8UC3中 8表示每個通道佔8位U表示無符號C表示Char類型3表示通道數目是3,第四個參數是向量表示初始化每個像素值是多少,向量長度對應通道數目一致
  • Mat(Size size, int type, const Scalar &s)
  • Mat(int ndims, const int *sizes, int type)
  • Mat(int ndims, const int *sizes, int type, const Scalar &s)

(3)常用方法

  • void copyTo(Mat dst) 拷貝Mat對象。用法(拷貝到):Mat對象名.copyTo(Mat對象名);
  • void convertTo(Mat dst, int type) 轉換Mat矩陣類型
  • Mat clone() 完全克隆。用法:Mat對象名 = Mat對象名.clone();
  • int channels() 返回通道數
  • int depth() 返回位圖深度
  • bool empty() 返回是否爲空對象
  • uchar* ptr(i=0) 返回一個指向行的指針。說明:default=0是第一行的索引
  • Mat::zeros(int rows, int cols, int type) 返回一個Mat對象,像素值都爲0
  • Mat::eye(int rows, int cols, int type) 返回一個Mat對象,主對角線上像素值都爲1,其他像素爲0

Mat dst = Scalar( , , )括號內填入像素值,生成一個確定BGR像素的圖片。若範圍在0~255,則代表位圖深度爲8,RGB每個原色都分成255個子色。

(4)Mat對象使用

  • 部分複製:一般情況下只會複製Mat對象的頭和指針部分,不會複製數據部分
    • Mat A = imread(imgFilePath);
    • Mat B(A);
  • 完全複製:如果想把Mat對象的頭部和數據部分一起復制,可以通過如下兩個API實現
    • Mat B = A.clone()
    • Mat G; A.copyTo(G);

Mat定義數組Mat kernel = (Mat_<typename>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);

05 圖像操作

1 讀寫圖像
2 讀寫像素
3 修改像素值

(1)讀寫像素

  • 讀一個GRAY像素點的像素值(CV_8UC1)
    • int intensity = img.at<uchar>(row, col);
    • int intensity = img.at<uchar>(Point(row, col));
  • 讀一個RGB像素點的像素值(CV_8UC3)
    • Vec3f intensity = img.at<Vec3b>(row, col); Vec3b -> 返回值是unsigned char
    • int blue = intensity.val[0];
      int blue = img.at<Vec3b>(row, col)[0]; 這種寫法也是對的 blue
    • int green = intensity.val[1]; green
    • int red = intensity.val[2]; red

.ptr(row)[col]和.at(row, col)[channel]的區別:

  • .ptr<uchar>(row)[col]是在一個已展開的圖像上提取像素指針,對於多通道而言(BGR三通道ptr獲取像素指針時,每個位置三個通道是排開的,可以理解爲一個像素位置由三個字節構成)
    • 它的返回值類型爲const _Tp * ptr<_Tp, n>(const cv::Vec<int, n> &idx) const
  • .at<uchar>(row, col)[channel]是在原圖像位置上取出像素指針,通過[channel]獲取不同通道下的像素指針的
    • 它的返回值類型爲const _Tp &at<_Tp>(cv::Point pt) const

對各通道取反操作API

bitwise_not(input_img, output_img);

編譯器快捷鍵
選中代碼後Alt + 方向鍵 移動代碼塊

06 圖像混合

1 理論-線性混合操作
2 相關API(addWeighted)
3 代碼演示

(1)理論-線性混合操作

g(x)=(1α)f0(x)+αf1(x) g(x) = (1 - \alpha )f_{0}(x) + \alpha f_{1}(x)
其中α\alpha的取值範圍爲0~1之間。

(2)相關API(addWeighted)

void cv::addWeighted ( InputArray	src1,
					   double		alpha,
					   InputArray   src2,
					   double		beta,
					   double		gamma,
					   OutputArray  dst,
					   int 			dtype = -1
					 )

dst(I)=saturate(src1(I)α+src2(I)β+γ)(β=1α) dst(I) = saturate(src1(I) * \alpha + src2(I) * \beta + \gamma)(\beta = 1-\alpha)

  • 參數1:輸入圖像Mat-src1
  • 參數2:輸入圖像src1的α\alpha
  • 參數3:輸入圖像Mat-src2
  • 參數4:輸入圖像src2的α\alpha
  • 參數5:γ\gamma(gamma)值
  • 參數6:輸出混合圖像

注意點: 兩張圖像的大小和類型必須一致纔可以。

四個函數:

  • void add(cv::InputArray src1, cv::InputArray src2, cv::OutputArray dst, cv::InputArray mask = noArray(), int dtype = -1) 像素直接相加函數
  • void multiply(cv::InputArray src1, cv::InputArray src2, cv::OutputArray dst, double scale = (1.0), int dtype = -1) 像素直接相乘函數
  • int64 cv::getTickCount() 用於返回從操作系統啓動到當前所經的計時週期數
    • int64=>long long是64位整數,類似的有int16=>short,int32=>int
  • double cv::getTickFrequency() 用於返回CPU的頻率(1s內的計時週期數)
    • getTickCountt2()getTickCountt1()getTickFrequency()\frac {getTickCount_{t2}()-getTickCount_{t1}()}{getTickFrequency()} 用來計算當前程序的運行時間

07 調整圖像亮度與對比度

1 理論
2 代碼演示

(1)理論

圖像變換:

  • 像素變換 — 點操作
  • 鄰域操作 — 區域

調整圖像亮度和對比度屬於像素變換。 g(i,j)=αf(i,j)+β(g(i,j)=\alpha f(i,j) + \beta(其中α>0,β\alpha >0,\beta 是增益變量))
α\alpha控制對比度β\beta控制亮度

小知識點int64 類型是 long longsize_t 類型是 unsigned long long
演示

08 繪製形狀與文字

1 使用cv::Pointcv::Scalar
2 繪製線、矩形、圓、橢圓等幾本幾何形狀
3 隨機生成與繪製文本
4 代碼演示

(1)使用cv::Point與cv::Scalar

  • Point表示2D平面上一個點(x, y)
  • Scalar表示至多四個元素的向量

Point和Scalar都是類模板

//Scalar源碼types.hpp
typedef Scalar_<double> Scalar;

//Scalar_源碼
template<typename _Tp> class Scalar_ : public Vec<_Tp, 4>

//Vec源碼
template<typename _Tp, int cn> class Vec : public Matx<_Tp, cn, 1>

//Matx源碼
template<typename _Tp, int m, int n> class Matx

(2)繪製線、矩形、圓、橢圓等幾本幾何形狀

提醒: 當線寬設置爲-1時,會填充整個形狀

  • cv::line (線的類型:LINE_4\LINE_8\LINE_AA(反鋸齒))
void Draw_Line(int line_width, Mat& tmp)
{
	line(tmp, Point(100, 100), Point(200, 200), Scalar(0, 0, 255), line_width, LINE_8);
}
  • 橢圓 cv::ellipse
    • void ellipse(InputOutputArray img, Point center, Size axes, double angle, double startAngle, double endAngle, const Scalar& color, int thickness = 1, int lineType =8, int shift = 0)
      • Point 圓心座標
      • Size 橢圓長軸、短軸長度
      • angle 初始旋轉角度
      • startAngle 橢圓起始角度
      • endAngle 橢圓終止角度(這兩個用於畫橢圓弧)
void ellipse(int line_width, Mat& tmp)
{

	ellipse(tmp, Point(tmp.rows / 2, tmp.cols / 2), Size(tmp.rows / 4, tmp.cols / 8), 45, 0, 360, Scalar(150, 150, 0), line_width, LINE_8);
}
  • 矩形 cv::rectangle
    • Rect用來創建一個矩形對象。構造函數:
      • Rect::Rect()默認構造函數,矩形左上角的橫縱座標,矩形大小均爲0
      • Rect::Rect(point&, size&)Point對象初始化矩形左上角的橫縱座標;用**Size(width, height)**初始化矩形大小
      • Rect::Rect(INT, INT, INT, INT)用四個證書初始化角點的橫縱座標、矩形大小
Rect rect = Rect(100, 100, 200, 200);
//Rect rect(Point(100, 100), Size(200, 200));
void rectangle(int line_width, Mat& tmp)
{
	rectangle(tmp, rect, Scalar(0, 255, 0), line_width, LINE_8);
}
  • cv::circle
//和橢圓很相似,Size->radius半徑
void circle(int line_width, Mat& tmp)
{
	circle(tmp, Point(tmp.rows / 2, tmp.cols / 2), 50, Scalar(0, 125, 125), line_width, LINE_8);
}
  • 填充 cv::fillPoly
    • void fillPoly(InputOutputArray img, const Point **pts, const int *npts, int ncontours, const Scalar &color, int lineType = 8)
      • Point ** pts 用來接指向Point數組的指針
      • int *npts 用來接一共有幾個指向頂點位置的指針,實際頂點個數+1(最後一個指回初始點)
      • int ncontours 是幾個閉環,是point[1][6]中第一維的數值
void Polygon(Mat& tmp)
{
	//定義一個五邊形的五個頂點,必須用數組
	const Point point[1][6];
	point[0][0] = Point(0, 0);
	point[0][1] = Point(100, 100);
	point[0][2] = Point(100, 50);
	point[0][3] = Point(80, 30);
	point[0][4] = Point(50, 10);
	point[0][5] = Point(0, 0);
	const Point* pts[] = { point[0] };
	int npts[] = { 6 };	//定義成數組的原因是數組名是首地址,可以直接傳給指針
	
	fillPoly(tmp, pts, npts, 1, Scalar(100, 0, 0), 8);
} 
  • 文字 putText
    • void putText(InputOutputArray img, const String& text, Point org, int fontFace, double frontScale, Scalar color, int thickness, int lineType)
      • fontFace代表字體格式,如:CV_FONT_HERSHEY_COMPLAX CV_FONT_BLACK
putTest(img, "content", Point(img.rows / 2, img.cols / 2), CV_FONT_HERSHEY_COMPLAX, 1.0, Scalar(12, 24, 200), 3, 8);

這裏再次提一下waitKey函數

  • waitKey函數是一個等待鍵盤事件的函數,參數值delay<=0時等待時間無限長,delay爲正整數n時至少等待n毫秒的時間才結束。在等待的期間按下任意按鍵時函數結束,返回按鍵的鍵值(ascii碼),等待時間結束仍未按下按鍵則返回-1。該函數用在處理HighGUI窗口程序,最常見的便是與顯示圖像窗口imshow函數搭配使用
if (waitKey(delay_time) >= 0)
	break;

(3)隨機數生成cv::RNG

RNG rng

  • 生成高斯隨機數 rng.gaussian(double sigma)
  • 生成正態分佈隨機數 rng.uniform(int a, int b) a和b是取值範圍

隨機生成line&“OpenCV”

void randomgenerator()
{
	Size size(500, 500);
	Mat src = Mat::zeros(size, IMREAD_REDUCED_GRAYSCALE_2);
	RNG rng(1024);
	Point p1, p2;
	for (i = 0; i < 100000; i++)
	{
		p1 = Point(rng.uniform(0, src.cols), rng.uniform(0, src.rows));
		p2 = Point(rng.uniform(0, src.cols), rng.uniform(0, src.rows));
		p3 = Point(rng.uniform(0, src.cols), rng.uniform(0, src.rows));
		Scalar color(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));

		line(src, p1, p2, color, rng.uniform(0.5, 3), 8);
		putText(src, "OpenCV", p3, rng.uniform(0, 7), rng.uniform(0.7, 2.3), color, rng.uniform(2, 3)); 
		
		imshow("demo", src);
		if (waitKey(30) > 0)
			break;
	}
}

09 模糊圖像

1 模糊原理
2 中值濾波
3 雙邊濾波

(1)模糊原理

  • Smooth/Blur是圖像處理中最簡單和常用的操作之一
  • 使用該操作的原因之一就爲了給圖像預處理時降低噪聲
  • 使用Smooth/Blur操作其背後是數學的卷積計算g(i,j)=k,lf(i+k,j+l)h(k,l)g(i,j)=\sum_{k,l} f(i+k,j+l)h(k,l)
  • 通常這些卷積算子計算都是線性操作,所以又叫線性濾波

歸一化盒子濾波(均值濾波)

K=1KwidthKheight[111111111111] K=\frac{1}{K_{width}*K_{height}}\begin{bmatrix} 1 & 1 & 1 & \cdots & 1 \\ 1 & 1 & 1 & \cdots & 1 \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ 1 & 1 & 1 & \cdots & 1 \\ \end{bmatrix}
API:

blur(Mat src, Mat dst, Size(xradius, yradius), Point(-1, -1));

註釋: Point(-1, -1)意思是將輸出值賦給中心點。

高斯濾波

G0(x,y)=Ae(xμx)22σx2+(yμy)22σy2G_0(x,y)=Ae^{\frac{-(x-\mu_x)^2}{2\sigma_x^2}+\frac{-(y-\mu_y)^2}{2\sigma_y^2}}
kernel的值分佈是二維的高斯分佈函數(保留原有的特徵)

二維高斯
API:

GaussianBlur(Mat src, Mat dst, Size ksize, double sigmaX, double sigmaY = 0, int borderType = BORDER_DEFAULT);

其中sigmax和sigmay必須是正數而且是奇數。

中值濾波:清除椒鹽噪聲

  • 統計排序濾波器
  • 中值對椒鹽噪聲有很好的抑制作用
  • 取該卷積核內所有值的中位數賦給中心像素

API:

medianBlur(Mat src, Mat dst, int ksize);

雙邊濾波:保持輪廓

雙邊濾波
可以這樣理解,在高斯濾波的基礎上,添加了一種類似閾值的概念,按傳統的高斯濾波處理,但相鄰像素值超出閾值時不做處理,保留了邊緣信息

雙邊濾波分爲空間臨近度計算的權值像素值相似度計算的權值,在邊緣附近(高頻信號),離的較遠的像素不會太多影響到邊緣上的像素,這樣就能對邊緣附近的像素值予以保存。
f(x)=yΩf(y)w(x,y)yΩw(x,y)f'(x)=\frac{\sum_{y\in\Omega}f(y)w(x,y)}{\sum_{y\in\Omega}w(x,y)}
ff'是輸出圖像,ff是輸入圖像,Ω\Omega是以像素點xx爲中心的鄰域窗口,ww爲濾波核。
濾波核ww由與歐式距離相關的空域核ϕ\phi以及和臨近像素值差異相關的值域核ψ\psi的點積構成。
w(x,y)=ϕ(x,y)ψ(x,y)w(x,y)=\phi(x,y)·\psi(x,y)
兩個核都按照高斯分佈的形式
ϕ(x,y)=eyx22σd2\phi(x,y)=e^{-\frac{||y-x||^2}{2\sigma_d^2}}
ψ(x,y)=e(f(y)f(x))22σr2\psi(x,y)=e^{-\frac{(f(y)-f(x))^2}{2\sigma_r^2}}
從公式可以看出當相鄰像素值f(y)f(y)f(x)f(x)差值越大時,(f(y)f(x))22σr2-\frac{(f(y)-f(x))^2}{2\sigma_r^2}越小,ψ\psi的權重越小,這時就成空域核佔主要權重,實際上就是普通的高斯濾波形式。

API:

bilateralFilter(Mat src, Mat dst, int d, double sigmaColor, double sigmaSpace, int borderType = BORDER_DEFAULT);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章