--------------------------------
Author : ShawnDong
updateDate :2018.11.25
Blog : ShawnDong98.github.io
--------------------------------
C++部分
圖像的加載顯示與保存
加載圖像(cv::imread)
- imread加載圖像文件成為一個Mat對象,第一個參數(shù)表示圖像名稱
- 第二個參數(shù)表示加載的圖像是什么類型,支持常見的三個參數(shù)值
- IMREAD_UNCHANGED(<0)表示加載原圖,不做任何改變
- IMREAD_GARYSACLE(0)表示把原圖作為灰度圖像加載進來
- IMREAD_COLOR(>0)表示把原圖作為RGB圖像加載進來
ps:OpenCV支持JPG,PNG, TIFF等常見格式圖像文件加載
顯示圖像(cv::nameWindows與cv::imshow)
- nameWindows功能是創(chuàng)建一個OpenCV窗口,它是由OpenCV自動創(chuàng)建與釋放,你無需去銷毀它
- 常見用法nameWindow("Window Title", WINDOW_AUTOSIZE)
- WINDOW_AUTOSIZE會自動根據(jù)圖像大小,顯示窗口大小,不能人為改變窗口大小
- WiINDOW_NORMAL, 跟QT集成的時候會使用,允許修改窗口大小
- imshow根據(jù)窗口名稱顯示圖像到指定的窗口上去,第一個參數(shù)是窗口名稱,第二參數(shù)是Mat對象
保存圖像(cv::imwrite)
- 保存圖像文件到指定目錄路徑
- 只有8位,16位的PNG、JPG、Tiff文件格式而且是單通道或者三通道的BGR的圖像才可以通過這種方式保存
- 保存PNG格式的時候可以保存透明通道的圖片
- 可以指定壓縮參數(shù)
矩陣的掩膜操作
獲取圖像指針
- CV_Assert(myImage.depth()==CV_8U);
- Mat.ptr<uchar>(int i=0)獲取像素矩陣的指針, 索引i表示第幾行, 從0開始計行數(shù)
- 獲得當前行指針const uchar* current = myImage.ptr<uchar>(row);
- 獲取當前像素點P(row, col)的像素值p(row, col) = current[col]
像素范圍處理
- saturate_cast<uchar>(-100), 返回0.
- saturate_cast<uchar>(288), 返回255.
- saturate_cast<ucahr>(100), 返回100.
- 這個函數(shù)的功能是確保RGB值的范圍在0~255之間
掩膜操作實現(xiàn)圖像對比度調(diào)整
- 紅色是中心像素,從上到下,從左到右對每個像素做同樣的處理操作,得到最終結(jié)果就是對比度提高之后的輸出圖像Mat對象
- 根據(jù)掩膜來重新計算每個像素的像素值,掩膜(mask, 也被稱為Kernel)
ps: 感覺和卷積很相似


