OpenCV圖像處理視頻教程
---------------------------------------🤣 🤩 🤪 🧐 🤯 🥳 🤨 🥰 🤔 --------------------------------------------------
Author: XFFer_
文章目錄
01 概述 - OpenCV介紹與環境搭建
- 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)
,返回0saturate_cast<uchar>(288)
,返回255saturate_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_8UC2
、CV_8UC3
(彩色BGR)、CV_8UC4
(帶透明色的BGR)都是Unsigned 8bitsCV_32FC1
、CV_32FC2
、CV_32FC3
是float32位CV_64FC1
、CV_64FC2
、CV_64FC3
是double64位
- 解釋一下type矩陣類型
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 M(24, 24, CV_8UC3, Scalar(0, 0, 255))
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對象,像素值都爲0Mat::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 charint blue = intensity.val[0];
int blue = img.at<Vec3b>(row, col)[0];
這種寫法也是對的 blueint green = intensity.val[1];
greenint 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)理論-線性混合操作
其中的取值範圍爲0~1之間。
(2)相關API(addWeighted)
void cv::addWeighted ( InputArray src1,
double alpha,
InputArray src2,
double beta,
double gamma,
OutputArray dst,
int dtype = -1
)
- 參數1:輸入圖像Mat-src1
- 參數2:輸入圖像src1的值
- 參數3:輸入圖像Mat-src2
- 參數4:輸入圖像src2的值
- 參數5:(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內的計時週期數)- 用來計算當前程序的運行時間
07 調整圖像亮度與對比度
1 理論
2 代碼演示
(1)理論
圖像變換:
- 像素變換 — 點操作
- 鄰域操作 — 區域
調整圖像亮度和對比度屬於像素變換。 其中 是增益變量
控制對比度,控制亮度。
小知識點:int64
類型是 long long
,size_t
類型是 unsigned long long
08 繪製形狀與文字
1 使用
cv::Point
與cv::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
- 關於OpenCV中矩陣
Matx
和退化的單行矩陣Vec
源碼分析可以參考Ph.D, HUST.的OpenCV源碼閱讀之matx.h
(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()
默認構造函數,矩形左上角的橫縱座標,矩形大小均爲0Rect::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操作其背後是數學的卷積計算
- 通常這些卷積算子計算都是線性操作,所以又叫線性濾波
歸一化盒子濾波(均值濾波)
API:
blur(Mat src, Mat dst, Size(xradius, yradius), Point(-1, -1));
註釋: Point(-1, -1)意思是將輸出值賦給中心點。
高斯濾波
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);
雙邊濾波:保持輪廓
可以這樣理解,在高斯濾波的基礎上,添加了一種類似閾值的概念,按傳統的高斯濾波處理,但相鄰像素值超出閾值時不做處理,保留了邊緣信息。
雙邊濾波分爲空間臨近度計算的權值和像素值相似度計算的權值,在邊緣附近(高頻信號),離的較遠的像素不會太多影響到邊緣上的像素,這樣就能對邊緣附近的像素值予以保存。
是輸出圖像,是輸入圖像,是以像素點爲中心的鄰域窗口,爲濾波核。
濾波核由與歐式距離相關的空域核以及和臨近像素值差異相關的值域核的點積構成。
兩個核都按照高斯分佈的形式
從公式可以看出當相鄰像素值和差值越大時,越小,的權重越小,這時就成空域核佔主要權重,實際上就是普通的高斯濾波形式。
API:
bilateralFilter(Mat src, Mat dst, int d, double sigmaColor, double sigmaSpace, int borderType = BORDER_DEFAULT);