目錄
OpenCV環(huán)境搭建
加載 修改 保存圖像
矩陣的掩膜操作
Mat對象
圖像操作
圖像混合
調(diào)整圖像亮度與對比度
繪制形狀與文字
模糊圖像一
模糊圖像二
膨脹與腐蝕
形態(tài)學(xué)操作
形態(tài)學(xué)操作應(yīng)用-提取水平線和垂直線
圖像金字塔-上采集與降采集
基本閾值操作
自定義線性濾波
處理邊緣
Sobel算子
Laplance算子
Canny邊緣檢測
霍夫變換-直線
霍夫圓變換
像素重映射
直方圖均衡化
直方圖計算
直方圖比較
直方圖反向透射
模板匹配
輪廓發(fā)現(xiàn)
凸包
輪廓周圍繪制矩形框和圓形框
圓形矩
點多邊形測試
基于距離變換與分水嶺的圖像分割
OpenCV環(huán)境搭建
- 配置環(huán)境變量
- ?新建項目
視圖 - 其他窗口 - 屬性管理器
?添加附加依賴項
?測試代碼(這里應(yīng)當注意,在進行測試的時候,注意相應(yīng)位數(shù))
#include<opencv2\opencv.hpp>
using namespace cv;
int main(int argc, char** argv)
{
Mat src = imread("C:\\Users\\td\\Desktop\\he.jpeg");
if (src.empty())
{
printf("could not load image ...\n");
return -1;
}
namedWindow("test opencv setup ", CV_WINDOW_AUTOSIZE);
imshow("test opencv setup", src);
waitKey(0);
return 0;
}
加載 修改 保存圖像
- imread函數(shù)
imread功能是加載圖像文件成為一個Mat對象,其中第一個參數(shù)表示圖像文件名稱
第二個參數(shù),表示加載的圖像是什么類型,支持常見的三個參數(shù)值
IMREAD_UNCHANGED (<0) 表示加載原圖,不做任何改變
IMREAD_GRAYSCALE (0)表示把原圖作為灰度圖像加載進來,如下這行代碼就是加載灰度圖像.
Mat src = imread("C:\\Users\\td\\Desktop\\he.jpeg", IMREAD_GRAYSCALE);
IMREAD_COLOR (>0) 表示把原圖作為RGB圖像加載進來
注意:OpenCV支持JPG、PNG、TIFF等常見格式圖像文件加載
- namedWindow函數(shù)
namedWindos功能是創(chuàng)建一個OpenCV窗口,它是由OpenCV自動創(chuàng)建與釋放,你無需取銷毀它。 常見用法namedWindow("Window Title", WINDOW_AUTOSIZE)
WINDOW_AUTOSIZE會自動根據(jù)圖像大小,顯示窗口大小,不能人為改變窗口大小 WINDOW_NORMAL,跟QT集成的時候會使用,允許修改窗口大小。
- imshow函數(shù)
imshow根據(jù)窗口名稱顯示圖像到指定的窗口上去,第一個參數(shù)是窗口名稱,第二參數(shù)是Mat對象
- cvtColor函數(shù)
cvtColor的功能是把圖像從一個彩色空間轉(zhuǎn)換到另外一個色彩空間,有三個參數(shù),第一個參數(shù)表示源圖像、第二參數(shù)表示色彩空間轉(zhuǎn)換之后的圖像、第三個參數(shù)表示源和目標色彩空間如:COLOR_BGR2HLS 、COLOR_BGR2GRAY 等?
- imwrite函數(shù)
保存圖像文件到指定目錄路徑
只有8位、16位的PNG、JPG、Tiff文件格式而且是單通道或者三通道的BGR的圖像才可以通過這種方式保存
保存PNG格式的時候可以保存透明通道的圖片
可以指定壓縮參數(shù)
#include<opencv2\opencv.hpp>
using namespace cv;
int main(int argc, char** argv)
{
Mat src = imread("C:\\Users\\td\\Desktop\\he.jpeg");
if (src.empty())
{
printf("could not load image ...\n");
return -1;
}
namedWindow("test opencv setup ", CV_WINDOW_AUTOSIZE);
imshow("test opencv setup", src);
waitKey(20);
//轉(zhuǎn)換相應(yīng)的色彩空間
namedWindow("output windows", CV_WINDOW_AUTOSIZE);
Mat output;//存儲轉(zhuǎn)換之后的圖像
cvtColor(src, output,CV_BGR2BGR555);
imshow("output windows", output);
//保存圖片的過程
imwrite("D:/zhubajie.png", output);
waitKey(0);
return 0;
}
矩陣的掩膜操作
- ?掩膜操作:實現(xiàn)圖像對比度調(diào)整(來重新計算每個像素的像素值)
????????紅色是中心像素,從上到下,從左到右對每個像素做同樣的處理操作,得到最終結(jié)果就是對比度提高之后的輸出圖像Mat對象.如下圖所示:
????????計算公式是如下所示:
- ?圖像的通道數(shù)
基本上,描述一個像素點,如果是灰度,那么只需要一個數(shù)值來描述它,就是單通道。
如果一個像素點,有RGB三種顏色來描述它,就是三通道.
- 獲取圖像像素指針
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>
saturate_cast<uchar>(-100),返回 0。
saturate_cast<uchar>(288),返回255
saturate_cast<uchar>(100),返回100 這個函數(shù)的功能是確保RGB值得范圍在0~255之間
- 矩陣的掩膜操作的過程是如下代碼所示:
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
int main(int argc, char** argv) {
Mat src, dst;
src = imread("C:\\Users\\td\\Desktop\\he.jpeg");
if (!src.data) {
printf("could not load image...\n");
return -1;
}
namedWindow("input image", CV_WINDOW_AUTOSIZE);
imshow("input image", src);
/*
int cols = (src.cols-1) * src.channels();//寬度上面提前打算
int offsetx = src.channels();
int rows = src.rows;
dst = Mat::zeros(src.size(), src.type());//獲取和src Mat類型
for (int row = 1; row < (rows - 1); row++) {//第一行和最后一行是很難形成相應(yīng)的圖形,因此,進行一個省略的操作.
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]));
}
}
namedWindow("contrast image demo", CV_WINDOW_AUTOSIZE);
imshow("contrast image demo", dst);
*/
double t = getTickCount();
Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);//定義一個掩膜
filter2D(src, dst, src.depth(), kernel);//掩膜操作,將src之中的計算結(jié)果直接輸出到dst之中
double timeconsume = (getTickCount() - t) / getTickFrequency();
printf("tim consume %.2f\n", timeconsume);
namedWindow("contrast image demo", CV_WINDOW_AUTOSIZE);
imshow("contrast image demo", dst);
waitKey(0);
return 0;
}
Mat對象
- 什么叫做Mat對象??
????????在人眼之中,這是一個一個對象,但是在計算機的眼中,這是一個個數(shù)據(jù).?
- 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語言風(fēng)格的數(shù)據(jù)結(jié)構(gòu),需要開發(fā)者自己分配與管理內(nèi)存,對大的程序使用它容易導(dǎo)致內(nèi)存泄漏問題(建議不進行使用)
- 常用的構(gòu)造函數(shù)
- 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)建
(1)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ù)目一致
(2)cv::Mat::create 創(chuàng)建多維數(shù)組 ?? ?
int sz[3] = {2,2,2}; ? ? ?? ?Mat ?L(3,sz, CV_8UC1, Scalar::all(0)); 一般我們是用不到這個的,進行了解一下就行.
- 一些代碼
#include <opencv2/opencv.hpp> #include <iostream> using namespace std; using namespace cv; int main(int argc, char** argv) { Mat src; src = imread("C:\\Users\\td\\Desktop\\zhu.jpg"); if (src.empty()) { cout << "could not load image..." << endl; return -1; } namedWindow("input", CV_WINDOW_AUTOSIZE); imshow("input", src); /*Mat dst; dst = Mat(src.size(), src.type()); dst = Scalar(127, 0, 255); namedWindow("output", CV_WINDOW_AUTOSIZE); imshow("output", dst);*/ Mat dst; //src.copyTo(dst); namedWindow("output", CV_WINDOW_AUTOSIZE); cvtColor(src, dst, CV_BGR2GRAY); printf("input image channels : %d\n", src.channels()); printf("output image channels : %d\n", dst.channels()); int cols = dst.cols; int rows = dst.rows; printf("rows : %d cols : %d\n", rows, cols); const uchar* firstRow = dst.ptr<uchar>(0); printf("fist pixel value : %d\n", *firstRow); Mat M(100, 100, CV_8UC1, Scalar(127)); //cout << "M =" << endl << M << endl; Mat m1; m1.create(src.size(), src.type()); m1 = Scalar(0, 0, 255); Mat csrc; Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0); filter2D(src, csrc, -1, kernel); Mat m2 = Mat::eye(2, 2, CV_8UC1); cout << "m2 =" << endl << m2 << endl; Mat m2 = Mat::zeros(2, 2, CV_8UC1);//純黑的圖片 imshow("output", m2); waitKey(0); return 0; }
圖像操作
- 讀取像素
讀一個GRAY像素點的像素值(CV_8UC1)
Scalar intensity = img.at<uchar>(y, x); 或者 Scalar intensity = img.at<uchar>(Point(x, y));
讀一個RGB像素點的像素值
Vec3f intensity = img.at<Vec3f>(y, x); float blue = intensity.val[0]; float green = intensity.val[1]; float red = intensity.val[2];
- 修改像素值
- 灰度圖像
img.at<uchar>(y, x) = 128;
RGB三通道圖像
img.at<Vec3b>(y,x)[0]=128; // blue
img.at<Vec3b>(y,x)[1]=128; // green
img.at<Vec3b>(y,x)[2]=128; // red
空白圖像賦值 ????????img = Scalar(0);
ROI選擇 ????????Rect r(10, 10, 100, 100); ????????Mat smallImg = img(r);
- 代碼示例:
#include <opencv2/core/core.hpp> #include <opencv2/imgcodecs.hpp> #include <opencv2/opencv.hpp> #include <opencv2/highgui/highgui.hpp> #include <iostream> using namespace cv; using namespace std; int main(int argc, char** args) { Mat image = imread("C:/Users/td/Desktop/zhu.jpg", IMREAD_COLOR); if (image.empty()) { cout << "could not find the image resource..." << std::endl; return -1; } Mat grayImg; Mat dst; cvtColor(image, grayImg, COLOR_BGR2GRAY); //單通道的一個像素提取過程 int height = image.rows; int width = image.cols; int channels = image.channels(); printf("height=%d width=%d channels=%d", height, width, channels); for (int row = 0; row < height; row++) { for (int col = 0; col < width; col++) { int grey = image.at<uchar>(row,col); } } //三通道的像素提取過程 int height = image.rows; int width = image.cols; int channels = image.channels(); printf("height=%d width=%d channels=%d", height, width, channels); for (int row = 0; row < height; row++) { for (int col = 0; col < width; col++) { if (channels == 3) { int b = image.at<Vec3b>(row, col)[0]; // blue int g = image.at<Vec3b>(row, col)[1]; // green int r = image.at<Vec3b>(row, col)[2]; // red } } } bitwise_not(image,dst);//將255-現(xiàn)在的像素值得到相應(yīng)的結(jié)果. }
圖像混合
- 理論-線性混合操作
上述表示是兩幅圖像進行一個合并的過程,其中a代表著相應(yīng)的權(quán)重,上述是線型混合理論.
- ?API:addWeighted
參數(shù)1:輸入圖像Mat – src1
參數(shù)2:輸入圖像src1的alpha值 權(quán)重
參數(shù)3:輸入圖像Mat – src2
參數(shù)4:輸入圖像src2的beta值 權(quán)重
參數(shù)5:gamma值:校驗值
參數(shù)6:輸出混合圖像
注意點:兩張圖像的大小和類型必須一致才可以?
調(diào)整圖像亮度與對比度
- 理論
?像素變換 – 點操作 (調(diào)整圖像亮度和對比度屬于像素變換-點操作)
?鄰域操作 – 區(qū)域
- 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 給每個像素點每個通道賦值
- 代碼實現(xiàn)
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
int main(int argc, char** argv) {
Mat src, dst;
src = imread("C:/Users/td/Desktop/zhu.jpg");
if (!src.data) {
printf("could not load image...\n");
return -1;
}
char input_win[] = "input image";
cvtColor(src, src, CV_BGR2GRAY);
namedWindow(input_win, CV_WINDOW_AUTOSIZE);
imshow(input_win, src);
// contrast and brigthtness changes
int height = src.rows;
int width = src.cols;
dst = Mat::zeros(src.size(), src.type());
float alpha = 1.2;//對比度
float beta = 30;//調(diào)節(jié)亮度的關(guān)鍵參數(shù)
Mat m1;
src.convertTo(m1, CV_32F);
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
if (src.channels() == 3) {
float b = m1.at<Vec3f>(row, col)[0];// blue
float g = m1.at<Vec3f>(row, col)[1]; // green
float r = m1.at<Vec3f>(row, col)[2]; // red
// output
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);
}
else if (src.channels() == 1) {
float v = src.at<uchar>(row, col);
dst.at<uchar>(row, col) = saturate_cast<uchar>(v*alpha + beta);
}
}
}
char output_title[] = "contrast and brightness change demo";
namedWindow(output_title, CV_WINDOW_AUTOSIZE);
imshow(output_title, dst);
waitKey(0);
return 0;
}
繪制形狀與文字
- cv::Point與cv::Scalar
Point表示2D平面上一個點x,y ?? ?Point p; ?? ?p.x = 10; ?? ?p.y = 8; ??? ?or ?? ?p = Pont(10,8);
Scalar表示四個元素的向量 ?? ?Scalar(a, b, c);// a = blue, b = green, c = red表示RGB三個通道
- 測試代碼
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
Mat bgImage;
const char* drawdemo_win = "draw shapes and text demo";
void MyLines();
void MyRectangle();
void MyEllipse();
void MyCircle();
void MyPolygon();
void RandomLineDemo();
int main(int argc, char** argv) {
bgImage = imread("C:\\Users\\td\\Desktop/li.jpg");
if (!bgImage.data) {
printf("could not load image...\n");
return -1;
}
MyLines();
MyRectangle();
MyEllipse();
MyCircle();
MyPolygon();
//參數(shù)的含義 代表著不同的字體 放縮系數(shù)
putText(bgImage, "Hello OpenCV", Point(300, 300), CV_FONT_HERSHEY_COMPLEX, 1.0, Scalar(12, 23, 200), 3, 8);
namedWindow(drawdemo_win, CV_WINDOW_AUTOSIZE);
imshow(drawdemo_win, bgImage);
RandomLineDemo();
waitKey(0);
return 0;
}
void MyLines() {//執(zhí)行一個劃線的操作
Point p1 = Point(20, 30);
Point p2;
p2.x = 400;
p2.y = 400;
Scalar color = Scalar(0, 0, 255);
line(bgImage, p1, p2, color, 1, LINE_AA);//LINE_AA的含義是進行一個反鋸齒操作,LINE_8是進行一個常規(guī)的劃線操作
}
void MyRectangle() {//畫出矩形
Rect rect = Rect(200, 100, 300, 300);//參數(shù)1 參數(shù)2是起始位置,參數(shù)3 參數(shù)4是寬高
Scalar color = Scalar(255, 0, 0);
rectangle(bgImage, rect, color, 2, LINE_8);//參數(shù)4是線寬
}
void MyEllipse() {//橢圓
Scalar color = Scalar(0, 255, 0);
//下面參數(shù)的含義
// 圖像 中心點位置 長 高 角度 0-360 顏色 線寬
ellipse(bgImage, Point(bgImage.cols / 2, bgImage.rows / 2), Size(bgImage.cols / 4, bgImage.rows / 8), 90, 0, 360, color, 2, LINE_8);
}
void MyCircle() {//圓
Scalar color = Scalar(0, 255, 255);
Point center = Point(bgImage.cols / 2, bgImage.rows / 2);
circle(bgImage, center, 150, color, 2, 8);//參數(shù)3是半徑長度
}
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(bgImage, ppts, npt, 1, color, 8);//參數(shù)4是輪廓的含義
}
void RandomLineDemo() {
RNG rng(12345);//隨機的函數(shù),給他一個種子
Point pt1;
Point pt2;
Mat bg = Mat::zeros(bgImage.size(), bgImage.type());//生成一個純黑色的圖片,在這個純黑色的圖片之中進行相應(yīng)的操作.
namedWindow("random line demo", CV_WINDOW_AUTOSIZE);
for (int i = 0; i < 100000; i++) {
//生成隨機數(shù)的過程
pt1.x = rng.uniform(0, bgImage.cols);
pt2.x = rng.uniform(0, bgImage.cols);
pt1.y = rng.uniform(0, bgImage.rows);
pt2.y = rng.uniform(0, bgImage.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ù)學(xué)的卷積計算
通常這些卷積算子計算都是線性操作,所以又叫線性濾波
- 過程
上面的邊緣處理過程是需要進行使用插值處理
- ?模糊原理
- 歸一化盒子濾波(均值濾波)
- 高斯濾波
- ?相關(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ù)
- ?代碼示例
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
int main(int argc, char** argv) {
Mat src, dst;
src = imread("C:\\Users\\td\\Desktop/li.jpg");
if (!src.data) {
printf("could not load image...\n");
return -1;
}
char input_title[] = "input image";
char output_title[] = "blur image";
namedWindow(input_title, CV_WINDOW_AUTOSIZE);
namedWindow(output_title, CV_WINDOW_AUTOSIZE);
imshow(input_title, src);
//均值模糊
blur(src, dst, Size(11, 12), Point(-1, -1)); //這里的size(15,1)就是相當于電影之中的武打片,模糊
imshow(output_title, dst);
//高斯模糊
Mat gblur;
GaussianBlur(src, gblur, Size(11, 11), 11, 11);
imshow("gaussian blur", gblur);
waitKey(0);
return 0;
}
- ?結(jié)果
模糊圖像二
- 中值濾波
統(tǒng)計排序濾波器
中值對椒鹽噪聲有很好的抑制作用(就像是一個圖像上面撒了一些白點和黑點)
- ?雙邊濾波
均值模糊無法克服邊緣像素信息丟失缺陷。原因是均值濾波是基于平均權(quán)重
高斯模糊部分克服了該缺陷,但是無法完全避免,因為沒有考慮像素值的不同(只是考慮了相應(yīng)的空間之間的不用,但是沒有考慮像素的不同)
高斯雙邊模糊 – 是邊緣保留的濾波方法,避免了邊緣信息丟失,保留了圖像輪廓不變(邊緣保留的)
高斯濾波是高度中心對稱的,相應(yīng)的一個權(quán)重是對應(yīng)的。是需要進行一個中心化的過程,這就是進行一個高斯的過程,比較容易進行理解。就比如說下面的
x=-2,w=0.05; x=-1,w=0.15; x=0,w=0.6; x=1,w=0.15; x=1,w=0.15; x=2,w=0.05;
- ?API的調(diào)用
中值模糊medianBlur(Mat src, Mat dest, ksize)
雙邊模糊bilateralFilter(src, dest, d=15, 150, 3);(有一個數(shù)學(xué)公式,一頭霧水,就不看了)
- 15 –計算的半徑,半徑之內(nèi)的像數(shù)都會被納入計算,如果提供-1 則根據(jù)sigma space參數(shù)取值 ?? ?
- 150 – sigma color 決定多少差值之內(nèi)的像素會被計算 ??? ?
- 3 – sigma space 如果d的值大于0則聲明無效,否則根據(jù)它來計算d值 中值模糊的ksize大小必須是大于1而且必須是奇數(shù)。
- 代碼
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
int main(int argc, char** argv) {
Mat src, dst;
src = imread("C:\\Users\\td\\Desktop/li.jpg");
if (!src.data) {
printf("could not load image...\n");
return -1;
}
namedWindow("input image", CV_WINDOW_AUTOSIZE);
imshow("input image", src);
//medianBlur(src, dst, 3);//中值濾波 3*3類型
bilateralFilter(src, dst, 15, 100, 5);//輪廓是還在的,只是模糊了一下,如果要是使用高斯濾波的話GaussianBlur進行處理的話,會變成更加模糊.雙邊的會更加好一點
namedWindow("BiBlur Filter Result", CV_WINDOW_AUTOSIZE);
imshow("BiBlur Filter Result", dst);
Mat resultImg;
Mat kernel = (Mat_<int>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
filter2D(dst, resultImg, -1, kernel, Point(-1, -1), 0);
imshow("Final Result", resultImg);
waitKey(0);
return 0;
}
膨脹與腐蝕
- 形態(tài)學(xué)操作(morphology operators)-膨脹
圖像形態(tài)學(xué)操作 – 基于形狀的一系列圖像處理操作的合集,主要是基于集合論基礎(chǔ)上的形態(tài)學(xué)數(shù)學(xué)
形態(tài)學(xué)有四個基本操作:腐蝕、膨脹、開、閉
膨脹與腐蝕是圖像處理中最常用的形態(tài)學(xué)操作手段
- 膨脹
跟卷積操作類似,假設(shè)有圖像A和結(jié)構(gòu)元素B,結(jié)構(gòu)元素B在A上面移動,其中B定義其中心為錨點,計算B覆蓋下A的最大像素值用來替換錨點的像素,其中B作為結(jié)構(gòu)體可以是任意形狀
- 腐蝕
腐蝕跟膨脹操作的過程類似,唯一不同的是以最小值替換錨點重疊下圖像的像素值
- ?API
getStructuringElement(int shape, Size ksize, Point anchor) ?
- 形狀 (MORPH_RECT \MORPH_CROSS \MORPH_ELLIPSE) ?
- 大小 必須是奇數(shù) ?
- 錨點 默認是Point(-1, -1)意思就是中心像素
dilate(src, dst, kernel)
- ?調(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ù)
- 代碼
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
Mat src, dst;
char OUTPUT_WIN[] = "output image";
int element_size = 3;
int max_size = 21;
void CallBack_Demo(int, void*);
int main(int argc, char** argv) {
src = imread("C:\\Users\\td\\Desktop/li.jpg");
if (!src.data) {
printf("could not load image...\n");
return -1;
}
namedWindow("input image", CV_WINDOW_AUTOSIZE);
imshow("input image", src);
namedWindow(OUTPUT_WIN, CV_WINDOW_AUTOSIZE);
createTrackbar("Element Size :", OUTPUT_WIN, &element_size, max_size, CallBack_Demo);
CallBack_Demo(0, 0);
waitKey(0);
return 0;
}
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);
imshow(OUTPUT_WIN, dst);
return;
}
通過代碼的運行可以知道,膨脹是進行一個變白的過程,腐蝕是一個圖片變黑的過程.
實際的項目之中可以通過中值濾波,消除椒鹽噪聲,在用一次腐蝕去除掉相應(yīng)的干擾,最后,加上膨脹將輪廓進行一個放大的過程.
形態(tài)學(xué)操作
- 開操作- open
先腐蝕后膨脹:可以去掉小的對象,假設(shè)對象是前景色,背景是黑色
- 閉操作-close
先膨脹后腐蝕(bin2)? -? 可以填充小的洞(fill hole),假設(shè)對象是前景色,背景是黑色?
- 形態(tài)學(xué)梯度- Morphological Gradient
膨脹減去腐蝕又稱為基本梯度(其它還包括-內(nèi)部梯度、方向梯度)?
- ??頂帽 – top hat
頂帽 是原圖像與開操作之間的差值圖像
- 黑帽
黑帽是閉操作圖像與源圖像的差值圖像
- API調(diào)用
morphologyEx(src, dest, CV_MOP_BLACKHAT, kernel); ?
- Mat src – 輸入圖像 ?
- Mat dest – 輸出結(jié)果 ?
- int OPT – CV_MOP_OPEN/ CV_MOP_CLOSE/ CV_MOP_GRADIENT / CV_MOP_TOPHAT/ CV_MOP_BLACKHAT 形態(tài)學(xué)操作類型
-Mat kernel 結(jié)構(gòu)元素 int Iteration 迭代次數(shù),默認是1
- ?代碼
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
int main(int argc, char** argv) {
Mat src, dst;
src = imread("C:\\Users\\td\\Desktop/li.jpg");
if (!src.data) {
printf("could not load image...\n");
}
namedWindow("input image", CV_WINDOW_AUTOSIZE);
imshow("input image", src);
char output_title[] = "morphology demo";
namedWindow(output_title, CV_WINDOW_AUTOSIZE);
//結(jié)構(gòu)元素的大小的選取會影響相應(yīng)的最后獲得的結(jié)果
Mat kernel = getStructuringElement(MORPH_RECT, Size(11, 11), Point(-1, -1));
//開操作:CV_MOP_OPEN 閉操作:CV_MOP_CLOSE 梯度:CV_MOP_GRADIENT 頂帽:CV_MOP_TOPHAT:原圖像-開操作圖像 黑帽:CV_MOP_BLACKHAT:閉操作與原圖像之間的插值圖像
morphologyEx(src, dst, CV_MOP_BLACKHAT, kernel);
imshow(output_title, dst);
waitKey(0);
return 0;
}
- 結(jié)果:圖片是賊好看的
形態(tài)學(xué)操作應(yīng)用-提取水平線和垂直線
- 形態(tài)學(xué)操作的原理
圖像形態(tài)學(xué)操作時候,可以通過自定義的結(jié)構(gòu)元素實現(xiàn)結(jié)構(gòu)元素 對輸入圖像一些對象敏感、另外一些對象不敏感,這樣就會讓敏 感的對象改變而不敏感的對象保留輸出。通過使用兩個最基本的 形態(tài)學(xué)操作 – 膨脹與腐蝕,使用不同的結(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? ? ? API
- ?代碼
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
int main(int argc, char** argv) {
Mat src, dst;
src = imread("D:/vcprojects/images/chars.png");
if (!src.data) {
printf("could not load image...\n");
return -1;
}
char INPUT_WIN[] = "input image";
char OUTPUT_WIN[] = "result image";
namedWindow(INPUT_WIN, CV_WINDOW_AUTOSIZE);
imshow(INPUT_WIN, src);
Mat gray_src;
cvtColor(src, gray_src, CV_BGR2GRAY);
imshow("gray image", gray_src);
Mat binImg;
adaptiveThreshold(~gray_src, binImg, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 15, -2);
imshow("binary image", binImg);
// 水平結(jié)構(gòu)元素
Mat hline = getStructuringElement(MORPH_RECT, Size(src.cols / 16, 1), Point(-1, -1));
// 垂直結(jié)構(gòu)元素
Mat vline = getStructuringElement(MORPH_RECT, Size(1, src.rows / 16), Point(-1, -1));
// 矩形結(jié)構(gòu)
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
Mat temp;
erode(binImg, temp, kernel);
dilate(temp, dst, kernel);
// morphologyEx(binImg, dst, CV_MOP_OPEN, vline);
bitwise_not(dst, dst);
//blur(dst, dst, Size(3, 3), Point(-1, -1));
imshow("Final Result", dst);
waitKey(0);
return 0;
}
圖像金字塔-上采集與降采集
- 圖像金字塔概念
1. 我們在圖像處理中常常會調(diào)整圖像大小,最常見的就是放大(zoom in)和縮?。▃oom out),盡管幾何變換也可以實現(xiàn)圖像放大和縮小,但是這里我們介紹圖像金字塔
2. 一個圖像金字塔式一系列的圖像組成,最底下一張是圖像尺寸最大,最上方的圖像尺寸最小,從空間上從上向下看就想一個古代的金字塔。
- ?圖像金字塔概念
高斯金子塔 – 用來對圖像進行降采樣
拉普拉斯金字塔 – 用來重建一張圖片根據(jù)它的上層降采樣圖片
- 高斯金字塔
高斯金子塔是從底向上,逐層降采樣得到。
降采樣之后圖像大小是原圖像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.cols*2, src.rows*2)) 生成的圖像是原圖在寬與高各放大兩倍 pyrDown(Mat src, Mat dst, Size(src.cols/2, src.rows/2)) 生成的圖像是原圖在寬與高各縮小1/2
- 代碼
#include <opencv2/opencv.hpp>
#include <iostream>
#include "math.h"
using namespace cv;
int main(int agrc, char** argv) {
Mat src, dst;
src = imread("C:\\Users\\td\\Desktop/li.jpg");
if (!src.data) {
printf("could not load image...");
return -1;
}
char INPUT_WIN[] = "input image";
char OUTPUT_WIN[] = "sample up";
namedWindow(INPUT_WIN, CV_WINDOW_AUTOSIZE);
namedWindow(OUTPUT_WIN, CV_WINDOW_AUTOSIZE);
imshow(INPUT_WIN, src);
// 上采樣
pyrUp(src, dst, Size(src.cols * 2, src.rows * 2));
imshow(OUTPUT_WIN, dst);
// 降采樣
Mat s_down;
pyrDown(src, s_down, Size(src.cols / 2, src.rows / 2));
imshow("sample down", s_down);
// DOG
Mat gray_src, g1, g2, dogImg;
cvtColor(src, gray_src, CV_BGR2GRAY);
GaussianBlur(gray_src, g1, Size(5, 5), 0, 0);
GaussianBlur(g1, g2, Size(5, 5), 0, 0);
subtract(g1, g2, dogImg, Mat());
// 歸一化顯示:線性提亮的過程使用
normalize(dogImg, dogImg, 255, 0, NORM_MINMAX);
imshow("DOG Image", dogImg);
waitKey(0);
return 0;
}
- 結(jié)果
基本閾值操作
- 圖像閾值(threshold)
閾值是什么?簡單點說是把圖像分割的標尺,這個標尺是根據(jù)什么產(chǎn)生的,閾值產(chǎn)生算法?閾值類型。(Binary segmentation)將像素值看成是蘋果的大小, 大于某一個像素可以看成是一個部分,小于一個像素可以看成是另一個像素.這個像素標尺就是可以看成是相應(yīng)的閾值.
- ?閾值類型一閾值二值化(threshold binary)
左下方的圖表示圖像像素點Src(x,y)值分布情況,藍色水平線表示閾值
- ?閾值類型一閾值反二值化(threshold binary Inverted)
左下方的圖表示圖像像素點Src(x,y)值分布情況,藍色水平線表示閾值
- 閾值類型一截斷 (truncate)
左下方的圖表示圖像像素點Src(x,y)值分布情況,藍色水平線表示閾值
?
- 閾值類型一閾值取零 (threshold to zero)
左下方的圖表示圖像像素點Src(x,y)值分布情況,藍色水平線表示閾值
- 閾值類型一閾值反取零 (threshold to zero inverted)
左下方的圖表示圖像像素點Src(x,y)值分布情況,藍色水平線表示閾值
- ?描述
- ?代碼
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
Mat src, gray_src, dst;
int threshold_value = 127;//閾值(這個值是如何知道的??自己設(shè)定的)
int threshold_max = 255;
int type_value = 2;
int type_max = 4;
const char* output_title = "binary image";
void Threshold_Demo(int, void*);
int main(int argc, char** argv) {
src = imread("C:\\Users\\td\\Desktop/li.jpg");
if (!src.data) {
printf("could not load image...\n");
return -1;
}
namedWindow("input image", CV_WINDOW_AUTOSIZE);
namedWindow(output_title, CV_WINDOW_AUTOSIZE);
imshow("input image", src);
createTrackbar("Threshold Value:", output_title, &threshold_value, threshold_max, Threshold_Demo);
createTrackbar("Type Value:", output_title, &type_value, type_max, Threshold_Demo);
Threshold_Demo(0, 0);
waitKey(0);
return 0;
}
void Threshold_Demo(int, void*) {
cvtColor(src, gray_src, CV_BGR2GRAY);
threshold(src, dst, 0, 255, THRESH_TRIANGLE | type_value);//最后的一個參數(shù)是相應(yīng)的閾值處理的方式,自動幫助我們計算閾值
imshow(output_title, dst);
}
自定義線性濾波
- 卷積概念
卷積是圖像處理中一個操作,是kernel在圖像的每個像素上的操作。 Kernel本質(zhì)上一個固定大小的矩陣數(shù)組,其中心點稱為錨點(anchor point)
?把kernel放到像素數(shù)組之上,求錨點周圍覆蓋的像素乘積之和(包括錨點),用來替換錨點覆蓋下像素點值稱為卷積處理。數(shù)學(xué)表達如下:
- 卷積操作
Sum = 8x1+6x1+6x1+2x1+8x1+6x1+2x1+2x1+8x1
New pixel = sum / (m*n)?
?
- ?常見的算子
Robert算子
Sobel算子
拉普拉斯算子
- 自定義卷積模糊
filter2D方法filter2D( Mat src, //輸入圖像
Mat dst, // 模糊圖像
int depth, // 圖像深度32/8
Mat kernel, // 卷積核/模板
Point anchor, // 錨點位置
double delta // 計算出來的像素+delta )
其中 kernel是可以自定義的卷積核?
- 代碼
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
int main(int argc, char** argv) {
Mat src, dst;
int ksize = 0;
src = imread("C:\\Users\\td\\Desktop/li.jpg");
if (!src.data) {
printf("could not load image...\n");
return -1;
}
char INPUT_WIN[] = "input image";
char OUTPUT_WIN[] = "Custom Blur Filter Result";
namedWindow(INPUT_WIN, CV_WINDOW_AUTOSIZE);
namedWindow(OUTPUT_WIN, CV_WINDOW_AUTOSIZE);
imshow(INPUT_WIN, src);
// Sobel X 方向
// Mat kernel_x = (Mat_<int>(3, 3) << -1, 0, 1, -2,0,2,-1,0,1);
// filter2D(src, dst, -1, kernel_x, Point(-1, -1), 0.0);
// Sobel Y 方向
// Mat yimg;
// Mat kernel_y = (Mat_<int>(3, 3) << -1, -2, -1, 0,0,0, 1,2,1);
// filter2D(src, yimg, -1, kernel_y, Point(-1, -1), 0.0);
// 拉普拉斯算子
//Mat kernel_y = (Mat_<int>(3, 3) << 0, -1, 0, -1, 4, -1, 0, -1, 0);
//filter2D(src, dst, -1, kernel_y, Point(-1, -1), 0.0);
//自動卷積和進行寧一個操作的過程
int c = 0;
int index = 0;
while (true) {
c = waitKey(500);
if ((char)c == 27) {// ESC
break;
}
ksize = 5 + (index % 8) * 2;
//獲取初始像素的平方分之一
Mat kernel = Mat::ones(Size(ksize, ksize), CV_32F) / (float)(ksize * ksize);
filter2D(src, dst, -1, kernel, Point(-1, -1));
index++;
imshow(OUTPUT_WIN, dst);
}
// imshow("Sobel Y", yimg);
return 0;
}
處理邊緣
- 卷積邊緣問題
如上所示,在橘色的小格子里面,相應(yīng)的邊緣部分的兩行格子是不能夠進行處理掉的,以此,卷積的邊緣處理方式也是發(fā)揮著至關(guān)重要的作用的.
圖像卷積的時候邊界像素,不能被卷積操作,原因在于邊界像素沒有完全跟kernel重疊,所以當3x3濾波時候有1個像素的邊緣沒有被處理,5x5濾波的時候有2個像素的邊緣沒有被處理。
- 處理邊緣的方式
在卷積開始之前增加邊緣像素,填充的像素值為0或者RGB黑色,比如3x3在 四周各填充1個像素的邊緣,這樣就確保圖像的邊緣被處理,在卷積處理之 后再去掉這些邊緣。openCV中默認的處理方法是: BORDER_DEFAULT,此外 常用的還有如下幾種: ?
- BORDER_CONSTANT – 填充邊緣用指定像素值 ?
- BORDER_REPLICATE – 填充邊緣像素用已知的邊緣像素值。 ?
- BORDER_WRAP – 用另外一邊的像素來補償填充
- ?給圖像添加邊緣API
copyMakeBorder( ?
- Mat src, // 輸入圖像 ?
- Mat dst, // 添加邊緣圖像 ?
- int top, // 邊緣長度,一般上下左右都取相同值, ?
- int bottom, ?
- int left, ?
- int right, ?
- int borderType // 邊緣類型 ?
- Scalar value )
- 代碼
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
int main(int argc, char** argv) {
Mat src, dst;
src = imread("C:\\Users\\td\\Desktop/li.jpg");
if (!src.data) {
printf("could not load image...\n");
return -1;
}
char INPUT_WIN[] = "input image";
char OUTPUT_WIN[] = "Border Demo";
namedWindow(INPUT_WIN, CV_WINDOW_AUTOSIZE);
namedWindow(OUTPUT_WIN, CV_WINDOW_AUTOSIZE);
imshow(INPUT_WIN, src);
int top = (int)(0.05*src.rows);
int bottom = (int)(0.05*src.rows);
int left = (int)(0.05*src.cols);
int right = (int)(0.05*src.cols);
RNG rng(12345);//定義一個隨機數(shù)
int borderType = BORDER_DEFAULT;
int c = 0;
while (true) {
c = waitKey(500);
// ESC
if ((char)c == 27) {
break;
}
if ((char)c == 'r') {
borderType = BORDER_REPLICATE;
} else if((char)c == 'w') {
borderType = BORDER_WRAP;
} else if((char)c == 'c') {
borderType = BORDER_CONSTANT;
}
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
copyMakeBorder(src, dst, top, bottom, left, right, borderType, color);
imshow(OUTPUT_WIN, dst);
}
GaussianBlur(src, dst, Size(5, 5), 0, 0);
imshow(OUTPUT_WIN, dst);
waitKey(0);
return 0;
}
Sobel算子
- 卷積應(yīng)用-圖像邊緣提取
上圖之中就是皮膚和頭發(fā)之間的像素會發(fā)生突然的變遷過程,中間的圖是進行一個躍遷的過程,右圖是進行一個求導(dǎo)的過程.中間的那一個點,也就是相應(yīng)的一個拐點.
- 卷積應(yīng)用-圖像邊緣提取
邊緣是什么 – 是像素值發(fā)生躍遷的地方,是圖像的顯著特征之一,在圖像特征提取、對象檢測、模式識別等方面都有重要的作用。
如何捕捉/提取邊緣 – 對圖像求它的一階導(dǎo)數(shù) ?? ? ?
delta = ?f(x) – f(x-1), delta越大,說明像素在X方向變化越大,邊緣信號越強.
- Sobel算子
是離散微分算子(discrete differentiation operator),用來計算圖像灰度的近似梯度
Soble算子功能集合高斯平滑和微分求導(dǎo).
又被稱為一階微分算子,求導(dǎo)算子,在水平和垂直兩個方向上求導(dǎo),得到圖像X方法與Y方向梯度圖像.
求取導(dǎo)數(shù)的近似值,kernel=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、
double scale ?= 1,//放大或者進行縮小的過程
double delta = 0
int borderType = BORDER_DEFAULT )
?當我們輸入的是一個灰度圖像,會發(fā)現(xiàn)input depth是位于0-255之間,在這個范圍之中的時候,選擇進行輸出的過程需要進行一個處理,我們將處理的結(jié)果進行一個更大的輸出,就避免了精度的缺失.
- ?API說明cv::Scharr
cv::Scharr (
InputArray Src // 輸入圖像
OutputArray dst// 輸出圖像,大小與輸入圖像一致
int depth // 輸出圖像深度.
Int dx. // X方向,幾階導(dǎo)數(shù)
int dy // Y方向,幾階導(dǎo)數(shù).
double scale = 1
double delta = 0
int borderType = BORDER_DEFAULT
)
- ?其它API
GaussianBlur( src, dst, Size(3,3), 0, 0, BORDER_DEFAULT );
cvtColor( src, ?gray, COLOR_RGB2GRAY );
addWeighted( A, 0.5,B, 0.5, 0, AB);
convertScaleAbs(A, B)// 計算圖像A的像素絕對值,輸出到圖像B
- 代碼過程講解
高斯平滑處理 - 轉(zhuǎn)灰度 - 求梯度X和Y - 振幅圖像
- 代碼
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
int main(int argc, char** argv) {
Mat src, dst;
src = imread("C:\\Users\\td\\Desktop/li.jpg");
if (!src.data) {
printf("could not load image...\n");
return -1;
}
char INPUT_TITLE[] = "input image";
char OUTPUT_TITLE[] = "sobel-demo";
namedWindow(INPUT_TITLE, CV_WINDOW_AUTOSIZE);
namedWindow(OUTPUT_TITLE, CV_WINDOW_AUTOSIZE);
imshow(INPUT_TITLE, src);
Mat gray_src;
GaussianBlur(src, dst, Size(3, 3), 0, 0);//高斯模糊
cvtColor(dst, gray_src, CV_BGR2GRAY);//轉(zhuǎn)化為一張灰度圖像
imshow("gray image", gray_src);
Mat xgrad, ygrad;
Scharr(gray_src, xgrad, CV_16S, 1, 0);//X方向的一階導(dǎo)數(shù)
Scharr(gray_src, ygrad, CV_16S, 0, 1);//Y方向的一階導(dǎo)數(shù)
// Sobel(gray_src, xgrad, CV_16S, 1, 0, 3);
// Sobel(gray_src, ygrad, CV_16S, 0, 1, 3);
convertScaleAbs(xgrad, xgrad);//變成正數(shù)
convertScaleAbs(ygrad, ygrad);
imshow("xgrad", xgrad);
imshow("ygrad", ygrad);
Mat xygrad = Mat(xgrad.size(), xgrad.type());
//測試過程之中防止出現(xiàn)截斷進行的操作
//需要將輸入的類型與輸出的類型是保持一致的
printf("type : %d\n", xgrad.type());//0 uchar 類型
int width = xgrad.cols;
int height = ygrad.rows;
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
int xg = xgrad.at<uchar>(row, col);
int yg = ygrad.at<uchar>(row, col);
int xy = xg + yg;
xygrad.at<uchar>(row, col) = saturate_cast<uchar>(xy);
}
}
//addWeighted(xgrad, 0.5, ygrad, 0.5, 0, xygrad);
imshow(OUTPUT_TITLE, xygrad);
waitKey(0);
return 0;
}
- 結(jié)果示意圖
Laplance算子
- 理論
解釋:在二階導(dǎo)數(shù)的時候,最大變化處的值為零即邊緣是零值。通過二階 導(dǎo)數(shù)計算,依據(jù)此理論我們可以計算圖像二階導(dǎo)數(shù),提取邊緣。
- Laplance算子
拉普拉斯算子(Laplance operator)?
- ?處理流程
高斯模糊 – 去噪聲GaussianBlur()
轉(zhuǎn)換為灰度圖像cvtColor()
拉普拉斯 – 二階導(dǎo)數(shù)計算Laplacian()
取絕對值convertScaleAbs()
顯示結(jié)果
- ?API使用cv::Laplacian
Laplacian( InputArray src,
OutputArray dst, int depth, //深度CV_16S
int kisze, // 3
double scale = 1,
double delta =0.0,
int borderType = 4 )
- 代碼
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
int main(int argc, char** argv) {
Mat src, dst;
src = imread("C:\\Users\\td\\Desktop/li.jpg");
if (!src.data) {
printf("could not load image");
}
char input_title[] = "input image";
char output_title[] = "Laplaiance Result";
namedWindow(input_title, CV_WINDOW_AUTOSIZE);
imshow(input_title, src);
Mat gray_src, edge_image;
GaussianBlur(src, dst, Size(3, 3), 0, 0);
cvtColor(dst, gray_src, CV_BGR2GRAY);
Laplacian(gray_src, edge_image, CV_16S, 3);
convertScaleAbs(edge_image, edge_image);//變成8位的
threshold(edge_image, edge_image, 0, 255, THRESH_OTSU | THRESH_BINARY);//對于邊緣的圖像進行相應(yīng)的處理,進行一個二值化的過程,自動尋找相應(yīng)的閾值.
namedWindow(output_title, CV_WINDOW_AUTOSIZE);
imshow(output_title, edge_image);
waitKey(0);
return 0;
}
- 效果圖
Canny邊緣檢測
- Canny算法介紹
Canny是邊緣檢測算法,在1986年提出的,??是一個很好的邊緣檢測器,? 很常用也很實用的圖像處理方法.
- Canny算法介紹 – 五步 in cv::Canny
高斯模糊 - GaussianBlur
灰度轉(zhuǎn)換 - cvtColor
計算梯度 – Sobel/Scharr
非最大信號抑制
高低閾值輸出二值圖像
- Canny算法介紹 - 非最大信號抑制
- ?Canny算法介紹-高低閾值輸出二值圖像
T1, T2為閾值,凡是高于T2的都保留,凡是小于T1都丟棄,從高于T2的像素出發(fā),凡是大于T1而且相互連接的,都保留。最終得到一個輸出二值圖像。
推薦的高低閾值比值為 T2: T1 = 3:1/2:1其中T2為高閾值,T1為低閾值
- API – cv::Canny
Canny( InputArray src, // 8-bit的輸入圖像
OutputArray edges,// 輸出邊緣圖像, 一般都是二值圖像,背景是黑色
double threshold1,// 低閾值,常取高閾值的1/2或者1/3
double threshold2,// 高閾值 int aptertureSize,//
Soble算子的size,通常3x3,取值3
bool L2gradient // 選擇 true表示是L2來歸一化,否則用L1歸一化 )
- 代碼
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
Mat src, gray_src, dst;
int t1_value = 50;
int max_value = 255;
const char* OUTPUT_TITLE = "Canny Result";
void Canny_Demo(int, void*);
int main(int argc, char** argv) {
src = imread("C:\\Users\\td\\Desktop/li.jpg");
if (!src.data) {
printf("could not load image...\n");
return -1;
}
char INPUT_TITLE[] = "input image";
namedWindow(INPUT_TITLE, CV_WINDOW_AUTOSIZE);
namedWindow(OUTPUT_TITLE, CV_WINDOW_AUTOSIZE);
imshow(INPUT_TITLE, src);
cvtColor(src, gray_src, CV_BGR2GRAY);
createTrackbar("Threshold Value:", OUTPUT_TITLE, &t1_value, max_value, Canny_Demo);
Canny_Demo(0, 0);
waitKey(0);
return 0;
}
void Canny_Demo(int, void*) {
Mat edge_output;
blur(gray_src, gray_src, Size(3, 3), Point(-1, -1), BORDER_DEFAULT);
Canny(gray_src, edge_output, t1_value, t1_value * 2, 3, false);
//dst.create(src.size(), src.type());
//src.copyTo(dst, edge_output);//參數(shù)少一些東西,不盡興拷貝像素
// (edge_output, edge_output);
imshow(OUTPUT_TITLE, ~edge_output);
}
- 效果
霍夫變換-直線(這一個項目之中可能會用到,需要特別注意)
- 霍夫直線變換介紹
Hough Line Transform用來做直線檢測
前提條件 – 邊緣檢測已經(jīng)完成
平面空間到極坐標空間轉(zhuǎn)換
- 霍夫直線變換介紹
- ?霍夫直線變換介紹
對于任意一條直線上的所有點來說
變換到極坐標中,從[0~360]空間,可以得到r的大小
屬于同一條直線上點在極坐標空(r, theta)必然在一個點上有最強的信號出現(xiàn),根據(jù)此反算到平面坐標中就可以得到直線上各點的像素坐標, 從而得到直線.
- ?從平面坐標變換到霍夫空間(極坐標)
- API學(xué)習(xí)
標準的霍夫變換 cv::HoughLines從平面坐標轉(zhuǎn)換到霍夫空間,最終輸出是
表示極坐標空間
霍夫變換直線概率 cv::HoughLinesP最終輸出是直線的兩個點
- ?代碼
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
using namespace std;
int main(int argc, char** argv) {
Mat src, src_gray, dst;
src = imread("C:\\Users\\td\\Desktop/li.jpg");
if (!src.data) {
printf("could not load image...\n");
return -1;
}
char INPUT_TITLE[] = "input image";
char OUTPUT_TITLE[] = "hough-line-detection";
namedWindow(INPUT_TITLE, CV_WINDOW_AUTOSIZE);
namedWindow(OUTPUT_TITLE, CV_WINDOW_AUTOSIZE);
imshow(INPUT_TITLE, src);
// extract edge 邊緣檢測,直接提取邊緣
Canny(src, src_gray, 150, 200);//確定是8位的就可以
cvtColor(src_gray, dst, CV_GRAY2BGR);
imshow("edge image", src_gray);
vector<Vec2f> lines;//定義一個霍夫變換的直線,存放直線的數(shù)組
HoughLines(src_gray, lines, 1, CV_PI / 180, 150, 0, 0);
for (size_t i = 0; i < lines.size(); i++) {
float rho = lines[i][0]; // 極坐標中的r長度
float theta = lines[i][1]; // 極坐標中的角度
Point pt1, pt2;
double a = cos(theta), b = sin(theta);
double x0 = a*rho, y0 = b*rho;
// 轉(zhuǎn)換為平面坐標的四個點
pt1.x = cvRound(x0 + 1000 * (-b));
pt1.y = cvRound(y0 + 1000 * (a));
pt2.x = cvRound(x0 - 1000 * (-b));
pt2.y = cvRound(y0 - 1000 * (a));
line(dst, pt1, pt2, Scalar(0, 0, 255), 1, CV_AA);
}
/*
vector<Vec4f> plines;
HoughLinesP(src_gray, plines, 1, CV_PI / 180.0, 10, 0, 10);
Scalar color = Scalar(0, 0, 255);
for (size_t i = 0; i < plines.size(); i++) {
Vec4f hline = plines[i];
line(dst, Point(hline[0], hline[1]), Point(hline[2], hline[3]), color, 3, LINE_AA);
}*/
imshow(OUTPUT_TITLE, dst);
waitKey(0);
return 0;
}
- 結(jié)果示意圖
霍夫圓變換
- 霍夫圓檢測原理
?
- 霍夫圓變換原理
從平面坐標到極坐標轉(zhuǎn)換三個參數(shù)
假設(shè)平面坐標的任意一個圓上的點,轉(zhuǎn)換到極坐標中:?處有最大值,霍夫變換正是利用這個原理實現(xiàn)圓的檢測。
- 相關(guān)API cv::HoughCircles
因為霍夫圓檢測對噪聲比較敏感,所以首先要對圖像做中值濾波。
基于效率考慮,Opencv中實現(xiàn)的霍夫變換圓檢測是基于圖像梯度的實現(xiàn),分為兩步: ?? ?
1. 檢測邊緣,發(fā)現(xiàn)可能的圓心 ?? ?
2. 基于第一步的基礎(chǔ)上從候選圓心開始計算最佳半徑大小
- HoughCircles參數(shù)說明
- ?代碼
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
using namespace std;
int main(int argc, char** argv) {
Mat src, dst;
src = imread("C:\\Users\\td\\Desktop/li.jpg");
if (!src.data) {
printf("could not load image...\n");
return -1;
}
char INPUT_TITLE[] = "input image";
char OUTPUT_TITLE[] = "hough circle demo";
namedWindow(INPUT_TITLE, CV_WINDOW_AUTOSIZE);
namedWindow(OUTPUT_TITLE, CV_WINDOW_AUTOSIZE);
imshow(INPUT_TITLE, src);
// 中值濾波:防止噪點的產(chǎn)生
Mat moutput;
medianBlur(src, moutput, 3);
cvtColor(moutput, moutput, CV_BGR2GRAY);
// 霍夫圓檢測
vector<Vec3f> pcircles;
HoughCircles(moutput, pcircles, CV_HOUGH_GRADIENT, 1, 10, 100, 30, 5, 50);
src.copyTo(dst);
for (size_t i = 0; i < pcircles.size(); i++) {
Vec3f cc = pcircles[i];
circle(dst, Point(cc[0], cc[1]), cc[2], Scalar(0, 0, 255), 2, LINE_AA);
circle(dst, Point(cc[0], cc[1]), 2, Scalar(198, 23, 155), 2, LINE_AA);
}
imshow(OUTPUT_TITLE, dst);
waitKey(0);
return 0;
}
- 效果圖
像素重映射
- 什么是像素重映射
簡單點說就是把輸入圖像中各個像素按照一定的規(guī)則映射到另外一張圖像的對應(yīng)位置上去,形成一張新的圖像。
- ?什么是像素重映射
假設(shè)有映射函數(shù)
- API介紹cv::remap
- ?代碼
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
Mat src, dst, map_x, map_y;
const char* OUTPUT_TITLE = "remap demo";
int index = 0;
void update_map(void);
int main(int argc, char** argv) {
src = imread("C:\\Users\\td\\Desktop/li.jpg");
if (!src.data) {
printf("could not load image...\n");
return -1;
}
char input_win[] = "input image";
namedWindow(input_win, CV_WINDOW_AUTOSIZE);
namedWindow(OUTPUT_TITLE, CV_WINDOW_AUTOSIZE);
imshow(input_win, src);
//32位的單通道的圖像
map_x.create(src.size(), CV_32FC1);//32位F類型
map_y.create(src.size(), CV_32FC1);
int c = 0;
while (true) {
c = waitKey(500);
if ((char)c == 27) {
break;
}
index = c % 4;
update_map();
remap(src, dst, map_x, map_y, INTER_LINEAR, BORDER_CONSTANT, Scalar(0, 255, 255));
imshow(OUTPUT_TITLE, dst);
}
return 0;
}
void update_map(void) {
//取得像素的過程
for (int row = 0; row < src.rows; row++) {
for (int col = 0; col < src.cols; col++) {
switch (index) {
case 0:
if (col >= (src.cols * 0.25) && col <= (src.cols*0.75) && row >= (src.rows*0.25) && row <= (src.rows*0.75)) {
map_x.at<float>(row, col) = 2 * (col - (src.cols*0.25));
map_y.at<float>(row, col) = 2 * (row - (src.rows*0.25));
}
else {
map_x.at<float>(row, col) = 0;
map_y.at<float>(row, col) = 0;
}
break;
case 1:
map_x.at<float>(row, col) = (src.cols - col - 1);
map_y.at<float>(row, col) = row;
break;
case 2:
map_x.at<float>(row, col) = col;
map_y.at<float>(row, col) = (src.rows - row - 1);
break;
case 3:
map_x.at<float>(row, col) = (src.cols - col - 1);
map_y.at<float>(row, col) = (src.rows - row - 1);
break;
}
}
}
}
直方圖均衡化
- 什么是直方圖(Histogram)
- ?什么是直方圖
圖像直方圖,是指對整個圖像像在灰度范圍內(nèi)的像素值(0~255)統(tǒng)計出現(xiàn)頻率次數(shù),據(jù)此生成的直方圖,稱為圖像直方圖-直方圖。直方圖反映了圖像灰度的分布情況, 是圖像的統(tǒng)計學(xué)特征。
- 圖像直方圖
由上面的直方圖可見,我們是一般選用127作為圖像的二值化的點.(原因是如上面所示)
- ?直方圖均衡化
一種提高圖像對比度的方法,拉伸圖像灰度值范圍。
拉刺的一個過程.
- ?直方圖均衡化
如何實現(xiàn),通過上一課中的remap我們知道可以將圖像灰度分布從一個分布映射到另外一個分布,然后在得到映射后的像素值即可。
- API說明cv::equalizeHist
equalizeHist(
InputArray src,//輸入圖像,必須是8-bit的單通道圖像
OutputArray dst// 輸出結(jié)果 )
- ?代碼
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
int main(int argc, char** argv) {
Mat src, dst;
src = imread("C:\\Users\\td\\Desktop/li.jpg");
if (!src.data) {
printf("could not load image...\n");
return -1;
}
cvtColor(src, src, CV_BGR2GRAY);
//進行一個均衡化的過程
equalizeHist(src, dst);
char INPUT_T[] = "input image";
char OUTPUT_T[] = "result image";
namedWindow(INPUT_T, CV_WINDOW_AUTOSIZE);
namedWindow(OUTPUT_T, CV_WINDOW_AUTOSIZE);
imshow(INPUT_T, src);
imshow(OUTPUT_T, dst);
waitKey(0);
return 0;
}
- 效果示意圖
直方圖計算
- 直方圖概念
- 直方圖概念?
上述直方圖概念是基于圖像像素值,其實對圖像梯度、每個像素的角度、等一切圖像的屬性值,我們都可以建立直方圖。這個才是直方圖的概念真正意義,不過是基于圖像像素灰度直方圖是最常見的。
直方圖最常見的幾個屬性:
?- dims 表示維度,對灰度圖像來說只有一個通道值dims=1 ?
- bins 表示在維度中子區(qū)域大小劃分,bins=256,劃分為256個級別 ?
- range 表示值得范圍,灰度值范圍為[0~255]之間
- API
- ?代碼演示
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace std;
using namespace cv;
int main(int argc, char** argv) {
Mat src = imread("C:\\Users\\td\\Desktop/li.jpg");
if (!src.data) {
printf("could not load image...\n");
return -1;
}
char INPUT_T[] = "input image";
char OUTPUT_T[] = "histogram demo";
namedWindow(INPUT_T, CV_WINDOW_AUTOSIZE);
namedWindow(OUTPUT_T, CV_WINDOW_AUTOSIZE);
imshow(INPUT_T, src);
// 分通道顯示
vector<Mat> bgr_planes;
split(src, bgr_planes);
imshow("single channel demo", bgr_planes[0]);//這里是相應(yīng)的一個單通道的輸出
// 計算直方圖
int histSize = 256;
float range[] = { 0, 256 };
const float *histRanges = { range };
Mat b_hist, g_hist, r_hist;
calcHist(&bgr_planes[0], 1, 0, Mat(), b_hist, 1, &histSize, &histRanges, true, false);
calcHist(&bgr_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRanges, true, false);
calcHist(&bgr_planes[2], 1, 0, Mat(), r_hist, 1, &histSize, &histRanges, true, false);
// 歸一化
int hist_h = 400;
int hist_w = 512;
int bin_w = hist_w / histSize;
Mat histImage(hist_w, hist_h, CV_8UC3, Scalar(0, 0, 0));
normalize(b_hist, b_hist, 0, hist_h, NORM_MINMAX, -1, Mat());
normalize(g_hist, g_hist, 0, hist_h, NORM_MINMAX, -1, Mat());
normalize(r_hist, r_hist, 0, hist_h, NORM_MINMAX, -1, Mat());
// render histogram chart
for (int i = 1; i < histSize; i++) {
line(histImage, Point((i - 1)*bin_w, hist_h - cvRound(b_hist.at<float>(i - 1))),
Point((i)*bin_w, hist_h - cvRound(b_hist.at<float>(i))), Scalar(255, 0, 0), 2, LINE_AA);
line(histImage, Point((i - 1)*bin_w, hist_h - cvRound(g_hist.at<float>(i - 1))),
Point((i)*bin_w, hist_h - cvRound(g_hist.at<float>(i))), Scalar(0, 255, 0), 2, LINE_AA);
line(histImage, Point((i - 1)*bin_w, hist_h - cvRound(r_hist.at<float>(i - 1))),
Point((i)*bin_w, hist_h - cvRound(r_hist.at<float>(i))), Scalar(0, 0, 255), 2, LINE_AA);
}
imshow(OUTPUT_T, histImage);
waitKey(0);
return 0;
}
直方圖比較
- 直方圖比較方法-概述
對輸入的兩張圖像計算得到直方圖H1與H2,歸一化到相同的尺度空間 然后可以通過計算H1與H2的之間的距離得到兩個直方圖的相似程度進, 而比較圖像本身的相似程度。Opencv提供的比較方法有四種:
Correlation 相關(guān)性比較
Chi-Square 卡方比較
Intersection 十字交叉性
Bhattacharyya distance 巴氏距離
- 直方圖比較方法-相關(guān)性計算(CV_COMP_CORREL)
- ?直方圖比較方法-卡方計算(CV_COMP_CHISQR)
- 直方圖比較方法-十字計算(CV_COMP_INTERSECT)
- ?直方圖比較方法-巴氏距離計算(CV_COMP_BHATTACHARYYA )
- 相關(guān)API
- 過程
首先把圖像從RGB色彩空間轉(zhuǎn)換到HSV色彩空間cvtColor(最為敏感,將三通道變?yōu)閮蓚€通道)
計算圖像的直方圖,然后歸一化到[0~1]之間calcHist和normalize
使用上述四種比較方法之一進行比較compareHist
- 代碼
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace std;
using namespace cv;
string convertToString(double d);
int main(int argc, char** argv) {
Mat base, test1, test2;
Mat hsvbase, hsvtest1, hsvtest2;
base = imread("C:\\Users\\td\\Desktop/li.jpg");
if (!base.data) {
printf("could not load image...\n");
return -1;
}
test1 = imread("C:\\Users\\td\\Desktop/li.jpg");
test2 = imread("C:\\Users\\td\\Desktop/li.jpg");
cvtColor(base, hsvbase, CV_BGR2HSV);
cvtColor(test1, hsvtest1, CV_BGR2HSV);
cvtColor(test2, hsvtest2, CV_BGR2HSV);
int h_bins = 50; int s_bins = 60;
int histSize[] = { h_bins, s_bins };
// hue varies from 0 to 179, saturation from 0 to 255
float h_ranges[] = { 0, 180 };
float s_ranges[] = { 0, 256 };
const float* ranges[] = { h_ranges, s_ranges };
// Use the o-th and 1-st channels
int channels[] = { 0, 1 };
MatND hist_base;
MatND hist_test1;
MatND hist_test2;
calcHist(&hsvbase, 1, channels, Mat(), hist_base, 2, histSize, ranges, true, false);
normalize(hist_base, hist_base, 0, 1, NORM_MINMAX, -1, Mat());
calcHist(&hsvtest1, 1, channels, Mat(), hist_test1, 2, histSize, ranges, true, false);
normalize(hist_test1, hist_test1, 0, 1, NORM_MINMAX, -1, Mat());
calcHist(&hsvtest2, 1, channels, Mat(), hist_test2, 2, histSize, ranges, true, false);
normalize(hist_test2, hist_test2, 0, 1, NORM_MINMAX, -1, Mat());
double basebase = compareHist(hist_base, hist_base, CV_COMP_INTERSECT);
double basetest1 = compareHist(hist_base, hist_test1, CV_COMP_INTERSECT);
double basetest2 = compareHist(hist_base, hist_test2, CV_COMP_INTERSECT);
double tes1test2 = compareHist(hist_test1, hist_test2, CV_COMP_INTERSECT);
printf("test1 compare with test2 correlation value :%f", tes1test2);
Mat test12;
test2.copyTo(test12);
putText(base, convertToString(basebase), Point(50, 50), CV_FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255), 2, LINE_AA);
putText(test1, convertToString(basetest1), Point(50, 50), CV_FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255), 2, LINE_AA);
putText(test2, convertToString(basetest2), Point(50, 50), CV_FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255), 2, LINE_AA);
putText(test12, convertToString(tes1test2), Point(50, 50), CV_FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255), 2, LINE_AA);
namedWindow("base", CV_WINDOW_AUTOSIZE);
namedWindow("test1", CV_WINDOW_AUTOSIZE);
namedWindow("test2", CV_WINDOW_AUTOSIZE);
imshow("base", base);
imshow("test1", test1);
imshow("test2", test2);
imshow("test12", test12);
waitKey(0);
return 0;
}
string convertToString(double d) {
ostringstream os;
if (os << d)
return os.str();
return "invalid conversion";
}
直方圖反向投射
- 反向投影
反向投影是反映直方圖模型在目標圖像中的分布情況 簡單點說就是用直方圖模型去目標圖像中尋找是否有相似的對象。
通常用HSV色彩空間的HS兩個通道直方圖模型
- 實現(xiàn)步驟
加載圖片imread
將圖像從RGB色彩空間轉(zhuǎn)換到HSV色彩空間cvtColor
計算直方圖和歸一化calcHist與normalize
Mat與MatND其中Mat表示二維數(shù)組,MatND表示三維或者多維數(shù)據(jù),此處均可以用Mat表示。
計算反向投影圖像 - calcBackProject
- 代碼
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace std;
using namespace cv;
Mat src; Mat hsv; Mat hue;
int bins = 12;
void Hist_And_Backprojection(int, void*);
int main(int argc, char** argv) {
src = imread("D:/vcprojects/images/t1.jpg");
if (src.empty()) {
printf("could not load image...\n");
return -1;
}
const char* window_image = "input image";
namedWindow(window_image, CV_WINDOW_NORMAL);
namedWindow("BackProj", CV_WINDOW_NORMAL);
namedWindow("Histogram", CV_WINDOW_NORMAL);
cvtColor(src, hsv, CV_BGR2HSV);
hue.create(hsv.size(), hsv.depth());
int nchannels[] = { 0, 0 };
mixChannels(&hsv, 1, &hue, 1, nchannels, 1);
createTrackbar("Histogram Bins:", window_image, &bins, 180, Hist_And_Backprojection);
Hist_And_Backprojection(0, 0);
imshow(window_image, src);
waitKey(0);
return 0;
}
void Hist_And_Backprojection(int, void*) {
float range[] = { 0, 180 };
const float *histRanges = { range };
Mat h_hist;
calcHist(&hue, 1, 0, Mat(), h_hist, 1, &bins, &histRanges, true, false);
normalize(h_hist, h_hist, 0, 255, NORM_MINMAX, -1, Mat());
Mat backPrjImage;
calcBackProject(&hue, 1, 0, h_hist, backPrjImage, &histRanges, 1, true);
imshow("BackProj", backPrjImage);
int hist_h = 400;
int hist_w = 400;
Mat histImage(hist_w, hist_h, CV_8UC3, Scalar(0, 0, 0));
int bin_w = (hist_w / bins);
for (int i = 1; i < bins; i++) {
rectangle(histImage,
Point((i - 1)*bin_w, (hist_h - cvRound(h_hist.at<float>(i - 1) * (400 / 255)))),
//Point(i*bin_w, (hist_h - cvRound(h_hist.at<float>(i) * (400 / 255)))),
Point(i*bin_w, hist_h),
Scalar(0, 0, 255), -1);
}
imshow("Histogram", histImage);
return;
}
模板匹配
- 模板匹配介紹
- ?模板匹配介紹
- 模板匹配就是在整個圖像區(qū)域發(fā)現(xiàn)與給定子圖像匹配的小塊區(qū)域。
- 所以模板匹配首先需要一個模板圖像T(給定的子圖像)
- 另外需要一個待檢測的圖像-源圖像S
- 工作方法,在帶檢測圖像上,從左到右,從上向下計算模板圖像與重疊子圖像的匹配度,匹配程度越大,兩者相同的可能性越大。
- 模板匹配介紹 – 匹配算法介紹
OpenCV有六種常見的匹配方法,分別是計算平方不同 計算相關(guān)性 計算相關(guān)系數(shù) 計算歸一化平方不同 計算歸一化相關(guān)性 計算歸一化相關(guān)系數(shù)
- 相關(guān)API介紹cv::matchTemplate
- 相關(guān)API介紹cv::matchTemplate
- ?代碼
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace std;
using namespace cv;
Mat src, temp, dst;
int match_method = TM_SQDIFF;
int max_track = 5;
const char* INPUT_T = "input image";
const char* OUTPUT_T = "result image";
const char* match_t = "template match-demo";
void Match_Demo(int, void*);
int main(int argc, char** argv) {
// 待檢測圖像
src = imread("D:/vcprojects/images/flower.png");
// 模板圖像
temp = imread("D:/vcprojects/images/t2.png");
if (src.empty() || temp.empty()) {
printf("could not load image...\n");
return -1;
}
namedWindow(INPUT_T, CV_WINDOW_AUTOSIZE);
namedWindow(OUTPUT_T, CV_WINDOW_NORMAL);
namedWindow(match_t, CV_WINDOW_AUTOSIZE);
imshow(INPUT_T, temp);
const char* trackbar_title = "Match Algo Type:";
createTrackbar(trackbar_title, OUTPUT_T, &match_method, max_track, Match_Demo);
Match_Demo(0, 0);
waitKey(0);
return 0;
}
void Match_Demo(int, void*) {
//輸出的結(jié)果,首先定義相應(yīng)的輸出結(jié)果的矩陣
//下面的公式是已經(jīng)給出的
int width = src.cols - temp.cols + 1;
int height = src.rows - temp.rows + 1;
Mat result(width, height, CV_32FC1);//輸出是32位的浮點型
//進行相應(yīng)的模板匹配的過程
matchTemplate(src, temp, result, match_method, Mat());
//歸一化處理 變成是0-1之間的
normalize(result, result, 0, 1, NORM_MINMAX, -1, Mat());
//找出相應(yīng)的位置,找出最小最大值的匹配
Point minLoc;
Point maxLoc;
double min, max;
src.copyTo(dst);
Point temLoc;
minMaxLoc(result, &min, &max, &minLoc, &maxLoc, Mat());
if (match_method == TM_SQDIFF || match_method == TM_SQDIFF_NORMED) {//這句代碼是個什么?????????????
temLoc = minLoc;
}
else {
temLoc = maxLoc;
}
// 繪制矩形
//目標圖像上面進行繪制
rectangle(dst, Rect(temLoc.x, temLoc.y, temp.cols, temp.rows), Scalar(0, 0, 255), 2, 8);
rectangle(result, Rect(temLoc.x, temLoc.y, temp.cols, temp.rows), Scalar(0, 0, 255), 2, 8);
imshow(OUTPUT_T, result);
imshow(match_t, dst);
}
輪廓發(fā)現(xiàn)
- 輪廓發(fā)現(xiàn)(find contour)
輪廓發(fā)現(xiàn)是基于圖像邊緣提取的基礎(chǔ)尋找對象輪廓的方法。 所以邊緣提取的閾值選定會影響最終輪廓發(fā)現(xiàn)結(jié)果
- API 輪廓發(fā)現(xiàn)(find contour)?
- API 輪廓繪制(draw contour)?
- ?代碼過程
輸入圖像轉(zhuǎn)為灰度圖像cvtColor
使用Canny進行邊緣提取,得到二值圖像
使用findContours尋找輪廓
使用drawContours繪制輪廓
- 代碼測試
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace std;
using namespace cv;
Mat src, dst;
const char* output_win = "findcontours-demo";
int threshold_value = 100;
int threshold_max = 255;
RNG rng;
void Demo_Contours(int, void*);
int main(int argc, char** argv) {
src = imread("C:\\Users\\td\\Desktop/li.jpg");
if (src.empty()) {
printf("could not load image...\n");
return -1;
}
namedWindow("input-image", CV_WINDOW_AUTOSIZE);
namedWindow(output_win, CV_WINDOW_AUTOSIZE);
imshow("input-image", src);
cvtColor(src, src, CV_BGR2GRAY);
const char* trackbar_title = "Threshold Value:";
//創(chuàng)建一個trackbar的過程,前面已經(jīng)用了好多了.注意trackbar的使用可以應(yīng)用到項目之中.
createTrackbar(trackbar_title, output_win, &threshold_value, threshold_max, Demo_Contours);
Demo_Contours(0, 0);
waitKey(0);
return 0;
}
void Demo_Contours(int, void*) {
Mat canny_output;
vector<vector<Point>> contours;
vector<Vec4i> hierachy;
//最后的一個參數(shù)false是代表著之直接使用絕對值的方式進行相應(yīng)的求解,但是如果要是使用true,代表著使用開根號的方式進行相應(yīng)的求解,這種方式進行相應(yīng)的求解的時候,會發(fā)生計算量大的問題
Canny(src, canny_output, threshold_value, threshold_value * 2, 3, false);
findContours(canny_output, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
dst = Mat::zeros(src.size(), CV_8UC3);
RNG rng(12345);
for (size_t i = 0; i < contours.size(); i++) {
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
drawContours(dst, contours, i, color, 2, 8, hierachy, 0, Point(0, 0));
}
imshow(output_win, dst);
}
- 效果圖
凸包
- 概念介紹
什么是凸包(Convex Hull)?????????
在一個多變形邊緣或者內(nèi)部任意兩個點的連線都包含在多邊形邊界或者內(nèi)部。
正式定義: 包含點集合S中所有點的最小凸多邊形稱為凸包
上圖之中,左邊的圖是定義為相應(yīng)的凸包.
- 概念介紹-Graham掃描算法
首先選擇Y方向最低的點作為起始點p0
從p0開始極坐標掃描,依次添加p1….pn(排序順序是根據(jù)極坐標的角度大小,逆時針方向)
對每個點pi來說,如果添加pi點到凸包中導(dǎo)致一個左轉(zhuǎn)向(逆時針方法)
則添加該點到凸包, 反之如果導(dǎo)致一個右轉(zhuǎn)向(順時針方向)刪除該點從凸包中
- API說明cv::convexHull
- ?代碼過程
首先把圖像從RGB轉(zhuǎn)為灰度
然后再轉(zhuǎn)為二值圖像
在通過發(fā)現(xiàn)輪廓得到候選點
凸包API調(diào)用
繪制顯示。
- ?代碼
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace std;
using namespace cv;
Mat src, src_gray, dst;
int threshold_value = 100;
int threshold_max = 255;
const char* output_win = "convex hull demo";
void Threshold_Callback(int, void*);
RNG rng(12345);
int main(int argc, char** argv) {
src = imread("C:\\Users\\td\\Desktop/li.jpg");
if (!src.data) {
printf("could not load image...\n");
return -1;
}
const char* input_win = "input image";
namedWindow(input_win, CV_WINDOW_AUTOSIZE);
namedWindow(output_win, CV_WINDOW_NORMAL);
const char* trackbar_label = "Threshold : ";
cvtColor(src, src_gray, CV_BGR2GRAY);
blur(src_gray, src_gray, Size(3, 3), Point(-1, -1), BORDER_DEFAULT);
imshow(input_win, src_gray);
createTrackbar(trackbar_label, output_win, &threshold_value, threshold_max, Threshold_Callback);
Threshold_Callback(0, 0);
waitKey(0);
return 0;
}
void Threshold_Callback(int, void*) {
Mat bin_output;
vector<vector<Point>> contours;
vector<Vec4i> hierachy;
threshold(src_gray, bin_output, threshold_value, threshold_max, THRESH_BINARY);
findContours(bin_output, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
vector<vector<Point>> convexs(contours.size());
for (size_t i = 0; i < contours.size(); i++) {
convexHull(contours[i], convexs[i], false, true);
}
// 繪制
dst = Mat::zeros(src.size(), CV_8UC3);
vector<Vec4i> empty(0);
for (size_t k = 0; k < contours.size(); k++) {
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
drawContours(dst, contours, k, color, 2, LINE_8, hierachy, 0, Point(0, 0));
drawContours(dst, convexs, k, color, 2, LINE_8, empty, 0, Point(0, 0));
}
imshow(output_win, dst);
return;
}
輪廓周圍繪制矩形框和圓形框
- ?輪廓周圍繪制矩形 -API
基于RDP算法實現(xiàn),目的是減少多邊形輪廓點數(shù)
cv::boundingRect(InputArray points)得到輪廓周圍最小矩形左上交點坐標和右下角點坐標,繪制一個矩形
cv::minAreaRect(InputArray ?points)得到一個旋轉(zhuǎn)的矩形,返回旋轉(zhuǎn)矩形
?cv::minEnclosingCircle(InputArray points, //得到最小區(qū)域圓形 ?? ?
Point2f& center, // 圓心位置 ?? ?
float& radius)// 圓的半徑
cv::fitEllipse(InputArray ?points)得到最小橢圓
- 步驟
首先將圖像變?yōu)槎祱D像
發(fā)現(xiàn)輪廓,找到圖像輪廓
通過相關(guān)API在輪廓點上找到最小包含矩形和圓,旋轉(zhuǎn)矩形與橢圓。
繪制它們。
- 代碼
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace std;
using namespace cv;
Mat src, gray_src, drawImg;
int threshold_v = 170;
int threshold_max = 255;
const char* output_win = "rectangle-demo";
RNG rng(12345);
void Contours_Callback(int, void*);
int main(int argc, char** argv) {
src = imread("C:\\Users\\td\\Desktop/li.jpg");
if (!src.data) {
printf("could not load image...\n");
return -1;
}
cvtColor(src, gray_src, CV_BGR2GRAY);
blur(gray_src, gray_src, Size(3, 3), Point(-1, -1));
const char* source_win = "input image";
namedWindow(source_win, CV_WINDOW_AUTOSIZE);
namedWindow(output_win, CV_WINDOW_AUTOSIZE);
imshow(source_win, src);
createTrackbar("Threshold Value:", output_win, &threshold_v, threshold_max, Contours_Callback);
Contours_Callback(0, 0);
waitKey(0);
return 0;
}
void Contours_Callback(int, void*) {
Mat binary_output;
vector<vector<Point>> contours;
vector<Vec4i> hierachy;
threshold(gray_src, binary_output, threshold_v, threshold_max, THRESH_BINARY);
//imshow("binary image", binary_output);
findContours(binary_output, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(-1, -1));
vector<vector<Point>> contours_ploy(contours.size());
vector<Rect> ploy_rects(contours.size());
vector<Point2f> ccs(contours.size());
vector<float> radius(contours.size());
vector<RotatedRect> minRects(contours.size());
vector<RotatedRect> myellipse(contours.size());
for (size_t i = 0; i < contours.size(); i++) {
approxPolyDP(Mat(contours[i]), contours_ploy[i], 3, true);
ploy_rects[i] = boundingRect(contours_ploy[i]);
minEnclosingCircle(contours_ploy[i], ccs[i], radius[i]);
if (contours_ploy[i].size() > 5) {
myellipse[i] = fitEllipse(contours_ploy[i]);
minRects[i] = minAreaRect(contours_ploy[i]);
}
}
// draw it
drawImg = Mat::zeros(src.size(), src.type());
Point2f pts[4];
for (size_t t = 0; t < contours.size(); t++) {
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
//rectangle(drawImg, ploy_rects[t], color, 2, 8);
//circle(drawImg, ccs[t], radius[t], color, 2, 8);
if (contours_ploy[t].size() > 5) {
ellipse(drawImg, myellipse[t], color, 1, 8);
minRects[t].points(pts);
for (int r = 0; r < 4; r++) {
line(drawImg, pts[r], pts[(r + 1) % 4], color, 1, 8);
}
}
}
imshow(output_win, drawImg);
return;
}
圓形矩
- 幾何矩
?圖像中心Center(x0, y0)------質(zhì)量的中心
- ?API介紹與使用-計算矩cv::moments
- 步驟
提取圖像邊緣
發(fā)現(xiàn)輪廓
計算每個輪廓對象的矩
計算每個對象的中心、弧長、面積?
- ?代碼
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace std;
using namespace cv;
Mat src, gray_src;
int threshold_value = 80;
int threshold_max = 255;
const char* output_win = "image moents demo";
RNG rng(12345);
void Demo_Moments(int, void*);
int main(int argc, char** argv) {
src = imread("C:\\Users\\td\\Desktop/li.jpg");
if (!src.data) {
printf("could not load image...\n");
return -1;
}
cvtColor(src, gray_src, CV_BGR2GRAY);
GaussianBlur(gray_src, gray_src, Size(3, 3), 0, 0);
char input_win[] = "input image";
namedWindow(input_win, CV_WINDOW_AUTOSIZE);
namedWindow(output_win, CV_WINDOW_AUTOSIZE);
imshow(input_win, src);
createTrackbar("Threshold Value : ", output_win, &threshold_value, threshold_max, Demo_Moments);
Demo_Moments(0, 0);
waitKey(0);
return 0;
}
void Demo_Moments(int, void*) {
Mat canny_output;
vector<vector<Point>> contours;
vector<Vec4i> hierachy;
//提取圖像邊緣
Canny(gray_src, canny_output, threshold_value, threshold_value * 2, 3, false);
//發(fā)現(xiàn)輪廓
findContours(canny_output, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
vector<Moments> contours_moments(contours.size());
vector<Point2f> ccs(contours.size());//中心位置
for (size_t i = 0; i < contours.size(); i++) {
contours_moments[i] = moments(contours[i]);
//中心位置
ccs[i] = Point(static_cast<float>(contours_moments[i].m10 / contours_moments[i].m00), static_cast<float>(contours_moments[i].m01 / contours_moments[i].m00));
}
Mat drawImg;// = Mat::zeros(src.size(), CV_8UC3);
src.copyTo(drawImg);
for (size_t i = 0; i < contours.size(); i++) {
if (contours[i].size() < 100) {
continue;
}
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));//給定一個顏色
printf("center point x : %.2f y : %.2f\n", ccs[i].x, ccs[i].y);
printf("contours %d area : %.2f arc length : %.2f\n", i, contourArea(contours[i]), arcLength(contours[i], true));
drawContours(drawImg, contours, i, color, 2, 8, hierachy, 0, Point(0, 0));
circle(drawImg, ccs[i], 2, color, 2, 8);
}
imshow(output_win, drawImg);
return;
}
點多邊形測試
- 概念介紹 - 點多邊形測試
測試一個點是否在給定的多邊形內(nèi)部,邊緣或者外部
- ?API介紹 cv::pointPolygonTest
- ?代碼-步驟
構(gòu)建一張400x400大小的圖片, Mat::Zero(400, 400, CV_8UC1)
畫上一個六邊形的閉合區(qū)域line
發(fā)現(xiàn)輪廓
對圖像中所有像素點做點與多邊形測試,得到距離
歸一化后顯示。
- 代碼
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace std;
using namespace cv;
int main(int argc, char** argv) {
const int r = 100;
Mat src = Mat::zeros(r * 4, r * 4, CV_8UC1);
//畫出六個點
vector<Point2f> vert(6);
vert[0] = Point(3 * r / 2, static_cast<int>(1.34*r));
vert[1] = Point(1 * r, 2 * r);
vert[2] = Point(3 * r / 2, static_cast<int>(2.866*r));
vert[3] = Point(5 * r / 2, static_cast<int>(2.866*r));
vert[4] = Point(3 * r, 2 * r);
vert[5] = Point(5 * r / 2, static_cast<int>(1.34*r));
//連接六個點的線
for (int i = 0; i < 6; i++) {
line(src, vert[i], vert[(i + 1) % 6], Scalar(255), 3, 8, 0);
}
vector<vector<Point>> contours;
vector<Vec4i> hierachy;
Mat csrc;
src.copyTo(csrc);
//找這個輪廓
findContours(csrc, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
Mat raw_dist = Mat::zeros(csrc.size(), CV_32FC1);
//對于每個像素進行相應(yīng)的處理
for (int row = 0; row < raw_dist.rows; row++) {
for (int col = 0; col < raw_dist.cols; col++) {
//返回距離
double dist = pointPolygonTest(contours[0], Point2f(static_cast<float>(col), static_cast<float>(row)), true);
raw_dist.at<float>(row, col) = static_cast<float>(dist);
}
}
double minValue, maxValue;
minMaxLoc(raw_dist, &minValue, &maxValue, 0, 0, Mat());
Mat drawImg = Mat::zeros(src.size(), CV_8UC3);
for (int row = 0; row < drawImg.rows; row++) {
for (int col = 0; col < drawImg.cols; col++) {
float dist = raw_dist.at<float>(row, col);
if (dist > 0) {//在內(nèi)部
drawImg.at<Vec3b>(row, col)[0] = (uchar)(abs(1.0 - (dist / maxValue)) * 255);
}
else if (dist < 0) {//在外部
drawImg.at<Vec3b>(row, col)[2] = (uchar)(abs(1.0 - (dist / minValue)) * 255);
}
else {
drawImg.at<Vec3b>(row, col)[0] = (uchar)(abs(255 - dist));
drawImg.at<Vec3b>(row, col)[1] = (uchar)(abs(255 - dist));
drawImg.at<Vec3b>(row, col)[2] = (uchar)(abs(255 - dist));
}
}
}
const char* output_win = "point polygon test demo";
char input_win[] = "input image";
namedWindow(input_win, CV_WINDOW_AUTOSIZE);
namedWindow(output_win, CV_WINDOW_AUTOSIZE);
imshow(input_win, src);
imshow(output_win, drawImg);
waitKey(0);
return 0;
}
- 效果圖
基于距離變換與分水嶺的圖像分割
- 什么是圖像分割(Image Segmentation)
圖像分割(Image Segmentation)是圖像處理最重要的處理手段之一
圖像分割的目標是將圖像中像素根據(jù)一定的規(guī)則分為若干(N)個cluster集合,每個集合包含一類像素。
根據(jù)算法分為監(jiān)督學(xué)習(xí)方法和無監(jiān)督學(xué)習(xí)方法,圖像分割的算法多數(shù)都是無監(jiān)督學(xué)習(xí)方法 - KMeans?
- 距離變換與分水嶺介紹
?
距離變換常見算法有兩種 - 不斷膨脹/ 腐蝕得到 ?- 基于倒角距離
?分水嶺變換常見的算法 - 基于浸泡理論實現(xiàn)
- API
- 處理流程
1.將白色背景變成黑色-目的是為后面的變換做準備
2. 使用filter2D與拉普拉斯算子實現(xiàn)圖像對比度提高,sharp
3. 轉(zhuǎn)為二值圖像通過threshold
4. 距離變換
5. 對距離變換結(jié)果進行歸一化到[0~1]之間
6. 使用閾值,再次二值化,得到標記
7. 腐蝕得到每個Peak - erode
8.發(fā)現(xiàn)輪廓 – findContours
9. 繪制輪廓- drawContours
10.分水嶺變換 watershed
11. 對每個分割區(qū)域著色輸出結(jié)果?
- 代碼
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace std;
using namespace cv;
int main(int argc, char** argv) {
char input_win[] = "input image";
char watershed_win[] = "watershed segmentation demo";
Mat src = imread("C:\\Users\\td\\Desktop/1.png");
// Mat src = imread("D:/kuaidi.jpg");
if (src.empty()) {
printf("could not load image...\n");
return -1;
}
namedWindow(input_win, CV_WINDOW_AUTOSIZE);
imshow(input_win, src);
// 1. change background : 將原來的背景變成黑色
for (int row = 0; row < src.rows; row++) {
for (int col = 0; col < src.cols; col++) {
if (src.at<Vec3b>(row, col) == Vec3b(255, 255, 255)) {
src.at<Vec3b>(row, col)[0] = 0;
src.at<Vec3b>(row, col)[1] = 0;
src.at<Vec3b>(row, col)[2] = 0;
}
}
}
namedWindow("black background", CV_WINDOW_AUTOSIZE);
imshow("black background", src);
// 2. sharpen:提高圖像的對比度,為下一步進行二值化進行準備
Mat kernel = (Mat_<float>(3, 3) << 1, 1, 1, 1, -8, 1, 1, 1, 1);
Mat imgLaplance;
Mat sharpenImg = src;
//進行均值處理的時候.是不會出現(xiàn)負數(shù)的,一旦要是存在相應(yīng)的負數(shù),就是需要將相應(yīng)的位數(shù)變成32位
filter2D(src, imgLaplance, CV_32F, kernel, Point(-1, -1), 0, BORDER_DEFAULT);
src.convertTo(sharpenImg, CV_32F);
Mat resultImg = sharpenImg - imgLaplance;
resultImg.convertTo(resultImg, CV_8UC3);
imgLaplance.convertTo(imgLaplance, CV_8UC3);
imshow("sharpen image", resultImg);
// src = resultImg; // copy back
// 3. convert to binary:進行二值變換
Mat binaryImg;
cvtColor(src, resultImg, CV_BGR2GRAY);
threshold(resultImg, binaryImg, 40, 255, THRESH_BINARY | THRESH_OTSU);
imshow("binary image", binaryImg);
//4. 進行距離變換
Mat distImg;
distanceTransform(binaryImg, distImg, DIST_L1, 3, 5);
normalize(distImg, distImg, 0, 1, NORM_MINMAX);
imshow("distance result", distImg);
//5. binary again:距離變換之后的二值化處理---得到相應(yīng)的孤立的點
threshold(distImg, distImg, .4, 1, THRESH_BINARY);
Mat k1 = Mat::ones(13, 13, CV_8UC1);
erode(distImg, distImg, k1, Point(-1, -1));
imshow("distance binary image", distImg);
//6. markers 發(fā)現(xiàn)輪廓
Mat dist_8u;
distImg.convertTo(dist_8u, CV_8U);
vector<vector<Point>> contours;
findContours(dist_8u, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(0, 0));
// create makers 創(chuàng)建輪廓
Mat markers = Mat::zeros(src.size(), CV_32SC1);
for (size_t i = 0; i < contours.size(); i++) {
drawContours(markers, contours, static_cast<int>(i), Scalar::all(static_cast<int>(i) + 1), -1);
}
circle(markers, Point(5, 5), 3, Scalar(255, 255, 255), -1);
imshow("my markers", markers * 1000);
// perform watershed: 創(chuàng)建一個分水嶺
watershed(src, markers);
Mat mark = Mat::zeros(markers.size(), CV_8UC1);
markers.convertTo(mark, CV_8UC1);
bitwise_not(mark, mark, Mat());
imshow("watershed image", mark);
// generate random color
vector<Vec3b> colors;
for (size_t i = 0; i < contours.size(); i++) {
int r = theRNG().uniform(0, 255);
int g = theRNG().uniform(0, 255);
int b = theRNG().uniform(0, 255);
colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
}
// fill with color and display final result
Mat dst = Mat::zeros(markers.size(), CV_8UC3);
for (int row = 0; row < markers.rows; row++) {
for (int col = 0; col < markers.cols; col++) {
int index = markers.at<int>(row, col);
if (index > 0 && index <= static_cast<int>(contours.size())) {
dst.at<Vec3b>(row, col) = colors[index - 1];
}
else {
dst.at<Vec3b>(row, col) = Vec3b(0, 0, 0);
}
}
}
imshow("Final Result", dst);
waitKey(0);
return 0;
}
- 結(jié)果示意圖
文章來源:http://www.zghlxwxcb.cn/news/detail-443502.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-443502.html
到了這里,關(guān)于OpenCV圖像處理基礎(chǔ)(C++版)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!