代碼實現(xiàn):
int cols = (src.cols - 1) * src.channels();
int offsetx = src.channels();
int rows = src.rows;
dst = Mat::zeros(src.size(), src.type());
for (int row = 1; row < (rows - 1); row++)
{
const uchar* previous = src.ptr<uchar>(row - 1);
const uchar* current = src.ptr<uchar>(row);
const uchar* next = src.ptr<uchar>(row + 1);
uchar* output = dst.ptr<uchar>(row);
for (int col = offsetx; col < cols; col++)
{
output[col] = saturate_cast<uchar>(5 * current[col] - (current[col - offsetx]
+ current[col + offsetx] + previous[col] + next[col]));
}
}
函數(shù)調(diào)用filer2D功能
- 定義掩膜:Mat kernel = (Mat_<char>(3, 3)<<0, -1, 0, -1, 5, -1, 0, -1, 0);
- filter2D(src, dst, src.depth(), kernel);其中src與dst是Mat類型變量,src.depth表示位圖深度, 有32, 24, 8等。
代碼實現(xiàn):可以實現(xiàn)和上面代碼同樣的功能
double t;
t = (double)getTickCount();
filter2D(src, dst, src.depth(), kernel);
t = ((double)getTickCount() - t) / getTickFrequency();
cout << "Built in Filter2D time passed in seconds" << t << endl;
Mat對象
Mat對象與IplImage對象
- Mat對象OpenCV2.0后引進的圖像數(shù)據(jù)結(jié)構(gòu)、自動分配內(nèi)存,不存在內(nèi)存泄漏的問題,是面向?qū)ο蟮臄?shù)據(jù)結(jié)構(gòu)。分了兩個部分,頭部與數(shù)據(jù)部分
- IplImage是從2001年OpenCV發(fā)布之后就一直存在,是C語言風格的數(shù)據(jù)結(jié)構(gòu),需要開發(fā)者自己分類和管理內(nèi)存,對大的程序使用它容易導(dǎo)致內(nèi)存泄漏問題
Mat對象構(gòu)造函數(shù)與常用方法
構(gòu)造函數(shù):
- Mat()
- Mat(int rows, int cols, int type)
- Mat(Size size, int type)
- Mat(int rows, int cols, int type, const Scalar &s)
- Mat(Size size, int type, const Scalar &s)
- Mat(int ndims, const int *sizes, int type)
- Mat(int ndims, const int *szie, int type, const Scalar &s)
常用方法:
- void copyTo(Mat mat)
- void convertTo(Mat dst, int type)
- Mat clone()
- int channels()
- int depth()
- bool empty()
- uchar* ptr(i=0)
Mat對象使用
- 部分復(fù)制:一般情況下只會復(fù)制Mat對象的頭和指針部分,不會復(fù)制數(shù)據(jù)部分
Mat A = imread(imgFilePath);
Mat B(A) //只復(fù)制
- 完全復(fù)制:如果想把Mat對象的頭部和數(shù)據(jù)部分一起復(fù)制,可以通過以下兩個API實現(xiàn)
Mat F = A.clone();
Mat G;
A.copyTo(G)
Mat對象使用的四個要點
- 輸出圖像的內(nèi)存是自動分配的
- 使用OpenCV的C++接口, 不需要考慮內(nèi)存分配問題
- 賦值操作和拷貝構(gòu)造函數(shù)只會復(fù)制頭部分
- 使用clone與copyTo兩個函數(shù)實現(xiàn)數(shù)據(jù)完全復(fù)制
Mat對象創(chuàng)建
- cv::Mat::Mat構(gòu)造函數(shù)
Mat M(2, 2, CV_8UC3, Scalar(0, 0, 255))
其中前兩個參數(shù)分別表示行(row)跟列(column)、第三個CV_8UC3中的8表示每個通道占8位、 U表示無符號,
C表示char類型,3表示通道數(shù)目是3, 第四個參數(shù)是向量表示初始化每個像素值是多少,向量長度對應(yīng)通道
數(shù)目一致 - 創(chuàng)建多維數(shù)組cv::Mat::create
int sz[3] = [2, 2, 2];
Mat L(3, sz, CV_8UC1, Scalar::all(0));
cv::Mat::create實現(xiàn)
Mat M;
M.create(4, 3, CV_8UC2);
M = Scalar(127, 127);
定義小數(shù)組
Mat C = (Mat_<double>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
cout << "C = " << endl << C << endl << endl;
圖像操作
- 讀寫圖像
- 讀寫像素
- 修改像素值
- 灰度圖像
cvtColor(src, gray_src, CV_BGR2GRAY);
int height = gray_src.rows;
int width = gray_src.cols;
for(int row = 0; row < height; row++)
{
for (int col = 0; col < width; col++)
{
int gray = gray_src.at<uchar>(row, col);
gray_src.at<uchar>(row, col) = 255 - gray;
}
}
- RGB三通道圖像
for (int row = 0; row < height; row++)
{
for (int col = 0; col < width; col++)
{
if (nc == 1)
{
int gray = gray_src.at<uchar>(row, col);
gray_src.at<uchar>(row, col) = 255 - gray;
}
else if (nc == 3)
{
int b = src.at<Vec3b>(row, col)[0];
int g = src.at<Vec3b>(row, col)[1];
int r = src.at<Vec3b>(row, col)[2];
dst.at<Vec3b>(row, col)[0] = 255 - b;
dst.at<Vec3b>(row, col)[1] = 255 - g;
dst.at<Vec3b>(row, col)[2] = 255 - r;
}
}
}
其實以上代碼和 bitwise_not(src. dst) 這個api效果是一樣的
- 空白圖像賦值
- ROI選擇
Vec3b與Vec3F
- Vec3b對應(yīng)三通道的順序是blue、green、 red的uchar類型數(shù)據(jù)
- Vec3f對應(yīng)三通道的float類型數(shù)據(jù)
- 把CV_8UC1轉(zhuǎn)換到CV32F1實現(xiàn)如下:
src.convertTo(dst, CV_32F);
圖像混合
-
理論-線性混合操作
- 相關(guān)API(addWeighted)
void cv::addWeighted(inputArray src1,
double alpha,
inputArray src2,
double beta,
double gamma,
OutputArray dst,
int dtype = -1
)
參數(shù)1: 輸入圖像Mat - src1
參數(shù)2: 輸入圖像src1的alpha值
參數(shù)3: 輸入圖像Mat - src2
參數(shù)4: 輸入圖像src2的alpha值
參數(shù)5: gamma值
參數(shù)6: 輸出混合圖像
注意:兩張圖像的大小和類型必須一致才可以
- 代碼演示
double alpha = 0.5;
if (src1.rows == src2.rows && src1.cols == src2.cols && src1.type() == src2.type())
{
addWeighted(src1, alpha, src2, (1.0 - alpha), 0.0, dst);
//multiply(src1, src2, dst, 1.0);
namedWindow("blend demo ", CV_WINDOW_AUTOSIZE);
imshow("blend demo", dst);
}
調(diào)整圖像亮度和對比度
理論
圖像變換可以看作如下:
- 像素變換-點操作
-
鄰域操作-區(qū)域
調(diào)整圖像亮度和對比度屬于像素變換-點操作
代碼演示
for (int row = 0; row < height; row++)
{
for (int col = 0; col < width; col++)
{
if (nc == 1)
{
int v = src.at<uchar>(row, col);
dst.at<uchar>(row, col) = 255 - v;
}
else if (nc == 3)
{
int b = src.at<Vec3b>(row, col)[0];
int g = src.at<Vec3b>(row, col)[1];
int r = src.at<Vec3b>(row, col)[2];
dst.at<Vec3b>(row, col)[0] = saturate_cast<uchar>(b*alpha + beta);
dst.at<Vec3b>(row, col)[1] = saturate_cast<uchar>(g*alpha + beta);
dst.at<Vec3b>(row, col)[2] = saturate_cast<uchar>(r*alpha + beta);
}
}
其中alpha控制對比度,beta控制亮度。
重要的API
- Mat new_image = Mat::zeros(image.size(), image.type());創(chuàng)建一張跟原圖像大小和類型一致的空包圖像、像素值初始化為0.
- saturate_cast<uchar>(value)確保值大小范圍為0~255之間
- Mat.at<Vec3b>(y, x)[index] = value 給每個像素點每個通道賦值
繪制形狀和文字
-
使用cv::Point與cv::Scalar
- Point表示2D平面上一個點x, y
Point p;
p.x = 10;
p.y = 8;
or
p = Point(10, 8) - Scalar表示四個元素的向量
Scalar(a, b, c); //a = blue, b = green, c = red表示RGB三個通道 - Point表示2D平面上一個點x, y
-
繪制線、矩形、圓、橢圓等基本幾何形狀
- 畫線cv::line(LINE_4\LINE_8\LINE_AA)
void myLines() { Point p1 = Point(20, 30); Point p2; p2.x = 400; p2.y = 400; Scalar color = Scalar(0, 0, 255); line(src, p1, p2, color, 1, LINE_8); }- 畫橢圓cv::ellipse
void myEllipse() { Scalar color = Scalar(0, 255, 0); ellipse(src, Point(src.cols / 2, src.rows / 2), Size(src.cols / 4, src.rows / 8), 90, 0, 360, color, 2, LINE_8); }- 畫矩形cv::rectangle
void myRectangle() { Rect rect = Rect(200, 100, 300, 300); Scalar color = Scalar(255, 0, 0); rectangle(src, rect, color, 1, LINE_8); }- 畫圓cv::circle
void myCircle() { Scalar color = Scalar(0, 255, 255); Point center = Point(src.cols / 2, src.rows / 2); circle(src, center, 150, color, 1, LINE_8); }- 畫填充cv::fillPoly
void myPolygon() { Point pts[1][5]; pts[0][0] = Point(100, 100); pts[0][1] = Point(100, 200); pts[0][2] = Point(200, 200); pts[0][3] = Point(200, 100); pts[0][4] = Point(100, 100); const Point* ppts[] = { pts[0] }; int npt[] = { 5 }; Scalar color = Scalar(255, 12, 255); fillPoly(src, ppts, npt, 1, color, 8); } -
隨機生成(隨機數(shù)生成cv::RNG)與繪制文本
- 生成高斯隨機數(shù)gaussian(double sigma)
- 生成正態(tài)分布隨機數(shù)uniform(int a, int b)
void RandomLineDemo() { RNG rng(12345); Point pt1; Point pt2; Mat bg = Mat::zeros(src.size(), src.type()); namedWindow("random line demo", CV_WINDOW_AUTOSIZE); for (int i = 0; i < 65535; i++) { pt1.x = rng.uniform(0, src.cols); pt2.x = rng.uniform(0, src.cols); pt1.y = rng.uniform(0, src.rows); pt2.y = rng.uniform(0, src.rows); Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)); if (waitKey(50) > 0) break; line(bg, pt1, pt2, color, 1, 8); imshow("random line demo", bg); } }
模糊操作一
- 模糊原理
Smooth/Blur是圖像處理中最簡單和常用的操作之一
使用該操作的原因之一就是為了給圖像預(yù)處理時候減低噪聲
-
使用Smooth/Blur操作背后是數(shù)學的卷積計算
通常這些卷積算子計算都是線性操作,所以又叫線性濾波
-
歸一化盒子濾波(均值濾波)
-
高斯濾波

