KANIKIG

KANIKIG

just for fun | 兴趣使然 Ph.D. in Engineering|❤️ #NFT $ETH| [Twitter](https://twitter.com/kanikig2)|[Github](https://github.com/KANIKIG)|[Telegram channel](https://t.me/kanikigtech)

OpenCV基礎筆記

image

概述:OpenCV 介紹與環境#

OpenCV 介紹#

  • opencv 計算機視覺開源庫,算法涉及圖像處理與機器學習。
  • Intel 公司貢獻,俄羅斯工程師貢獻大部分 C/C++ 代碼。
  • BSD 許可,可免費商用。
  • SDK 支持 Java,Python,IOS,Android。

OpenCV 框架#

iShot2020-09-2402.26.26.png

示例代碼#

#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int main() {
    Mat srcImage = imread("/Users/kanikig/Documents/CProjects/test/test.jpg");
    if (!srcImage.data) {
        std::cout << "Image not loaded";
        return -1;
    }
    imshow("[img]", srcImage);
    waitKey(0);
    return 0;
}

加載、修改、保存圖像#

色彩空間

  • 位圖
  • 灰度
  • RGB 真彩色
  • CMYK
  • HSV
  • YUV

加載圖像#

Mat srcImage = imread(filename, int flags)

flags = 
IMREAD_UNCHANGED (<0) 表示加載原圖,不做任何改變
IMREAD_GRAYSCALE (0)表示把原圖作為灰度圖像加載進來
IMREAD_COLOR (>0) 表示把原圖作為RGB圖像加載進來(default)

顯示圖像#

namedWindow("窗口1", CV_WINDOW_NORMAL);
	/*
	參數1:窗口的名字
	參數2:窗口類型,CV_WINDOW_AUTOSIZE 時表明窗口大小等於圖片大小。不可以被拖動改變大小。
	CV_WINDOW_NORMAL 時,表明窗口可以被隨意拖動改變大小。
	*/

imshow("窗口1", srcImage);	//在“窗口1”這個窗口輸出圖片。

修改圖像#

Mat gray_image;
cvtColor(image, gray_image, COLOR_RGB2GRAY);

保存圖像#

imwrite(filename, Input_img, param);
	/*
	保存圖像文件到指定目錄路徑
	只有8位、16位的PNG、JPG、Tiff文件格式而且是單通道或者三通道的BGB的圖像才可以通過這種方式保存
	保存PNG格式的時候可以保存透明通道的圖片
	可以指定壓縮參數
	*/

矩陣的掩膜操作#

獲取像素指針#

//確定圖像深度,若為false報錯
CV_Assert(myImage.depth() == CV_8U); 
//獲取當前行指針(row從0開始)
const uchar*  current= Image.ptr<uchar>(row);
//獲得當前像素點像素值
p(row,col) =current[col];
//像素範圍處理,保證RGB在0-255
saturate_cast<ucahr>();

掩膜(mask)操作#