假設(shè)有6x6的圖像像素點矩陣
卷積過程:6x6上面是個3x3的窗口,從左到右,從上向下移動,黃色的每個像素點值之和取平均值賦給中心紅色像素作為它卷積處理之后新的像素值。每次移動一個像素格。(均值濾波)
- 相關(guān)API
-
均值模糊
blur(Mat src, Mat dst, Size(xradius, yradius), Point(-1, -1));
高斯模糊
GaussianBlur(Mat src, Mat dst, Size(11, 11), sigmax, sigmay);
其中Size(x, y), x, y必須為正數(shù)而且是奇數(shù)
-
模糊操作二
- 中值濾波
- 統(tǒng)計排序濾波器
- 中值對椒鹽噪聲有很好的抑制作用(巡線時可以用到)
- 雙邊濾波(高斯雙邊濾波)
- 均值模糊無法克服邊緣像素信息丟失缺陷。原因是均值濾波是基于平均權(quán)重。
- 高斯模糊部分克服了該缺陷,但是無法完全避免, 因為沒有考慮像素值的不同
-
高斯雙邊模糊-是邊緣保留的濾波方法,避免了邊緣信息丟失,保留了圖像輪廓不變
相關(guān)API
- 中值模糊medianBlur(Mat src, Mat dst, ksize)
中值模糊的ksize大小必須是大于1而且必須是奇數(shù) - 雙邊模糊bilateraFilter(src, dst, d=15, 150, 3);
- 15計算的半徑,半徑之內(nèi)的像素會被納入計算,如果提供-1則根據(jù)sigma space參數(shù)取值
- 150 - sigma color決定多少之內(nèi)的像素會被計算
- 3 - sigma space如果d的值大于0則聲明無效,否則根據(jù)它來計算d值
膨脹與腐蝕
形態(tài)學操作(morphology operators)
- 圖像形態(tài)學操作-基于形狀的一系列圖像處理操作的合集,主要是基于集合論基礎(chǔ)上的形態(tài)學數(shù)學
- 形態(tài)學有四個基本操作:腐蝕、膨脹、開、閉
- 膨脹與腐蝕是圖像處理中最常用的形態(tài)學操作手段
形態(tài)學操作-膨脹
- 跟卷積操作類似, 假設(shè)有圖像A和結(jié)構(gòu)元素B, 結(jié)構(gòu)元素B在A上面移動,其中B定義其中心為錨點,計算B覆蓋下A的最大像素值用來替換錨點的像素,其中B作為結(jié)構(gòu)體可以是任意形狀
形態(tài)學操作-腐蝕
- 腐蝕跟膨脹操作的過程類似,唯一不同的是以最小值替換錨點重疊下圖像的像素值
相關(guān)API
-
getStucturingElement(int shape, Size ksize, Point anchor)
- 形狀(MORPH_RECT\MORPH_CROSS\MORPH_ELLIPSE)
- 大小
- 錨點 默認是Point(-1, -1)意思就是中心像素
-
dilate(src, dst, kernel)
-
erode(src, dst, kernel)
代碼演示:
void CallBack_Demo(int, void*)
{
int s = element_size * 2 + 1;
Mat structureElement = getStructuringElement(MORPH_RECT, Size(s, s), Point(-1, -1));
//dilate(src, dst, structureElement, Point(-1, -1), 1);
erode(src, dst, structureElement, Point(-1, -1));
imshow(OUTPUT_WIN, dst);
}
動態(tài)調(diào)整結(jié)構(gòu)元素大小
- TrackBar-createTrackbar(const String &trackbarname, const String winName, int* value, int count, Trackbarcallback func, void* userdata=0)
其中最重要的是callback函數(shù)功能。如果設(shè)置為NULL就是說只有值update,但是不會調(diào)用callback的函數(shù)。
代碼演示:
createTrackbar("Element Size:", OUTPUT_WIN, &element_size, max_size, CallBack_Demo);
形態(tài)學操作
-
開操作-open
-
先腐蝕后膨脹
-
可以去掉小的對象,假設(shè)對象是前景色,背景是黑色
-
-
閉操作-close
-
先膨脹后腐蝕(bin2)
-
可以填充小的洞(fill hole), 假設(shè)對象是前景色,背景是黑色
-
總結(jié):閉操作用于去掉小黑點,開操作用于去掉小白點
-
形態(tài)學梯度-Morphological Gradient
-
膨脹減去腐蝕
- 又稱為基本梯度(其他還包括內(nèi)部梯度(原圖減去腐蝕)、方向梯度(在x和y方向進行一個梯度的計算))
-
-
頂帽-top hat
-
頂帽是原圖像與開操作(先腐蝕后膨脹)之間的差值圖像
-
-
黑帽-black hat
-
黑帽是閉操作(先膨脹后腐蝕)與原圖像之間的差值圖像
-
相關(guān)API
- morphologyEx(src, dst, CV_MOP_BLACKHAT,kernel);
- Mat src - 輸入圖像
- Mat dst - 輸出結(jié)果
- int OPT-CV_MOP_OPEN/CV_MOP_CLOSE/CV_MOP_GRADIENT/CV_MOP_TOPHAT/CV_MOP_BLACKHAT形態(tài)學操作類型
- Mat kernel 結(jié)構(gòu)元素
- int Iteration 迭代次數(shù), 默認是1
代碼示例:
namedWindow(OUTPUT_WIN, CV_WINDOW_AUTOSIZE);
Mat kernel = getStructuringElement(MORPH_RECT, Size(11, 11), Point(-1, -1));
morphologyEx(src, dst,CV_MOP_CLOSE, kernel, Point(-1, -1));
imshow(OUTPUT_WIN, dst);
形態(tài)學操作應(yīng)用-提取水平和垂直直線
原理方法
圖像形態(tài)學操作時候,可以通過自定義的結(jié)構(gòu)元素實現(xiàn)結(jié)構(gòu)元素對輸入圖像一些對象敏感、另外一些對象不敏感,這樣就會讓敏感的對象改變而不敏感的對象保留輸出。通過使用兩個最基本的形態(tài)學操作-膨脹與腐蝕,使用不同的結(jié)構(gòu)元素實現(xiàn)對輸入圖像的操作,得到想要的結(jié)果。
- 膨脹,輸出的像素值是結(jié)構(gòu)元素覆蓋下圖像的最大像素值
- 腐蝕,輸出的像素值是結(jié)構(gòu)元素覆蓋下圖像的最小像素值
結(jié)構(gòu)元素
- 上述膨脹與腐蝕過程可以使用任意的結(jié)構(gòu)元素
- 常見的形狀:矩形、圓、直線、磁盤形狀、磚石形狀等各種自定義形狀
提取步驟
- 輸入圖像彩色圖像imread
- 轉(zhuǎn)換為灰度圖像-cvtColor
- 轉(zhuǎn)換為二值圖像-adaptiveThreshold
- 定義結(jié)構(gòu)元素
- 開操作(腐蝕+膨脹)提取水平與垂直線
轉(zhuǎn)換為二值圖像-adaptiveThreshold
adativeThreshold(
Mat src, //輸入的灰度圖像
Mat dst, //二值圖像
double maxValue, //二值圖像最大值
int adaptiveMethod, //自適應(yīng)方法,只能其中之一
//ADAPTIVE_THRESH_MEAN_C, ADAPTIVE_THRESH_GAUSSIAN_C
int thresholdType, //閾值類型
int blocksize, //塊大小
double C //常量C 可以是正數(shù), 0, 負數(shù)
)