//定義掩膜
Mat kernel = (Mat_<char>(3,3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
//掩膜操作
filter2D(src, dst, src.depth(), kernel);

//程序計時
t = (double)getTickCount();
t = ((double)getTickCount() - t)/getTickFrequency();

Mat 對象#

Mat 對象使用#

//構造函數
Mat dst;
dst = Mat(int rows, int cols, int type);
dst = Mat(Size size, int type);
//等價於
dst.create(Size size, int type);
//其中前兩個參數分別表示行(row)跟列(column)、第三個CV_8UC3中的8表示每個通道占8位、U表示無符號、C表示Char類型、3表示通道數目是3,第四個參數是向量表示初始化每個像素值是多少,向量長度對應通道數目一致
dst = Mat M(2,2,CV_8UC3, Scalar(0,0,255))

dst = Scalar(127, 0, 255); //將圖像設置成單一灰度和顏色
//常用方法
Image.cols;
Image.rows;
Image.copyTo(mat);
Image.clone();
Image.convertTo(Mat dst, int type);
Image.channels(); //RGB=3, 灰度圖=1
Image.depth(); //一般寫-1
Image.empty();
uchar* ptr(row);

Mat dst = src.clone();
src.copyTo(dst);

Mat 定義數組#

Mat C = (Mat_<double>(3,3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
eye(int rows, int cols, int type); //生成單位矩陣

圖像操作#

讀寫圖像#

imread();
imwrite();

讀寫像素#

int px = 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) {
      image.at<Vec3b>(row, col)[0] = 0; // blue
      image.at<Vec3b>(row, col)[1] = 0; // green
    }
  }
}
//Vec3b對應三通道的順序是blue、green、red的uchar類型數據。Vec3f對應三通道的float類型數據
//把CV_8UC1轉換到CV32F1實現如下:src.convertTo(dst, CV_32F);

//反色
bitwise_not(src, dst);

圖像混合#

線性混合理論#

iShot2020-09-2402.22.30.png

相關 API#

AddWeighted( const CvArr* src1, double alpha,const CvArr* src2, double beta,double gamma, CvArr* dst );
/*
參數1:src1,第一個原數組
參數2:alpha,第一個數組元素權重
參數3:src2第二個原數組
參數4:beta,第二個數組元素權重
參數5:gamma,圖1與圖2作和後添加的數值(默認0)。不要太大,不然圖片一片白。總和等於255以上就是純白色了。
參數6:dst,輸出圖片
*/

調整亮度與對比度#

理論#

iShot2020-09-2402.22.16.png

示例#

Mat new_image = Mat::zeros( image.size(), image.type() ); //創建一張跟原圖像大小和類型一致的空白圖像、像素值初始化為0
saturate_cast<uchar>(value) //確保值大小範圍為0~255之間
Mat.at<Vec3b>(y,x)[index]=value //給每個像素點每個通道賦值


int height = image.rows;
int width = image.cols;
double alpha = 1.2;
double beta = 50;
output = Mat::zeros(image.size(), image.type());
for (int row = 0; row < height; row++) {
  for (int col = 0; col < width; col++) {
    output.at<Vec3b>(row, col)[0] = saturate_cast<uchar>(alpha * image.at<Vec3b>(row, col)[0] + beta); //blue
    output.at<Vec3b>(row, col)[1] = saturate_cast<uchar>(alpha * image.at<Vec3b>(row, col)[1] + beta); //green
    output.at<Vec3b>(row, col)[2] = saturate_cast<uchar>(alpha * image.at<Vec3b>(row, col)[2] + beta); //red
    }
  }
}

繪製形狀和文字#

Point 與 Scalar#

Point p;
p.x = 10;
p.y = 8;
//等價於
p = Point(10,8);

Scalar(B, G, R);

繪製形狀#

(LINE_4, LINE_8, LINE_AA) //AA反鋸齒
line(frame, beginPoint, endPoint, Scalar(0, 0, 255), 2); //起點為beginPoint,終點是endPoint,顏色是紅色,線寬是2,shift為默認值

Rect rect = Rect(200, 100, 300, 300); // 起始xy,寬高
rectangle(Image, rect, color, 2, LINE_8);

eclipse(Image, Point(Image.cols/2, Image.rows/2), Size, 90, 0, 360, color, 2);

circle(central point, radius, color, 2);

//多邊形
Point pts[1][5];
pts[0][0] = Point(100,100);
fillpoly(Image, in, out, 1, color, 8);

文字#

putText(
		cv::Mat& img, // 待繪製的圖像
		const string& text, // 待繪製的文字
		cv::Point origin, // 文本框的左下角
		int fontFace, // 字體 (如cv::FONT_HERSHEY_PLAIN)
		double fontScale, // 尺寸因子,值越大文字越大
		cv::Scalar color, // 線條的顏色(RGB)
		int thickness = 1, // 線條寬度
		int lineType = 8, // 線型(4鄰域或8鄰域,默認8鄰域)
		bool bottomLeftOrigin = false // true='origin at lower left'
	);

隨機繪製#

RNG rng(12345);
Point pt1;
Point pt2;
for (int i =0; i < 1000; i++){
  pt1.x = rng.uniform(0, Image.cols);
  pt1.y = rng.uniform(0, Image.rows);
  pt2.x = rng.uniform(0, Image.cols);
  pt2.y = rng.uniform(0, Image.rows);
  Scalar color = Scalar(rng.uniform(0,255), rng.uniform(0,255), rng.uniform(0,255));
  if (wairKey(50) > 0){
    break; //50秒循環一次,直到按鍵
  }
  line(Image, pt1, pt2, color, 1, 8);
}

模糊圖像一#

原理#

iShot2020-09-2403.32.36.png

iShot2020-09-2403.32.43.png

iShot2020-09-2403.32.48.png

示例#

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 必須是正數而且是奇數

圖像模糊二#

中值濾波#

iShot2020-09-2404.20.14.png

雙邊濾波#

iShot2020-09-2404.23.30.png

示例#

medianBlur(Mat src, Mat dest, ksize);
bilateralFilter(src, dest, 15, 100, 3);

/* 15 –計算的半徑d,半徑之內的像數都會被納入計算,如果提供-1 則根據sigma space參數取值
	 100 – sigma color 決定多少差值之內的像素會被計算
 	 3 – sigma space 如果d的值大於0則聲明無效,否則根據它來計算d值
中值模糊的ksize大小必須是大於1而且必須是奇數。
*/

膨脹與腐蝕#

iShot2020-09-2803.40.02.png

腐蝕#

iShot2020-09-2803.40.14.png

膨脹#

iShot2020-09-2803.40.09.png

示例#

kernel = getStructuringElement(int shape, Size ksize, Point anchor);
/*
 - 形狀 (MORPH_RECT \MORPH_CROSS \MORPH_ELLIPSE)
 - 大小 奇數 Size(1,1);
 - 錨點 默認是Point(-1, -1)意思就是中心像素
*/
dilate(src, dst, kernel);
erode(src, dst, kernel);

//動態調整結構元素大小
createTrackbar(const String & trackbarname, const String winName,  int* value, int count, Trackbarcallback func, void* userdata=0);
/*
形式參數一、trackbarname:滑動空間的名稱;
形式參數二、winname:滑動空間用於依附的圖像窗口的名稱;
形式參數三、value:初始化閾值;
形式參數四、count:滑動控件的刻度範圍;
形式參數五、TrackbarCallback是回調函數,其定義如下:
*/
void (CV_CDECL *TrackbarCallback)(int pos, void* userdata);

demo#

#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("D:/vcprojects/images/test1.png");
	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;
}

形態學操作#

開 open#

iShot2020-09-2804.12.31.png

關 close#

iShot2020-09-2804.12.46.png

形態學梯度 Morphological Gradient#

iShot2020-09-2804.23.08.png

頂帽 top hat#

iShot2020-09-2804.23.13.png

黑帽 black hat#

iShot2020-09-2804.23.19.png

示例#

Mat kernel = getStructuringElement(MORPH_RECT, Size(11,11), Point(-1, -1));
morphologyEx(src, dst, CV_MOP_BLACKHAT, kernel);
/*
- Mat src – 輸入圖像
 - Mat dest – 輸出結果
 - int OPT – CV_MOP_OPEN/ CV_MOP_CLOSE/ CV_MOP_GRADIENT / CV_MOP_TOPHAT/ CV_MOP_BLACKHAT 形態學操作類型
Mat kernel 結構元素
int Iteration 迭代次數,默認是1
*/

形態學操作 提取水平垂直線#

原理#

iShot2020-09-2902.29.04.png

iShot2020-09-2902.29.10.png

iShot2020-09-2902.29.15.png