注意:自適應(yīng)閾值二值化的方式通過計算每個像素周圍bxb大小像素塊的加權(quán)均值并減去常量C得到,如果圖片像素比較高,像素塊又比較小,有可能會出現(xiàn)二值不好的情況,比如這樣,黑色的部分被二值成了白色。

代碼實現(xiàn):
void Binarry_Line()
{
Mat gray_src;
cvtColor(src, gray_src, CV_BGR2GRAY);
namedWindow("gray_image", CV_WINDOW_NORMAL);
cvResizeWindow("gray_image", 500, 500);
imshow("gray_image", gray_src);
Mat binImg;
threshold(gray_src, binImg, 80, 255, THRESH_BINARY_INV);
//adaptiveThreshold(gray_src, binImg, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 101, -2);
namedWindow("binarry image", CV_WINDOW_NORMAL);
cvResizeWindow("binarry image", 500, 500);
imshow("binarry image", binImg);
Mat hline = getStructuringElement(MORPH_RECT, Size(src.cols / 16, 1), Point(-1, -1));
Mat vline = getStructuringElement(MORPH_RECT, Size(1, src.rows / 16), Point(-1, -1));
Mat temp;
erode(binImg, temp, hline);
dilate(temp, dst, hline);
namedWindow("final image", CV_WINDOW_NORMAL);
cvResizeWindow("final image", 500, 500);
imshow("final image", dst);
//createTrackbar("Element Size:", OUTPUT_WIN, &element_size, max_size, CallBack_Demo);
//CallBack_Demo(0, 0);
}
圖像上采樣和降采樣
-
圖像金字塔概念
- 我們在圖像處理中常常會調(diào)整圖像大小,最常見的就是放大(zoom in)和縮小(zoom out)。
- 一個圖像金子塔是一系列的圖像組成,最底下一張是圖像尺寸最大,最上方的圖像尺寸最小,從空間上向下看就像一個古代的金字塔。
-
高斯金字塔
- 高斯金字塔是從底向下,逐層采樣得到。
- 降采樣之后圖像大小是原圖像MxN的M/2 x N/2, 就是對原圖像刪除偶數(shù)行與列,即得到降采樣之后上一層的圖片。
- 高斯金字塔的生成過程分兩步
- 對當前層進行高斯模糊
- 刪除當前層的偶數(shù)行與列
即可得到上一層的圖像,這樣上一層跟下一層相比,就只有它的1/4大小
拉普拉斯金字塔
-
高斯不同(Difference of Gaussian-DOG)
- 定義:就是把同一張圖像在不同的參數(shù)下做高斯模糊之后的結(jié)果相減,得到的輸出圖像,稱為高斯不同(DOG)
- 高斯不同是圖像的內(nèi)在特征, 在灰度圖像增強、角點檢測中經(jīng)常用到。
-
采樣相關(guān)API
- 上采樣(cv::pyrUp) - zoom in 放大
- 降采樣(cv::pyrDown) - zoom out 縮小
pyrUp(Mat src, Mat dst, Size(src.cols2, src.rows2))
生成的圖像是原圖在寬與高各放大兩倍
pyrDown(Mat src, Mat dst, Size(src.col/2, src.rows/2))
生成的圖像是原圖在寬與高各縮小1/2
代碼演示
void Sample()
{
//上采樣
Mat s_up;
pyrUp(src, s_up, Size(src.cols * 2, src.rows * 2));
namedWindow("sample up ", CV_WINDOW_AUTOSIZE);
imshow("sample up ", s_up);
//降采樣
Mat s_down;
pyrDown(src, s_down, Size(src.cols / 2, src.rows / 2));
namedWindow("sample down ", CV_WINDOW_AUTOSIZE);
imshow("sample down ", s_down);
Mat gray_src, g1, g2, dogImg;
cvtColor(src, gray_src, CV_BGR2GRAY);
GaussianBlur(gray_src, g1, Size(3, 3), 0, 0);
GaussianBlur(g1, g2, Size(3, 3), 0, 0);
subtract(g1, g2, dogImg, Mat());
normalize(dogImg, dogImg, 255, 0, NORM_MINMAX);
imshow("DOG Image", dogImg);
}
基本閾值操作(二值化)
-
圖像閾值(threshold)
- 閾值類型-閾值二值化(threshold binary)
左下方的圖表示圖像像素點Src(x, y)值分布情況, 藍色水平線表示閾值
- 閾值類型-閾值反二值化(threshold binary Inverted)
左下方的圖表示圖像像素點Src(x, y)值分布情況, 藍色水平線表示閾值
- 閾值類型-閾值取零(threshold to zero)
左下方的圖表示圖像像素點Src(x, y)值分布情況, 藍色水平線表示閾值
- 閾值類型-閾值反取零(threshold to zero inverted)
左下方的圖表示圖像像素點Src(x, y)值分布情況, 藍色水平線表示閾值
-
閾值類型
THRESH_OTSU和THRESH_TRIANGLE處理的圖像只能是8位的,一般來說是灰度圖像
代碼演示
void Threshold_Demo(int, void*)
{
cvtColor(src, gray_src, CV_BGR2GRAY);
threshold(gray_src, dst, threshold_value, threshold_max, type_value);
imshow(output_title, dst);
}
自定義線性濾波
卷積概念
- 卷積是圖像處理中的一個操作,是kernel在圖像的每個像素上的操作。
- kernel本質(zhì)上一個固定大小的矩陣數(shù)組,其中心點稱為錨點(anchor point)
卷積如何工作
-
把kernel放到像素數(shù)組之上,求錨點周圍覆蓋的像素乘積之和(包括錨點), 用來替換錨點覆蓋下像素點值稱為卷積處理。數(shù)學表達式如下:
卷積核又被稱為算子
常見算子:
-
Robert算子:
Robert算子也叫梯度算子,分別提取x方向的梯度和y方向的梯度
void Robert()
{
//Robert_X方向
Mat kernel_x = (Mat_<int>(2, 2) << 1, 0, 0, -1);
filter2D(src, dst, -1, kernel_x, Point(-1, -1), 0.0);
namedWindow("Robert_X", WINDOW_AUTOSIZE);
imshow("Robert_X", dst);
//Robert_Y方向
Mat ying;
Mat kernel_y = (Mat_<int>(2, 2) << 0, 1, -1, 0);
filter2D(src, ying, -1, kernel_y, Point(-1, -1), 0.0);
namedWindow(output_title, WINDOW_AUTOSIZE);
imshow(output_title, ying);
}
-
Sobel算子:表現(xiàn)方向上的差異
void Sobel()
{
//Sobel_X方向的差異
//Mat kernel_x = (Mat_<int>(3, 3) << -1, 0, 1, -2, 0, 2, -1, 0, 1);
//Sobel_Y方向的差異
Mat kernel_y= (Mat_<int>(3, 3) << -1, -2, -1, 0, 0, 0, 1, 2, 1);
filter2D(src, dst, -1, kernel_y, Point(-1, -1), 0.0);
namedWindow("Sobel", WINDOW_AUTOSIZE);
imshow("Sobel", dst);
}
-
拉普拉斯算子:提取邊緣體征
void Laplasian()
{
Mat kernel = (Mat_<int>(3, 3) << 0, -1, 0, -1, 4, -1, 0, -1, 0);
filter2D(src, dst, -1, kernel, Point(-1, -1), 0.0);
namedWindow("Laplasian", WINDOW_AUTOSIZE);
imshow("Laplasian", dst);
}
自定義卷積模糊
API:
filter2D(Mat src, //輸入圖像
Mat dst, //輸出圖像
Mat depth, //圖像深度 32/8
Mat kernel, //卷積核/模板
Point anchor, //錨點位置
double delta //計算出來的像素+delta
)
代碼演示:
void Customer_Filter()
{
while (true)
{
char c = waitKey(500);
if (c == 27)
{
break;
}
int ksize = 4 + (index % 5) * 2 + 1;
Mat kernel = Mat::ones(Size(ksize, ksize), CV_32F) / (float)(ksize * ksize);
filter2D(src, dst, -1, kernel, Point(-1, -1));
index++;
namedWindow("customer_filter", WINDOW_AUTOSIZE);
imshow("customer_filter", dst);
}
}
處理卷積邊緣
卷積邊界問題
- 圖像卷積的時候邊界像素,不能被卷積操作,原因在于邊界像素沒有完全跟kernel重疊,所以當3x3濾波時候有1個像素的邊緣沒有被處理,5x5濾波的時候有2個像素的邊緣沒有被處理。
處理邊緣
- 在卷積開始之前增加邊緣像素,填充的像素值為0或者RGB黑色,比如3x3在四周各填充1個像素的邊緣,這樣就確保圖像的邊緣被處理,在卷積處理之后再去掉這些邊緣。opencv中默認的處理方法是:BORDER_DEFAULT, 此外常用的還有如下幾種:
- BODER_CONSTANT - 填充邊緣用指定像素值
- BODER_REPLICATE - 填充邊緣像素用已知的邊緣像素值。
- BODER_WRAP - 用另外一邊的像素來補償填充
API說明
copyMakeBorder(
Mat src, //輸入圖像
Mat dst, //添加邊緣圖像
int top, //邊緣長度, 一般上下左右都取相同值
int bottom,
int left,
int right,
int boderType,
Scalar value
)
Sobel算子
卷積應(yīng)用-圖像邊緣提取
- 邊緣是什么 - 是像素值發(fā)生躍遷的地方,是圖像的顯著特征之一,在圖像特征提取、對象檢測、模式識別等方面都有重要的作用
- 如何捕捉/提取邊緣-對圖像求它的一階導(dǎo)數(shù)
delta = f(x) - f(x-1), delta越大,說明像素在x方向變化越大,邊緣信號越強
Sobel算子
- 是離散微分算子(discrete differentiation operator),用來計算圖像灰度的近似梯度
- Sobel算子功能集合高斯平滑和微分求導(dǎo)
-
又被稱為一階微分算子, 求導(dǎo)算子, 在水平和垂直兩個方向上求導(dǎo),得到圖像x方法與y方向梯度圖像
-
求取導(dǎo)數(shù)的近似值,kennel=3時不是很準確,OpenCV使用改進版本Scharr函數(shù),算子如下:
API說明cv::Sobel
cv::Sobel(
InputArray Src, //輸入圖像
OutputArray dst, //輸出圖像,大小與輸入圖像一致
int depth, //輸出圖像深度
int dx, //x方向, 幾階導(dǎo)數(shù)
int dy, //y方向, 幾階導(dǎo)數(shù)
int ksize, //Sobel算子kernel大小, 必須是1、 3、5、 7、(單數(shù))
double scale = 1,
double delta = 0,
int borderType = BORDER_DEFAULT
)