iShot2020-09-2902.29.22.png

實現思路#

iShot2020-09-2902.29.32.png

示例#

//二值化
adaptiveThreshold(
Mat src, // 輸入的灰度圖像
Mat dest, // 二值圖像
double maxValue, // 二值圖像最大值
int adaptiveMethod // 自適應方法,只能其中之一 – 
		         // ADAPTIVE_THRESH_MEAN_C , ADAPTIVE_THRESH_GAUSSIAN_C 
int thresholdType,// 閾值類型
int blockSize, // 塊大小
double C // 常量C 可以是正數,0,負數
);
adaptiveThreshold(~gray_src, binImg, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 15, -2);
// ~ 反色 bitwise_not(src, dst);

	// 水平結構元素
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));
	// 矩形結構,可以用於OCR
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));

morphologyEx(src, dst, CV_MOP_BLACKHAT, hline);

圖像上採樣和下採樣#

圖像金字塔#

iShot2020-09-2903.07.24.png

iShot2020-09-2903.08.01.png

iShot2020-09-2903.07.36.png

iShot2020-09-2903.07.49.png

示例#

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
// 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);

基本閾值操作#

閾值概念#

iShot2020-09-2903.25.07.png

閾值類型#

iShot2020-09-2903.25.56.png

iShot2020-09-2903.26.08.png

iShot2020-09-2903.26.14.png

iShot2020-09-2903.26.46.png

iShot2020-09-2903.26.52.png

iShot2020-09-2903.27.06.png

示例#

cvtColor(src, gray_src, CV_BGR2GRAY);
threshold(src, dst, 0, 255, THRESH_TRIANGLE | type_value);
//OISU和TRIANGLE自動計算閾值,故dst後閾值寫0被忽略

enum ThresholdTypes {
    THRESH_BINARY     = 0,
    THRESH_BINARY_INV = 1,
    THRESH_TRUNC      = 2,
    THRESH_TOZERO     = 3,
    THRESH_TOZERO_INV = 4,
    THRESH_MASK       = 7,
    THRESH_OTSU       = 8,
    THRESH_TRIANGLE   = 16
};

自定義線型濾波#

卷積概念#

iShot2020-09-2904.00.29.png

iShot2020-09-2904.00.43.png

iShot2020-09-2904.02.52.png

iShot2020-09-2904.02.15.png

常見算子#

iShot2020-09-2904.03.02.png

自定義卷積模糊#

filter2D(
Mat src, //輸入圖像
Mat dst, // 模糊圖像
int depth, // 圖像深度32/8
Mat kernel, // 卷積核/模板
Point anchor, // 錨點位置
double delta // 計算出來的像素+delta
)

示例#

	// 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);

// 拉普拉斯算子
Mat kernel = (Mat_<int>(3, 3) << 0, -1, 0, -1, 4, -1, 0, -1, 0);

filter2D(src, dst, -1, kernel_x, Point(-1, -1), 0.0);

邊緣處理#

卷積邊緣問題#

iShot2020-09-2904.13.20.png

處理邊緣#

iShot2020-09-2904.13.34.png

示例#