Laplacian算子
理論
在二階微分的時候, 最大變化處的值為零即邊緣是零值。通過二階導(dǎo)數(shù)計算,依據(jù)此理論我們可以計算圖像二階導(dǎo)數(shù),提取邊緣。

Laplacian算子
-
拉普拉斯算子(Laplacian operator)
相關(guān)API:cv::Laplance
處理流程
- 高斯模糊-去噪聲GaussianBlur()
- 轉(zhuǎn)換為灰度圖像cvtColor()
- 拉普拉斯-二階導(dǎo)數(shù)計算Laplacian()
- 取絕對值convertScaleAbs()
- 顯示結(jié)果
Canny邊緣檢測
圖像的邊緣檢測的原理是檢測出圖像中所有灰度值變化較大的點,而且這些點連接起來就構(gòu)成了若干線條,這些線條就可以稱為圖像的邊緣
Canny算法介紹
- Canny是邊緣檢測算法,在1986年提出。
- 是一個很好的邊緣檢測器
- 很常用也很實用的圖像處理方法
Canny算法過程-五步
- 高斯模糊-GaussianBlur
- 灰度轉(zhuǎn)換-cvtColor
- 計算梯度-Sobel/Scharr
- 非最大信號抑制
- 高低閾值輸出二值圖像
Canny算法介紹-非最大信號抑制

這里不是很明白
Canny算法介紹-高低閾值輸出二值圖像
- T1, T2為閾值, 凡是高于T2的都保留, 凡是小于T1都丟棄, 從高于T2的像素出發(fā), 凡是大于T1而且相互連接的,都保留。最終得到一個輸出二值圖像。
- 推薦的高低閾值比值為T2:T1 = 3:1 / 2:1其中T2為高閾值, T1為低閾值。
相關(guān)API-cv::Canny
Canny(
inputArray src, //8-bit的輸入圖像
OutputArray edges, //輸出邊緣圖像, 一般都是二值圖像
double threshold1, //低閾值, 常取高閾值的1/2, 或者1/3
double threshold2, //高閾值
int aptertureSize, //Sobel算子的size, 通常3x3, 取值3
boot L2gradient //選擇true表示是L2來歸一化, 否則用L1歸一化
)
尋找輪廓
相關(guān)API
void cv::findContours ( InputOutputArray image,
OutputArrayOfArrays contours,
OutputArray hierarchy,
int mode,
int method,
Point offset = Point()
)
參數(shù)介紹:
image:輸入圖像,圖像必須為8-bit單通道圖像,圖像中的非零像素將被視為1,0像素保留其像素值,故加載圖像后會自動轉(zhuǎn)換為二值圖像。我們同樣可以使用cv::compare,cv::inRange,cv::threshold,cv::adaptiveThreshold,cv::Canny等函數(shù)來創(chuàng)建二值圖像,,如果第四個參數(shù)為cv::RETR_CCOMP或cv::RETR_FLOODFILL,輸入圖像可以是32-bit整型圖像(CV_32SC1)
contours:檢測到的輪廓,每個輪廓都是以點向量的形式進行存儲即使用point類型的vector表示
hierarchy:可選的輸出向量(std::vector),包含了圖像的拓撲信息,作為輪廓數(shù)量的表示hierarchy包含了很多元素,每個輪廓contours[i]對應(yīng)hierarchy中hierarchy[i][0]~hierarchy[i][3],分別表示后一個輪廓,前一個輪廓,父輪廓,內(nèi)嵌輪廓的索引,如果沒有對應(yīng)項,則相應(yīng)的hierarchy[i]設(shè)置為負數(shù)。
mode:輪廓檢索模式,可以通過cv::RetrievalModes()查看詳細信息,如下
RETR_EXTERNAL:表示只檢測最外層輪廓,對所有輪廓設(shè)置hierarchy[i][2]=hierarchy[i][3]=-1
RETR_LIST:提取所有輪廓,并放置在list中,檢測的輪廓不建立等級關(guān)系
RETR_CCOMP:提取所有輪廓,并將輪廓組織成雙層結(jié)構(gòu)(two-level hierarchy),頂層為連通域的外圍邊界,次層位內(nèi)層邊界
RETR_TREE:提取所有輪廓并重新建立網(wǎng)狀輪廓結(jié)構(gòu)
RETR_FLOODFILL:官網(wǎng)沒有介紹,應(yīng)該是洪水填充法
method:輪廓近似方法可以通過cv::ContourApproximationModes()查看詳細信息
CHAIN_APPROX_NONE:獲取每個輪廓的每個像素,相鄰的兩個點的像素位置差不超過1
CHAIN_APPROX_SIMPLE:壓縮水平方向,垂直方向,對角線方向的元素值,保留該方向的中點坐標,如果一個矩形輪廓只需4個點來保存輪廓信息
CHAIN_APPROX_TC89_L1和CHAIN_APPROX_TC89_KCOS使用Teh-Chinl鏈逼近算法中的一種
Rect boundingRect(InputArray points)
參數(shù)介紹:
points:輸入信息,可以為包含點的容器(vector)或是Mat。
返回包覆輸入信息的最小正矩形。
RotatedRect minAreaRect(InputArray points)
參數(shù)介紹:
points:輸入信息,可以為包含點的容器(vector)或是Mat。
返回包覆輸入信息的最小斜矩形。
boundingRect和minAreaRect的區(qū)別如下圖

void minEnclosingCircle(InputArray points, Point2f& center, float& radius)
參數(shù)介紹:
points:輸入信息,可以為包含點的容器(vector)或是Mat。
center:包覆圓形的圓心。
radius:包覆圓形的半徑。
霍夫變換-直線
- Hough Line Transform用來做直線檢測
- 前提條件-邊緣檢測已經(jīng)完成
- 平面空間到極坐標空間轉(zhuǎn)換