copyMakeBorder(
 Mat src, // 輸入圖像
 Mat dst, // 添加邊緣圖像
 int top, // 邊緣長度,一般上下左右都取相同值,
 int bottom,
 int left,
 int right, 
 int borderType // 邊緣類型
 Scalar value 

while(true){
  c = waitKey(500);
  //esc
  if((char)c == 27)
  {break;}
}

Sobel 算子#

卷積應用 - 邊緣提取#

iShot2020-10-0204.39.29.png

iShot2020-10-0204.39.51.png

iShot2020-10-0204.40.04.png

iShot2020-10-0204.40.15.png

iShot2020-10-0204.49.30.png

示例#

Sobel (
InputArray Src // 輸入圖像
OutputArray dst// 輸出圖像,大小與輸入圖像一致
int depth // 輸出圖像深度. 
Int dx.  // X方向,幾階導數
int dy // Y方向,幾階導數. 
int ksize, SOBEL算子kernel大小,必須是1、357
double scale  = 1
double delta = 0
int borderType = BORDER_DEFAULT
)

//Sobel 的改進
Scharr (
InputArray Src // 輸入圖像
OutputArray dst// 輸出圖像,大小與輸入圖像一致
int depth // 輸出圖像深度. 
Int dx.  // X方向,幾階導數
int dy // Y方向,幾階導數. 
double scale  = 1
double delta = 0
int borderType = BORDER_DEFAULT
)

//步驟
GaussianBlur( src, dst, Size(3,3), 0, 0, BORDER_DEFAULT );
cvtColor( src,  gray, COLOR_RGB2GRAY );
Scharr(gray_src, xgrad, CV_16S, 1, 0);
Scharr(gray_src, ygrad, CV_16S, 0, 1);
// Sobel(gray_src, xgrad, CV_16S, 1, 0, 3);
// Sobel(gray_src, ygrad, CV_16S, 0, 1, 3);
convertScaleAbs(xgrad, xgrad);// 計算圖像A的像素絕對值,輸出到圖像B
convertScaleAbs(ygrad, ygrad);
addWeighted( xgrad, 0.5,ygrad, 0.5, 0, xygrad);

Laplance 算子#

理論#

iShot2020-10-0308.21.04.png

iShot2020-10-0308.21.20.png

示例#

Laplacian(
InputArray src,
OutputArray dst,
int depth, //深度CV_16S
int kisze, // 3
double scale = 1,
double delta =0.0,
int borderType = 4
)
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);
threshold(edge_image, edge_image, 0, 255, THRESH_OTSU | THRESH_BINARY);

Canny 邊緣檢測#

算法介紹#

iShot2020-10-0308.33.22.png

iShot2020-10-0308.33.30.png

iShot2020-10-0308.33.36.png

iShot2020-10-0308.40.36.png

示例#

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歸一化

cvtColor(src, gray_src, CV_BGR2GRAY);
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);

霍夫變換 - 直線檢測#

理論#

iShot2020-10-0308.48.47.png

iShot2020-10-0308.49.31.png

iShot2020-10-0308.49.25.png

iShot2020-10-0308.50.04.md.png

補充資料:https://blog.csdn.net/leonardohaig/article/details/87907462?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.edu_weight&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.edu_weight

示例#

HoughLines(
InputArray src, // 輸入圖像,必須8-bit的灰度圖像
OutputArray lines, // 輸出的極坐標來表示直線
double rho, // 生成極坐標時的像素掃描步長
double theta, //生成極坐標時的角度步長,一般取值CV_PI/180
int threshold, // 閾值,只有獲得足夠交點的極坐標點才被看成是直線
double srn=0;// 是否應用多尺度的霍夫變換,如果不是設置0表示經典霍夫變換
double stn=0;//是否應用多尺度的霍夫變換,如果不是設置0表示經典霍夫變換
double min_theta=0; // 表示角度掃描範圍 0 ~180之間, 默認即可
double max_theta=CV_PI
) // 一般情況是有經驗的開發者使用,需要自己反變換到平面空間

HoughLinesP(
InputArray src, // 輸入圖像,必須8-bit的灰度圖像
OutputArray lines, // 輸出的極坐標來表示直線
double rho, // 生成極坐標時的像素掃描步長,一般為1
double theta, //生成極坐標時的角度步長,一般取值CV_PI/180
int threshold, // 閾值,只有獲得足夠交點的極坐標點才被看成是直線
double minLineLength=0;// 最小直線長度
double maxLineGap=0;// 最大間隔
)
// extract edge
Canny(src, src_gray, 150, 200);
cvtColor(src_gray, dst, CV_GRAY2BGR);
imshow("edge image", src_gray);

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);
}

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。