公式由下往上倒推
霍夫直線變換介紹
- 對于任意一條直線上的所有點來說,變換到極坐標中,從[0~360]空間, 可以得到r的大小
- 屬于同一條直線上點在極坐標空間(r, theta)必然在一個點上有最強的信號出現(xiàn),根據(jù)此反算到平面坐標中就可以得到直線上各點的像素坐標。從而得到直線。
ps:不是很明白
相關(guān)API學習
- 標準的霍夫變換cv::HoughLines從平面坐標轉(zhuǎn)換到霍夫空間,最終輸出是(theta, r)表示極坐標空間
cv::HoughLines(
InputArray src, //輸出圖像,必須8bit的灰度圖像
OutputArray lines, //輸出的極坐標來表示直線
double rho, //生成極坐標時候的像素掃描步長
double theta, //生成極坐標時候的角度步長, 一般取值CV_PI/180
int threshold, //閾值, 只有獲得足夠交點的極坐標點才被看作直線
double srn=0, //是否應(yīng)用多尺度的霍夫變換,如果不是設(shè)置0表示經(jīng)典霍夫變換(就是進行金字塔的操作)
double stn=0, //是否應(yīng)用多尺度的霍夫變換,不過不是設(shè)置0表示經(jīng)典霍夫變換
double min_theta=0, //表示角度掃描范圍0~180之間, 默認即可
double max_theta=CV_PI
)
- 霍夫變換直線概率cv::HoughLinesP最終輸出是直線的兩個點(x0,y0,x1, y1)
cv::HoughLineP(
InputArray src, //輸入圖像,必須8-bit的灰度圖像
OutputArray lines, //輸出的坐標來表示直線
double rho, //生成極坐標時候的像素掃描步長
double theta, //生成極坐標的時候的角度步長,一般取值CV_PI/180
int threshold, //閾值,只有獲得足夠交點的極坐標才被看作是直線
double minLineLength=0, //最小直線長度
double maxLineGap=0 //最大間隔
霍夫圓檢測
霍夫圓檢測原理

- 因為霍夫圓檢測對噪聲比較敏感,所以首先要對圖像做中值濾波。
- 基于效率考慮,OpenCV中實現(xiàn)的霍夫變換圓檢測是基于圖像梯度的實現(xiàn),分為兩步:
- 檢測邊緣,發(fā)現(xiàn)可能的圓心
- 基于第一步的基礎(chǔ)上從候選的圓心開始計算最佳半徑大小
相關(guān)API cv::HoughCircles
HoughCircles(
InputArray image, //輸入圖像,必須是8位的單通道灰度圖像
OutputArray circles, //輸出結(jié)果,發(fā)現(xiàn)的圓信息
int method, //方法 - HOUGH_GRADIENT
double mindist, //10 最短距離 - 可以分辨是兩個圓的,否則認為是同心圓
double param1, //canny edge detection high threshold
double param2, //中心點累加器閾值 - 候選圓心
int minradius, //最小半徑
int maxradius //最大半徑
)
像素重映射
什么是像素重映射
簡單點說就是把輸入圖像中各個像素按照一定的規(guī)則映射到另外一張圖像的對應(yīng)位置上去,形成一張新的圖像


API介紹cv::remap
Remap(
InputArray src, //輸入圖像
OutputArray dst, //輸出圖像
InputArray map1, //x映射表 CV_32FC1 / CV_32FC2
InputArray map2, //y映射表
int interpolation, //選擇的插值方法, 常見線性插值, 可選擇立方等
int boderMode, //BODER_CONSTANT
const Scalar boderValue //color
)
python部分
import cv2 as cv
色彩空間轉(zhuǎn)換
cv.cvtColor()
參數(shù):
imgae: 圖片
gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
cv.imshow("gray", gray)
hsv = cv.cvtColor(image, cv.COLOR_BGR2HSV)
cv.imshow("hsv", hsv)
yuv = cv.cvtColor(image, cv.COLOR_BGR2YUV)
cv.imshow("yuv", yuv)
cv.inRange()
通道的分離與合并
cv.split(): 通道的分離
cv.merge(): 通道的合并
像素運算
加: cv.add(m1, m2)
減: cv.subtract(m1, m2)
乘: cv.multiply(m1, m2)
除: cv.divide(m1,m2)
均值&方差: cv.meanStdDev(m1)
與: cv.bitwise_and(m1,m2)
或: cv.bitwise_or(m1, m2)
非: cv.bitwise_not(m1)
cv.addWeighted: 調(diào)整對比度與兩度
ROI(Range Of Interest)
泛洪填充
- FLOODFILL_FIXED_RANGE:改變圖像,泛洪填充
- FLOODFILL_MASK_ONLY: 不改變圖像, 只填充遮罩層本身、忽略新的顏色值參數(shù)
- floodFill(Mat image, Mat mask, Point seedPoint, Scalar newVal)
- floodFill(image, mask, seedPoint, newVal, rect, loDiff, upDiff, flags)
src(seed.x, seed.y) - loDiff <= src(x, y) <= src(seed.x, seed.y) + upDiff
def fill_color_demo(image):
copyImg = image.copy()
h, w = image.shape[:2]
mask = np.zeros([h+2, w+2], np.uint8)
cv.floodFill(copyImg, mask, (30, 30), (0, 0, 255), (100, 100, 100), (50, 50, 50), cv.FLOODFILL_FIXED_RANGE)
cv.imshow("fill_color_demo", copyImg)
各個參數(shù)的作用
從seedPoint這個點取出的像素值BGR, 然后這個BGR減去loDiff是最小的閾值,加上upDiff是最大的閾值。然后所有在這個閾值范圍內(nèi)的像素填充newVal
模糊操作
-
均值模糊
高斯模糊 - 中值模糊: 椒鹽模糊
- 自定義模糊:銳化
def custom_blur_demo(image):
kernel = np.ones([5, 5], np.float32)/25
dst = cv.filter2D(image, -1, kernel=kernel)
cv.imshow("custom_blur_demo", dst)
邊緣保留濾波(EPF)
- 高斯雙邊
def bi_demo(image):
dst = cv.bilateralFilter(image, 0, 100, 15)
cv.imshow("bi_demo", dst)
- 均值遷移
def shift_demo(image):
dst = cv.pyrMeanShiftFiltering(image, 10, 50)
cv.imshow("shift_demo", dst)
直方圖的繪制
def plot_demo(image):
plt.hist(image.ravel(), 256, [0, 256])
plt.show()

def image_hist(image):
color = ('blue', 'green', 'red')
for i, color in enumerate(color):
hist = cv.calcHist([image], [i], None, [256], [0, 256])
plt.plot(hist, color=color)
plt.xlim([0, 256])
plt.show()

-
直方圖均衡化:對比度增強
全部直方圖均衡化
def equalHist_demo(image):
gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
dst = cv.equalizeHist(gray)
cv.imshow("equalHist_demo", dst)
局部直方圖均衡化
def clahe_demo(image):
gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
clahe = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
dst = clahe.apply(gray)
cv.imshow("clahe_demo", dst)
直方圖均衡化公式
巴氏距離
相關(guān)性
卡方
直方圖的降維計算不明白是怎么算出來的
(這里不明白公式的推導(dǎo))
- back_projection
def back_projection_demo():
sample = cv.imread('sample.png')
target = cv.imread('universe2.png')
roi_hsv = cv.cvtColor(sample, cv.COLOR_BGR2HSV)
target_hsv = cv.cvtColor(target, cv.COLOR_BGR2HSV)
cv.imshow("roi_hsv", roi_hsv)
cv.imshow("target_hsv", target_hsv)
roiHist = cv.calcHist([roi_hsv], [0, 1], None, [32, 32], [0, 100, 0, 256])
cv.normalize(roiHist, roiHist, 0, 255, cv.NORM_MINMAX)
dst = cv.calcBackProject([target_hsv], [0, 1], roiHist, [0, 180, 0, 256], 1)
cv.imshow("bacProjectionDemo", dst)
備注:以后查閱各API的參數(shù)及使用方法
模板匹配
簡單來說就是,在源圖像中尋找目標圖像的位置
- 標準平方差匹配:cv.TM_SQDIFF_NORMED
- 標準相關(guān)匹配:cv.TM_CCORR_NORMED
- 標準相關(guān)系數(shù)匹配: cv.TM_CCOEFF_NORMED
圖像二值化
OpenCV中圖像二值化的方法:
- OTSU
- Triange
- 手動與自動
全局閾值
def treshold_demo(image):
gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
ret, binarry = cv.threshold(gray, 127, 255, cv.THRESH_BINARY)
print("threshold value %s" % ret)
cv.imshow("binarry", binarry)
局部閾值
自適應(yīng)閾值
def local_threshold(image):
gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
binarry = cv.adaptiveThreshold(gray, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 25, 10)
cv.imshow("binarry", binarry)
超大圖像二值化: 如果圖像過大,一些噪聲或者光線就會影響二值化效果,所以通過分塊后進行閾值化效果更好。
- 分塊
- 全局閾值&局部閾值
- 圖像大小獲取
- 圖像ROI與空白圖像過濾
- 圖像二值化
圖像金字塔原理
reduce = 高斯模糊 + 降采樣
expand = 擴大 + 卷積
降采樣: PryDown
還原: PryUp
高斯金字塔與拉普拉斯金字塔
在使用拉普拉斯金字塔時注意,我們選用的圖片大小必須是2^n大小,或者是一個寬高相等的圖片
如何計算圖像邊緣?
1、對圖像求一階導(dǎo)數(shù),導(dǎo)數(shù)最大的時候,圖像的變化率最大,也就是圖像的邊緣。
2、對圖像求二階導(dǎo)數(shù),二階導(dǎo)數(shù)為0的時候,一階導(dǎo)數(shù)最大,源圖像變化率最大,也就是圖像的邊緣。
一階導(dǎo)數(shù)與sobel算子(scharr算子效果更明顯,但同時也會引入更多噪聲)
二階導(dǎo)數(shù)與拉普拉斯算子
Canny邊緣提取
1、高斯模糊 - GaussianBlur
2、灰度轉(zhuǎn)換 - cvtColor
3、計算梯度 - Sobel/Scharr
4、非最大信號抑制
5、高低閾值輸出二值圖像
直線檢測
霍夫直線變換(Hough Line Transform)
cv.HoughLines
前提條件: 邊緣檢測已經(jīng)完成
平面空間到極坐標空間轉(zhuǎn)換
霍夫圓檢測
霍夫圓變換原理
- 從平面坐標到極坐標轉(zhuǎn)換三個參數(shù)C(x0, y0, r)其中x0, y0是圓心
- 假設(shè)平面坐標的任意一個圓上的點,轉(zhuǎn)換到極坐標中:C(x0, y0, r)處有最大值, 霍夫變換正是利用這個原理實現(xiàn)圓的檢測(?)
現(xiàn)實考量:
- 因為霍夫圓檢測對噪聲比較敏感,所以要首先對圖像做中值濾波。
- 基于效率考慮, OpenCV中實現(xiàn)的霍夫變換圓檢測是基于圖像梯度的實現(xiàn),分為兩步:
- 檢測邊緣, 發(fā)現(xiàn)可能的圓心
- 基于第一步的基礎(chǔ)上從候選圓心開始計算最佳半徑大小。
輪廓發(fā)現(xiàn)
輪廓發(fā)現(xiàn):
是基于圖像邊緣提取的基礎(chǔ)尋找對象的輪廓的方法。
所以邊緣提取的閾值選定會影響最終輪廓發(fā)現(xiàn)結(jié)果。
API介紹:
- findContours發(fā)現(xiàn)輪廓
- drawContours繪制輪廓
如何利用梯度來避免閾值煩惱
對象檢測
- 弧長和面積
- 輪廓發(fā)現(xiàn)
- 計算每個輪廓的弧長與面積, 像素單位
- 多邊形擬合
- 獲取輪廓的多邊形擬合結(jié)果
- approxPolyDP參數(shù):
- contour
- epsilon越小越折線越逼近真實形狀
- close - 是否為閉合區(qū)域
- 幾何矩計算
圖像形態(tài)學
- 是圖像處理學科的一個單獨分支學科
- 灰度與二值圖像處理中重要手段
- 是由數(shù)學的集合論等相關(guān)理論發(fā)展起來的
膨脹與腐蝕
-
膨脹的作用
- 對象大小增加一個像素(3x3)
- 平滑對象邊緣
- 減少或者填充對象之間的距離
-
腐蝕(erode)的作用
- 對象大小減小一個像素(3x3)
- 平滑對象邊緣
- 弱化或者分割圖像之間的半島型連接
開閉操作
-
開操作
- 圖像形態(tài)學的重要操作之一, 基于膨脹與腐蝕操作組合形成的。
- 主要是應(yīng)用在二值圖像分析中,灰度圖像亦可。
- 開操作 = 腐蝕 + 膨脹, 輸入圖像 + 結(jié)構(gòu)元素
-
閉操作
- 圖像形態(tài)學的重要操作之一, 基于膨脹與腐蝕操作組合形成的。
- 主要是應(yīng)用在二值圖像分析中,灰度圖像亦可。
- 閉操作 = 膨脹 + 腐蝕, 輸入圖像 + 結(jié)構(gòu)元素
-
開閉操作作用
- 去除小的干擾塊 - 開操作
- 填充閉合區(qū)域 - 閉操作
- 水平或者垂直線提取
其他形態(tài)學操作
- 頂帽:原圖像與開操作之間的差值圖像
- 黑帽:閉操作與原圖像之間的差值圖像
-
形態(tài)學梯度
- 基本梯度:用膨脹后的圖像減去腐蝕后的圖像得到差值圖像。
- 內(nèi)部梯度:用原圖像減去腐蝕之后的圖像得到的差值圖像。
- 外部梯度:圖像膨脹之后再減去原來的圖像得到的差值圖像。
分水嶺算法
- 距離變換
- 分水嶺變換介紹
`





























