QT人工智能篇-opencv

第一章 認識opencv

1. 簡單概述

????????OpenCV是一個跨平臺的開源的計算機視覺庫,主要用于實時圖像處理和計算機視覺應用?。它提供了豐富的函數和算法,用于圖像和視頻的采集、處理、分析和顯示。OpenCV支持多種編程語言,包括C++、Python、Java等,可以應用于多個領域,如人臉識別、目標檢測、圖像分割、運動估計等

2. 應用場景

  1. 機器人:用于導航、避障、物體識別等。

  2. 自動駕駛:用于車道檢測、行人識別、交通標志識別等。

  3. 醫療影像:用于圖像分割、病變檢測等。

  4. 安防監控:用于運動檢測、人臉識別等。

  5. 增強現實:用于虛擬對象的實時疊加。

  6. 工業檢測:用于產品質量檢測、缺陷識別等。

  7. 娛樂:用于手勢識別、虛擬試衣等。

3. 開發環境搭建

????????不同版本的 Visual Studio 對 C++ 標準的支持不同,因此需要選擇與之兼容的 OpenCV 版本。以下是常見的對應關系:

????????OpenCV 官方提供了針對不同 Visual Studio 版本的預編譯庫(Prebuilt Libraries),可以直接下載并使用。以下是常見的對應關系 ?

3.1 下載OpenCV-4.8.0

https://opencv.org/releases/

? ? ? ? 需要注意的是下載之后的文件后綴是.exe但它其實是一個壓縮包?

3.2 配置環境變量

將上面兩個路徑添加到環境變量中,配置成功使用opencv_version命令測試

能直接打印版本信息,說明配置成功 ?

3.3 單個項目配置

新建項目,右鍵單擊項目,選擇屬性,打開屬性配置窗口


? ? ? ? 除此之外,還有個依賴項,需要配置

說明一下:

  • 建議開始時,選擇Release版?,則添加依賴項的時候就需要選擇opencv_world480.lib
  • 另一個是Debug版的?

3.4 全局項目配置?

打開視圖->其他窗口->屬性管理器

? ? ? ? 然后在這個文件中添加下面這段代碼

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"><ImportGroup Label="PropertySheets" /><PropertyGroup Label="UserMacros" /><PropertyGroup /><ItemDefinitionGroup /><ItemGroup />
</Project>

? ? ? ? 然后重啟vs,就會發現已經更新了,并在項目屬性中多出了這個東西

說明一下:

  • 接下的操作就是右鍵文件,點擊屬性,改變同單個項目配置文件的那三個東西

4. 入門案例

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;int main()
{// 讀取圖片Mat mat = imread("C:\\Users\\46285\\Desktop\\qt_code\\images\\pkq1.png");if (mat.empty()){cout << "圖片讀取失敗" << endl;return -1;}// 創建一個窗口,用來展示圖片// 窗口大小根據圖片自適應namedWindow("圖片展示", WINDOW_AUTOSIZE);// 展示圖片到窗口中imshow("圖片展示", mat);// 等待,0表示無限等待,直到按下任意鍵// 如果>0 表示停留N毫秒waitKey(0);return 1;
}

?4.1?imread讀取圖片

imread功能是加載圖像文件成為一個Mat對象:

CV_EXPORTS_W Mat imread( const String& filename, int flags = IMREAD_COLOR );

參數說明:

  • 第一個參數為文件路徑
  • 第二個參數為標志位讀取文件的格式:?
    • IMREAD_COLOR(默認):以彩色圖像讀取(默認),忽略 alpha 通道
    • IMREAD_GRAYSCALE:以灰度圖像讀取
    • IMREAD_UNCHANGED:保留圖像的原始通道數,包括 alpha 通道(如透明 PNG)
    • ....

返回值說明:?

  • 返回的是個Mat類型,是一個存儲像素的數組?
  • 當然如果返回為空,則表示圖片讀到失敗,即有可能是路徑錯誤

4.2?namedWindow創建窗口

namedWindow 是 OpenCV 中用于創建一個窗口的函數

CV_EXPORTS_W void namedWindow(const String& winname, int flags = WINDOW_AUTOSIZE);?

參數說明:

  • 第一個參數為窗口的名字?
  • 第二個參數為標志位:指定窗口的行為
    • WINDOW_AUTOSIZE(默認):窗口大小自動適應圖像,不能手動調整大小?
    • WINDOW_NORMAL:允許手動調整窗口大小

4.3 inshow窗口展示

CV_EXPORTS_W void imshow(const String& winname, InputArray mat);?

參數說明:

  • ?第一個參數為窗口名字
  • 第二個參數為:顯示的圖像數據,就是imread讀取到的圖像

4.4 waitKey等待鍵盤輸入

? ? ? ? 通常使用這個函數用來長時間顯示imshow顯示的窗口

CV_EXPORTS_W int waitKey(int delay = 0);?

參數說明:

  • ?當參數為0時:無限等待,直到用戶按下任意鍵
  • > 0:等待指定毫秒數,超時后自動繼續程序
  • < 0:和 0 類似,實際效果是無限等待

返回值說明:

  • 返回按鍵的 ASCII 值或鍵碼(例如 'q' 的值是 113
  • 且如果超時未按鍵,則返回 -1
void function3(Mat& src)
{while (true){imshow("圖片展示", src);// waitKey()等待用戶操作int c = waitKey(2000);// 等待2秒// 27就是esc按鍵,表明要退出if (c == 27){cout << c << endl;break;}}
}

?說明一下:

  • 這段代碼捕捉的是esc按鍵,當esc按下之后,就會退出

4.5?destroyAllWindows

? ? ? ? 這就是一個銷毀所有窗口的函數,使用時?destroyAllWindows(),雖然簡單,但是每次還是必須要加上的

5.?cvtColor修改圖像

????????cvtColor 是 OpenCV 中用來轉換圖像顏色空間的函數,比如常見的 BGR → 灰度、BGR → HSV 等。

CV_EXPORTS_W void cvtColor(InputArray src, OutputArray dst, int code, int dstCn = 0);

參數說明:?

  • 第一個參數:通常類型是Mat,就是原始的輸入圖像

  • 第二個參數:通常類似是Mat,就是用來接收修改后的圖像的

  • 第三個參數用于指定顏色轉換的類型

    • COLOR_BGR2GRAY 表示為BGR → 灰度圖?
    • COLOR_BGR2RGB 表示為BGR → RGB
    • COLOR_BGR2HSV 表示為BGR → HSV 色彩空間
    • COLOR_BGR2Lab 表示為BGR → Lab 色彩空間
    • COLOR_GRAY2BGR 表示為灰度圖 → BGR(三通道)
    • COLOR_RGB2BGR 表示為RGB → BGR(調換通道順序)
void function1(Mat& src)
{// 處理圖像Mat temp;// 將BGR圖像轉換為GRAY圖像cvtColor(src, temp, COLOR_BGR2GRAY);namedWindow("灰度", WINDOW_AUTOSIZE);imshow("灰度", temp);
}

?

6. imwrite保存圖像

?????????imwrite 是 OpenCV 中用于將圖像保存到文件的函數,是圖像輸出環節的核心函數之一。

CV_EXPORTS_W bool imwrite(const String& filename, InputArray img, const std::vector<int>& params = std::vector<int>());?

參數說明:

  • 第一個參數:保存的文件路徑
  • 第二個參數:為被保存的圖像數據
  • 第三個參數(可選):保存時的參數設置,例如壓縮質量等
    • PNG?IMWRITE_PNG_COMPRESSION?壓縮級別,范圍 0-9(默認 3)
    • JPEG?IMWRITE_JPEG_QUALITY?圖像質量,范圍 0-100(默認 95)
    • WEBP?IMWRITE_WEBP_QUALITY?質量參數,范圍 1-100
void function2(Mat& src)
{// 處理圖像Mat temp;// 將BGR圖像轉換為GRAY圖像cvtColor(src, temp, COLOR_BGR2GRAY);namedWindow("灰度", WINDOW_AUTOSIZE);imshow("灰度", temp);// 保存圖像路徑imwrite("C:\\Users\\46285\\Desktop\\qt_code\\save.png", temp);
}

?第二章 圖像基礎操作

1. 通道

單通道(灰度圖像):0-255,其中0表示黑色,255表示白色,也被稱為

三通道(彩色圖像):每個像素由三個數值分別表示藍色(Blue)綠色(Green) 紅色(Red)的強度

其他通道類型 :除了常見的單通道和三通道外,還有四通道和多通道

2. 數據類型

2.1 CV_8U無符號整型

每個像素的值是一個 8 位無符號整數,取值范圍為 [0, 255]

2.2 CV_32F浮點型

每個像素的值是一個 32 位浮點數,取值范圍為 [0.0, 1.0] 或其他浮點范圍

3. Mat對象

????????就是一個多維數組,可以用來表示圖像矩陣數據。支持多種數據類型,支持多通道數據

Mat包含:數據頭(圖像基本信息),數據指針(實際存儲像素的內存區域),引用計數 ?

3.1 創建mat對象 && 賦值

void function4(Mat& src)
{// 創建一個4*4大小的單通道空白圖像Mat m1 = Mat::zeros(400, 400, CV_8UC1);// 給單通道賦值m1 = 255;// 三通道Mat m2 = Mat::zeros(400, 400, CV_8UC3);// 給三通道賦值m2 = Scalar(0, 0, 255);imshow("m2", m2);
}

說明一下:

  • CV_8UC1表示單通道無符號整型
  • CV_8UC3表示三通道無符號整型

3.2 克隆 && 拷貝

? ? ? ? Mat對象的拷貝構造賦值重載和C++默認規則都是一樣都是淺拷貝

void function5(Mat& src)
{Mat m1 = Mat::zeros(400, 400, CV_8UC3);Mat m2 = m1;// 拷貝構造是淺拷貝Mat m3;m3 = m1;// 賦值重載也是淺拷貝Mat m4 = m1.clone();// 克隆是深拷貝Mat m5;m1.copyTo(m5);// 等價于m5 = m1(深拷貝)}

3.3 其他函數

cv::Mat::rows返回矩陣的行數。高
cv::Mat::cols返回矩陣的列數。寬
cv::Mat::channels()返回矩陣的通道數。
cv::Mat::empty()檢查矩陣是否為空。
cv::Mat::clone()返回矩陣的深拷貝。
cv::Mat::copyTo()將矩陣復制到另一個矩陣。
cv::Mat::convertTo()將矩陣轉換為另一種數據類型。
cv::Mat::reshape()改變矩陣的形狀(不改變數據)。

4.?圖像像素基本操作

????????我們可以通過對圖像的像素做讀寫操作,從而修改圖像像素的值,改變圖像的亮度&對比度

Mat對象本身就是一個保存像素點的二維數組?

4.1 圖像像素的訪問

?數組

void function7(Mat& src)
{Mat m1 = src.clone();int w = m1.cols;int h = m1.rows;int channel = m1.channels();// 通過數組遍歷for (int row = 0; row < h; row++){for (int col = 0; col < w; col++){// 單通道 - 灰度圖像if (channel == 1){// 等價于int pv = m1[i][j];int pv = m1.at<uchar>(row, col);// 泛型需要傳類型m1.at<uchar>(row, col) = 255 - pv;}// 三通道 - 彩色圖像if (channel == 3){Vec3b bgr = m1.at<Vec3b>(row, col);m1.at<Vec3b>(row, col)[0] = 255 - bgr[0];m1.at<Vec3b>(row, col)[1] = 255 - bgr[1];m1.at<Vec3b>(row, col)[2] = 255 - bgr[2];}}}imshow("圖像修改后", m1);
}

?

?說明一下:

  • 訪問單通道Mat數組下標的方法是 對象名.at<類型>(指定行,指定列)
    比如:m1.at<uchar>(row, col)
  • 訪問三通道Mat數組下標的方法是 對象名.at<類型>(指定行,指定列)[指定通道]
    比如:<Vec3b>m1.at<Vec3b>(row, col)[0]

指針

// 通過指針遍歷
for (int row = 0; row < h; row++)
{uchar* current_row = m1.ptr<uchar>(row);for (int col = 0; col < w; col++){// 單通道 - 灰度圖像if (channel == 1){int pv = *current_row;*current_row++ = 255 - pv;}// 三通道 - 彩色圖像if (channel == 3){*current_row++ = 255 - *current_row;*current_row++ = 255 - *current_row;*current_row++ = 255 - *current_row;}}
}
imshow("圖像修改后", m1);

?4.2 圖像像素的加減乘除

自己獲取像素的值做計算

void function8(Mat& src)
{Mat m1 = src.clone();Mat m2 = Mat(m1.size(), m1.type());m2 = Scalar(50, 50, 50);Mat m3 = Mat(m1.size(), m1.type());int w = m1.cols;int h = m1.rows;int channel = m1.channels();// 通過數組遍歷for (int row = 0; row < h; row++){for (int col = 0; col < w; col++){Vec3b b1 = m1.at<Vec3b>(row, col);Vec3b b2 = m2.at<Vec3b>(row, col);// 因為像素的值的范圍是0-255,因此做了計算后,如果擔心像素的值超過這個范圍,就可以使用saturate_cast()函數模板,將值限制在0-255之間m3.at<Vec3b>(row, col)[0] = saturate_cast<uchar>(b1[0] - b2[0]);m3.at<Vec3b>(row, col)[1] = saturate_cast<uchar>(b1[1] - b2[1]);m3.at<Vec3b>(row, col)[2] = saturate_cast<uchar>(b1[2] - b2[2]);}}imshow("加減乘除", m3);
}

說明一下:

  • 由于圖像的加減乘除可以會超過[0,255],所以可以加上saturate_cast<uchar>(像素值)

直接使用Mat對象做計算 ?

void function9(Mat& src)
{Mat m1 = src.clone();Mat dst;dst = m1 + Scalar(50, 50, 50);imshow("加法", dst);dst = m1 - Scalar(50, 50, 50);imshow("減法", dst);dst = m1 / Scalar(2, 2, 2);imshow("除法", dst);// opencv不支持這樣直接做乘法操作 dst = m1 * Scalar(1, 1, 1);imshow("乘法", dst);
}

?說明一下:

  • 目標圖像 = 源圖像 + Scalar(像素值,像素值,像素值)
  • 注意:OpenCV不支持 對象名 * Scalar(像素值,像素值,像素值)

使用提供的函數做計算 ?

void function10(Mat& src)
{Mat m1 = src.clone();Mat m2 = Mat(m1.size(), m1.type());m2 = Scalar(50, 50, 50);Mat dst;// 加法add(m1, m2, dst);imshow("加法", dst);// 減法subtract(m1, m2, dst);imshow("減法", dst);// 乘法multiply(m1, Scalar(2, 2, 2), dst);imshow("乘法", dst);// 除法divide(m1, Scalar(2, 2, 2), dst);imshow("除法", dst);
}

?

?

說明一下:

  • ?add加法,subtract減法,multiply乘法,divide除法

4.3 圖像像素的位運算

與運算 bitwise_and():可以用于提取圖像中的特定區域(掩碼操作)。等價于&

或運算 bitwise_or():可以用于合并圖像或填充區域。等價于|

非運算 bitwise_not():可以用于圖像的反色操作。等價于!

異或運算 bitwise_xor():可以用于檢測圖像差異或生成特殊效果,等價于^

void function11(Mat& src)
{Mat m1 = Mat::zeros(Size(255, 255), CV_8UC3);Mat m2 = Mat::zeros(Size(255, 255), CV_8UC3);rectangle(m1, Point(100, 50), Point(200, 150), Scalar(255, 255, 0), -1, LINE_8);rectangle(m2, Point(100, 50), Point(200, 150), Scalar(0, 255, 255), -1, LINE_8);imshow("m1", m1);imshow("m2", m2);Mat dst;//bitwise_and(m1, m2, dst);//bitwise_or(m1, m2, dst);//bitwise_not(m1, dst);bitwise_xor(m1, m2, dst);imshow("邏輯運算", dst);
}

?

? 4.4?addWeighted圖像加權融合

????????addWeighted()是 OpenCV 中用于 圖像加權融合 的函數。它可以將兩張圖像按照指定的權重進行線性組合,生成一張新的圖像。這個函數常用于圖像混合、透明度調整、圖像疊加等場景。

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

參數說明:

  • src1:輸入的第一張圖像(可以是 Mat 或 UMat)。alpha:第一張圖像的權重(比例因子)

  • src2:輸入的第二張圖像(同樣可以是 Mat 或 UMat)。beta:第二張圖像的權重(比例因子)

  • gamma:加到最終結果上的一個值(可以理解為整體加亮或者偏移

  • dst:輸出圖像(結果會存在這里)

  • dtype:可選參數,輸出圖像的數據類型默認是 -1,表示和輸入圖像相同。

src1圖像和src2圖像大小應該一致

比例因子是0-1之間的浮點

void function12(Mat& src)
{Mat m1 = src.clone();// 創建一張和m1一樣大小的圖像,并將圖像顏色變成藍色Mat m2 = Mat::zeros(m1.size(), m1.type());m2 = Scalar(255, 0, 0);// 圖像加權融合Mat dst;addWeighted(m1, 0.5, m2, 0.5, 50, dst);imshow("圖像加權融合", dst);
}

4.5?applyColorMap偽彩色映射

????????applyColorMap() 是 OpenCV 中用于 偽彩色映射 的函數。它可以將灰度圖像或單通道圖像轉換為彩色圖像,通過應用預定義的顏色映射表(Colormap)來增強圖像的視覺效果。偽彩色映射常用于熱力圖、深度圖、醫學圖像等場景

void cv::applyColorMap(InputArray src,OutputArray dst,int colormap
);

說明一下:

  • src:輸入圖像,通常是單通道的灰度圖CV_8UC1),也可以是已經歸一化后的單通道圖。

  • dst:輸出圖像,會是一個彩色的三通道圖CV_8UC3

  • colormap:預定義的顏色映射表編號,比如 COLORMAP_JET, COLORMAP_HOT, COLORMAP_COOL,等等。

使用場景:

  • 熱力圖:將灰度圖像轉換為彩色圖像,增強視覺效果。
  • 深度圖:將深度信息映射為彩色圖像,便于觀察。
  • 醫學圖像:增強醫學圖像的對比度,便于診斷。
  • 數據可視化:將單通道數據(如溫度、高度等)映射為彩色圖像。
void function13(Mat& src)
{Mat m1 = src.clone();Mat dst = Mat::zeros(m1.size(), m1.type());int i = 0;while (true){int key = waitKey(500);if (key == 27){break;}applyColorMap(m1, dst, i % 21);i++;imshow("彩色映射", dst);}
}

?

說明一下:

  • 一共有21種不同的顏色映射,感覺有點像ps的lut

5. 圖像通道的分離與操作

????????在圖像處理中,我們可以將一張彩色圖像的BGR通道,拆分為多個單通道圖像,或者對每個通道進行單獨處理,然后再將處理后的通道合并一張多通道圖像

?5.1?split通道分離

????????將多通道圖像拆分為多個單通道圖像。例如,將 BGR 彩色圖像拆分為藍色(B)、綠色(G)和紅色(R)三個單通道圖像

void function14(Mat& src)
{// 用來存儲單個通道的數組vector<Mat> channels;split(src, channels);Mat b = channels[0];Mat g = channels[1];Mat r = channels[2];imshow("b通道", b);imshow("g通道", g);imshow("r通道", r);
}

5.2?merge通道合并

????????將處理后的單通道圖像重新合并為一張多通道圖像

void function14(Mat& src)
{// 用來存儲單個通道的數組vector<Mat> channels;split(src, channels);Mat b = channels[0];Mat g = channels[1];Mat r = channels[2];//imshow("b通道", b);//imshow("g通道", g);//imshow("r通道", r);channels[2] = 0;    // 屏蔽r通道Mat dst = Mat::zeros(src.size(), src.type());merge(channels, dst);imshow("通道合并", dst);}

?

5.3?mixChannel通道混合與重組

?????????交換各個通道的值,比如:將B通道的值交換到G通道

void cv::mixChannels(const Mat* src,      // 輸入圖像數組size_t nsrcs,        // 輸入圖像的數量Mat* dst,            // 輸出圖像數組size_t ndsts,        // 輸出圖像的數量const int* fromTo,   // 通道映射關系size_t npairs        // 通道映射對的數量
);

參數說明:

  • src:輸入圖像數組(可以是一個或多個圖像)。

  • nsrcs:輸入圖像的數量。

  • dst:輸出圖像數組(可以是一個或多個圖像)。

  • ndsts:輸出圖像的數量。

  • fromTo:通道映射關系數組,表示從輸入圖像的哪個通道復制到輸出圖像的哪個通道。

  • npairs:通道映射對的數量。

void function14(Mat& src)
{Mat dst = Mat::zeros(src.size(), src.type());vector<Mat> channels;split(src, channels);channels[0] = 0;// 屏蔽b通道merge(channels, dst);int from_to[] = {0,1 ,   // 將src的b通道復制到dst的g通道1,2 ,   // 將src的g通道復制到dst的r通道2,0 };mixChannels(&src, 1, &dst, 1, from_to, 3);imshow("通道混合", dst);}

?

6. 色彩空間轉換

6.1?inRange摳圖

????????inRange()是 OpenCV 中用于根據顏色范圍提取圖像中特定區域的函數。它通常用于顏色過濾閾值化操作,比如提取圖像中某種顏色的區域。可以用這個函數來實現類似摳圖的操作

????????滿足顏色范圍的像素值為 255(白色)不滿足顏色范圍的像素值為 0(黑色) 可以用它來做一個類似變化背景,摳圖這么一個操作

void cv::inRange(InputArray src,       // 輸入圖像(單通道或多通道)InputArray lowerb,    // 下限(Scalar 或 Mat)InputArray upperb,    // 上限(Scalar 或 Mat)OutputArray dst       // 輸出二值圖像(單通道,CV_8U 類型)
);

參數說明:

  • src: 輸入圖像,可以是單通道或多通道圖像(例如灰度圖或彩色圖)。
  • lowerb: 下限值,表示顏色范圍的下限。如果輸入是多通道圖像,lowerb 應該是一個 Scalar 或 Mat,每個通道對應一個下限值。
  • upperb: 上限值,表示顏色范圍的上限。同樣,如果輸入是多通道圖像,upperb 應該是一個 Scalar 或 Mat,每個通道對應一個上限值。
  • dst: 輸出圖像,是一個二值圖像(單通道,CV_8U 類型)。滿足條件的像素值為 255,不滿足條件的像素值為 0

?????????HSV色彩空間通過色相、飽和度和明度來表示顏色,具有更好的直觀性,符合人類對顏色的感知方式。它常用于圖像處理中的顏色提取、目標追蹤、分割等任務

void function14(Mat& src)
{// step1: 將圖像轉換為HSVMat hsv;cvtColor(src, hsv, COLOR_BGR2HSV);// step2: 提取圖像的指定顏色Mat dst;inRange(hsv, Scalar(35, 43, 46), Scalar(77, 255, 255), dst);// step3: 再將圖像去反bitwise_not(dst, dst);// step4: 設置背景圖Mat redDst = Mat::zeros(src.size(), src.type());redDst = Scalar(40, 40, 200);// 拷貝原圖到redDstsrc.copyTo(redDst, dst);imshow("背景改變", redDst);
}

說明一下:

  • ?需要使用inRange摳圖首先就需要將bgr圖像變成HSV圖像
  • src.copyTo(redDst, dst);中的第二個參數為mask: 掩碼圖像,必須是單通道的 8 位圖像(CV_8U)。掩碼中值為 255 的像素位置會被復制,值為 0 的像素位置不會被復制。

? 7. 像素統計

7.1 minMaxLoc查找圖像中的最小值和最大值及其位置

void cv::minMaxLoc(InputArray src,                // 輸入圖像或矩陣(單通道)double* minVal,                // 返回的最小值double* maxVal,                // 返回的最大值Point* minLoc = nullptr,       // 返回的最小值位置(可選)Point* maxLoc = nullptr,       // 返回的最大值位置(可選)InputArray mask = noArray()    // 可選的掩碼,用于指定計算區域
);

參數說明:

  • src:輸入圖像或矩陣,必須是單通道(灰度圖像)
  • minVal:返回的最小值。
  • maxVal:返回的最大值。
  • minLoc:返回的最小值的位置(cv::Point 類型,可選)。
  • maxLoc:返回的最大值的位置(cv::Point 類型,可選)。
  • mask:可選的掩碼,用于指定計算區域。掩碼必須是單通道的 8 位圖像(CV_8U),且大小與輸入圖像相同。

7.2 meadStdDev計算圖像的均值和標準差

void cv::meanStdDev(InputArray src,         // 輸入圖像或矩陣OutputArray mean,       // 輸出的均值(每個通道的均值)OutputArray stddev,     // 輸出的標準差(每個通道的標準差)InputArray mask = noArray() // 可選的掩碼,用于指定計算區域
);

參數說明:

  • src:輸入圖像或矩陣,可以是單通道或多通道。

  • mean:輸出的均值,類型為 cv::Scalar。對于多通道圖像,mean 的每個元素對應一個通道的均值。

  • stddev:輸出的標準差,類型為 cv::Scalar。對于多通道圖像,stddev 的每個元素對應一個通道的標準差。

  • mask:可選的掩碼,用于指定計算區域。掩碼必須是單通道的 8 位圖像(CV_8U),且大小與輸入圖像相同。

void function14(Mat& src)
{Mat m1;cvtColor(src, m1, COLOR_BGR2GRAY);imshow("灰度圖像", m1);double minVal, maxVal;Point minLoc, maxLoc;// minMaxLoc()只能用來統計灰度圖像的,因此如果是一張彩色圖像,需要先將// 彩色圖像轉為灰度圖像minMaxLoc(m1, &minVal, &maxVal, &minLoc, &maxLoc);cout << "圖片像素最小值:" << minVal << "   最大值:" << maxVal << endl;cout << "最小值在" << minLoc << "    最大值在" << maxLoc << endl;// 計算均值和標準差Scalar mean, stddev;meanStdDev(src, mean, stddev);cout << "均值:" << mean[0] << endl;cout << "標準差:" << stddev[0] << endl;
}

說明一下:

  • 要進行像素統計,首先這張圖片一定要是灰度圖像

?8. 圖像像素轉換

8.1?convertTo像素類型轉換

????????將圖像的像素數據類型從一種格式轉換為另一種(如`uchar`轉`float`).Mat對象有提供一個函數convertTo()用于像素類型轉換

void cv::Mat::convertTo(OutputArray dst,int dtype,double alpha = 1,double beta = 0
) const;

參數說明:

  • dst:輸出圖像矩陣。

  • dtype:輸出圖像的數據類型,比如 CV_8U, CV_32F, CV_64F 等等。

  • alpha:可選縮放因子(scale factor)。

  • beta:可選偏移量(shift factor)

void function14(Mat& src)
{// 轉換為浮點型(CV_32FC3)Mat dst_float;src.convertTo(dst_float, CV_32FC3, 1.0 / 255.0);imshow("float型", dst_float);// 轉換為雙精度型Mat dst_double;src.convertTo(dst_double, CV_64FC1);imshow("雙精度型", dst_double);
}

?

8.2 什么情況下要使用像素類型轉換?

  • 它是為了防止計算溢出,比如當圖像需要進行數值運算(如乘法、卷積、矩陣運算)時
  • 滿足算法輸入要求。許多高級算法(如深度學習、濾波、矩陣分解)要求輸入為浮點型或特定范圍。等等還有很多場景都需要做像素類型轉換,來滿足一些特定場景 ?

9. 圖像像素歸一化?

9.1?normalize歸一化

?????????歸一化是指將圖像的像素值通過線性或非線性變換映射到特定范圍(如 [0,1] 或 [-1,1]),目的是消除數據量綱差異,提升算法魯棒性。它是圖像預處理的核心步驟之一

void cv::normalize(InputArray  src,              // 輸入圖像/矩陣OutputArray dst,              // 輸出圖像/矩陣double      alpha = 0,        // 歸一化下限(如0.0)double      beta = 1,         // 歸一化上限(如1.0)int         norm_type = NORM_L2,  // 歸一化類型int         dtype = -1,       // 輸出數據類型(默認與src相同)InputArray  mask = noArray()  // 可選掩碼
);

9.2?為什么需要歸一化?

  • 統一數據尺度:避免數值較大的像素主導模型訓練(如深度學習)。
  • ?加速收斂:優化算法(如梯度下降)在歸一化數據上更快收斂。 ?
  • 適應算法要求:許多算法(如SVM、神經網絡)要求輸入數據在固定范圍內。 ?
  • 增強對比度:例如直方圖均衡化就是一種非線性歸一化。

9.2 歸一化類型

?9.3?線性歸一化:將像素值線性映射到 [0,1]

void function14(Mat& src)
{Mat dst;normalize(src, dst, 0.0, 1.0, NORM_MINMAX, CV_32F);imshow("歸一化", dst);for (int i = 0; i < 3; i++){for (int j = 0; j < 3; j++){cout << (int)src.at<uchar>(i, j) << "\t";}cout << endl;}for (int i = 0; i < 3; i++){for (int j = 0; j < 3; j++){cout << dst.at<float>(i, j) << "\t";}cout << endl;}
}

9.4?均值方差歸一化

void function14(Mat& src)
{Mat dst;src.convertTo(dst, CV_32F);// 計算均值和方差Scalar mean, stddev;meanStdDev(dst, mean, stddev);cout << "均值:" << mean[0] << "    方差值:" << stddev << endl;// 歸一化// 將圖像的像素值轉換為均值為0、標準差為1的標準正態分布// 將圖像 dst 的每個像素值減去均值 mean[0],使數據分布的中心平移到0。// 將平移后的像素值除以標準差 stddev[0],使數據的標準差變為1。dst = (dst - mean[0]) / stddev[0];meanStdDev(dst, mean, stddev);cout << "均值:" << mean[0] << "    方差值:" << stddev << endl;imshow("歸一化", dst);
}

?

9.5?歸一化到特定范圍

?????????比如歸一化到-1 到 1

void function14(Mat& src)
{Mat dst;normalize(src, dst, -1.0, 1.0, NORM_MINMAX, CV_32F);imshow("歸一化", dst);for (int i = 0; i < 3; i++){for (int j = 0; j < 3; j++){cout << (int)src.at<uchar>(i, j) << "\t";}cout << endl;}for (int i = 0; i < 3; i++){for (int j = 0; j < 3; j++){cout << dst.at<float>(i, j) << "\t";}cout << endl;}
}

?

第三章 圖像幾何處理

1. 繪制幾何形狀的圖像?

1.1 rectangle繪制矩形

????????cv::rectangle() 是 OpenCV 中用于在圖像上繪制矩形的函數。它可以用來繪制矩形框,常用于目標檢測、圖像標注、區域標記等任務

void cv::rectangle(InputOutputArray img,              // 輸入輸出圖像Point pt1,                        // 矩形的一個頂點Point pt2,                        // 矩形的對角頂點const Scalar& color,              // 矩形的顏色int thickness = 1,                // 線條厚度,-1表示的填充該矩形int lineType = LINE_8,            // 線條類型int shift = 0                     // 點坐標的小數位數
);
void cv::rectangle(InputOutputArray img,              // 輸入輸出圖像Rect rec,                         // 矩形區域const Scalar& color,              // 矩形的顏色int thickness = 1,                // 線條厚度int lineType = LINE_8,            // 線條類型int shift = 0                     // 點坐標的小數位數
);

?參數說明:

  • thickness:線條的厚度。如果為負值(如 -1),表示填充矩形。
  • lineType:線條類型,默認為 cv::LINE_8(8-connected line)。其他可選值:cv::LINE_4(4-connected line)或 cv::LINE_AA(抗鋸齒線條)。
  • shift:點坐標的小數位數(通常為 0)。

void function14(Mat& src)
{// 矩形左上角的點Point p1(100, 50);// 矩形右下角的點  Point是坐標Point p2(250, 250);// Point的用法//rectangle(src,p1,p2,Scalar(0,0,255),5, LINE_AA);rectangle(src, Rect(100, 50, 150, 200), Scalar(0, 255, 0), 5);imshow("矩形", src);
}

說明一下:

  • ?使用第二個參數畫矩形時:Rect(起始點X, 起始點X, 寬, 高)

1.2 circle繪制圓形

????????cv::circle()是 OpenCV 中用于在圖像上繪制圓的函數。它可以用來繪制圓形,常用于目標檢測、圖像標注、區域標記等任務

void cv::circle(InputOutputArray img,              // 輸入輸出圖像Point center,                     // 圓心坐標int radius,                       // 圓的半徑const Scalar& color,              // 圓的顏色int thickness = 1,                // 線條厚度int lineType = LINE_8,            // 線條類型int shift = 0                     // 點坐標的小數位數
);
void function14(Mat& src)
{Point center(170, 130);circle(src, center, 90, Scalar(0, 0, 255), 1);imshow("圓形", src);
}

?

?1.3?line繪制線條

void function14(Mat& src)
{Point p1(0, 0);Point p2(src.cols, src.rows);line(src, p1, p2, Scalar(0, 0, 255), 2);imshow("線條", src);
}

1.4 ellipse繪制橢圓

void cv::ellipse(InputOutputArray img,              // 輸入輸出圖像Point center,                     // 橢圓中心Size axes,                        // 橢圓的長軸和短軸長度double angle,                     // 橢圓的旋轉角度(順時針方向)double startAngle,                // 圓弧的起始角度(順時針方向)double endAngle,                  // 圓弧的終止角度(順時針方向)const Scalar& color,              // 橢圓的顏色int thickness = 1,                // 線條厚度int lineType = LINE_8,            // 線條類型int shift = 0                     // 點坐標的小數位數
);
void function14(Mat& src)
{// 繪制橢圓RotatedRect rrt;// 設置橢圓的中心點rrt.center = Point(200, 200);rrt.size = Size(100, 200);rrt.angle = 45;ellipse(src, rrt, Scalar(0, 0, 255), 2, LINE_8);imshow("圖像展示", src);
}

?

1.5 繪制多邊形?

方式一: polylines() 繪制多邊形 + fillPoly() 填充多邊形

void function14(Mat& src)
{Point p1(300, 100);Point p2(200, 300);Point p3(250, 400);Point p4(350, 400);Point p5(400, 300);vector<Point> pts = { p1,p2,p3,p4,p5 };// 填充多邊形// fillPoly(src, pts, Scalar(255, 255, 0), LINE_AA);// 繪制多邊形 第三個參數,是否閉合多邊形polylines(src, pts, true, Scalar(0, 0, 255), 2, LINE_AA);imshow("圖形繪制", src);
}

?

方法二:drawContours()既繪制又填充?

?????????這個函數不是說一次只能繪制一個圖形,如果想一次性繪制多個圖形,定義多個vector<Point>,統一塞給一個vector<vector<Point>>

void cv::drawContours(InputOutputArray image,             // 輸入輸出圖像InputArrayOfArrays contours,       // 輪廓集合int contourIdx,                    // 要繪制的輪廓索引(-1 表示繪制所有輪廓)const Scalar& color,               // 輪廓顏色int thickness = 1,                 // 線條厚度int lineType = LINE_8,             // 線條類型InputArray hierarchy = noArray(),  // 輪廓層級信息int maxLevel = INT_MAX,            // 繪制輪廓的最大層級Point offset = Point()             // 輪廓坐標的偏移量
);
void function14(Mat& src)
{Point p1(300, 100);Point p2(200, 300);Point p3(250, 400);Point p4(350, 400);Point p5(400, 300);vector<Point> pts = { p1,p2,p3,p4,p5 };vector<vector<Point>> contours = { pts };// 第三個參數為第幾個圖形drawContours(src, contours, 0, Scalar(255, 255, 0), -1);imshow("圖形繪制", src);
}

說明一下:

  • ?drawContours(src, contours, 0, Scalar(255, 255, 0), -1);的第三個參數為幾個要繪制的圖形

1.6?案例練習-鼠標繪制幾何圖形?

void onMouse(int event, int x, int y, int flags, void* userdata)?

參數說明:

  • event 事件
  • x y ?指的是當前鼠標的坐標
  • flags 標志位
  • userdata ?傳遞的參數?
Point startP(-1, -1);	// 起始坐標點
Mat temp;				// 臨時Mat對象// step3: 鼠標回調函數
void onMouse(int event, int x, int y, int flags, void* userdata) {// 默認規定只能從左往右//Mat& src = (Mat&)userdata;Mat& src = *static_cast<Mat*>(userdata);  // 安全轉換// 鼠標左鍵按下,這就是起始點if (event == EVENT_LBUTTONDOWN){startP.x = x;startP.y = y;}// 鼠標左鍵抬起,這就是結束點else if (event == EVENT_LBUTTONUP){int dx = x - startP.x;// 寬int dy = y - startP.y;// 高// 繪制矩形if (dx > 0 || dy > 0){Rect rect(startP.x, startP.y, dx, dy);// 將選中區域展示在頁面上imshow("選中區域", src(rect));rectangle(src, rect, Scalar(0, 255, 0), 1);imshow("鼠標繪制", src);// 重置起始點startP.x = -1;startP.y = -1;}}// 鼠標移動else if (event == EVENT_MOUSEMOVE){int dx = x - startP.x;int dy = y - startP.y;if (startP.x > 0 && startP.y > 0){// 繪制矩形Rect rect(startP.x, startP.y, dx, dy);// 如果直接繪制,會將每一次移動的都畫出來,所以要在這一次繪制時,擦除之前的temp.copyTo(src);// 這個等價于src = temp;(深拷貝)rectangle(src, rect, Scalar(0, 255, 0), 1);imshow("鼠標繪制", src);}}
} void test(Mat& src)
{// step1 創建窗口namedWindow("鼠標繪制", WINDOW_AUTOSIZE);// step2 將窗口和鼠標事件綁定setMouseCallback("鼠標繪制", onMouse, &src);imshow("鼠標繪制", src);temp = src.clone();
}

說明一下:

  • imshow("選中區域", src(rect));? 將選中區域展示在頁面上? ??

2.?圖像處理

2.1 resize圖像縮放

插值方法,用于計算目標圖像的像素值。常用的插值方法有:

  • cv::INTER_NEAREST:最近鄰插值(速度快,質量低)。

  • cv::INTER_LINEAR:雙線性插值(默認值,速度較快,質量較好)。

  • cv::INTER_CUBIC:雙三次插值(速度較慢,質量較高)。

  • cv::INTER_AREA:區域插值(適合縮小圖像)。

  • cv::INTER_LANCZOS4:Lanczos 插值(速度最慢,質量最高)

void function1(Mat& src)
{Mat zoomin, zoomout;int w = src.cols;int h = src.rows;resize(src, zoomin, Size(w / 2, h / 2), 0, 0);resize(src, zoomout, Size(w * 1.5, h * 1.5), 0, 0);imshow("縮小", zoomin);imshow("放大", zoomout);
}

?

?2.2 flip圖像翻轉

????????圖像的翻轉不是圖像的旋轉,是沿著x或者y軸,將整張圖片類似于照鏡子一樣的,進行處理

void function1(Mat& src)
{Mat dst;// = 0: 沿 x 軸(垂直軸)翻轉。// > 0: 沿 y 軸(水平軸)翻轉。 // < 0 : 同時沿 x 軸和 y 軸翻轉。flip(src, dst, -90);imshow("圖像翻轉", dst);
}

?

說明一下:

  • = 0: 沿 x 軸(垂直軸)翻轉。
  • > 0: 沿 y 軸(水平軸)翻轉。?
  • < 0 : 同時沿 x 軸和 y 軸翻轉。

2.3 roi圖像裁剪

void function1(Mat& src)
{Rect roi(243, 6, 280, 210);//裁剪后的圖像是原圖像的一個子矩陣,修改裁剪后的圖像會影響原圖像。// 如果需要獨立的副本,可以使用cv::Mat::clone()方法。Mat dst = src(roi).clone();imshow("裁剪圖像", dst);imshow("原圖片", src);
}

2.4?warpAffine圖像仿射變換

????????warpAffine() 是 OpenCV 中用于執行仿射變換的函數。仿射變換包括旋轉、縮放、平移和剪切等操作

void warpAffine(InputArray src, OutputArray dst, InputArray M, Size dsize, int flags = INTER_LINEAR, int borderMode = BORDER_CONSTANT, const Scalar& borderValue = Scalar()
);

參數說明:

  • M: 2x3 的變換矩陣。
  • dsize: 輸出圖像的大小。
  • flags: 插值方法,默認為 INTER_LINEAR。常見的插值方法有:
    • INTER_NEAREST: 最近鄰插值
    • INTER_LINEAR: 雙線性插值(默認)
    • ?INTER_CUBIC: 雙三次插值
    • INTER_AREA: 區域插值
  • borderMode: 邊界填充模式,默認為 BORDER_CONSTANT。常見的邊界模式有:
    • BORDER_CONSTANT: 使用常數值填充邊界
    • BORDER_REPLICATE: 復制邊界像素
    • BORDER_REFLECT: 反射邊界像素
  • borderValue: 當 borderMode 為 BORDER_CONSTANT 時使用的邊界填充值,默認為 Scalar(),即黑色。

圖像旋轉?

void function1(Mat& src)
{Mat dst, M;int w = src.cols;int h = src.rows;// 矩陣中心點就是原圖的中心點// 做45度旋轉  按照1:1的比例縮放M = getRotationMatrix2D(Point(w / 2, h / 2), 45, 1.0);// 計算旋轉后的圖像邊界框Rect2f bbox = RotatedRect(Point2f(), src.size(), 45).boundingRect2f();// 調整平移分量M.at<double>(0, 2) += bbox.width / 2.0 - src.cols / 2.0;  // 水平偏移M.at<double>(1, 2) += bbox.height / 2.0 - src.rows / 2.0; // 垂直偏移// 仿射變換warpAffine(src, dst, M, bbox.size(), INTER_LINEAR, BORDER_CONSTANT, Scalar(255, 255, 0));imshow("旋轉變換", dst);
}

?

圖像平移?

void function1(Mat& src)
{// 定義平移量float tx = 100.0;  // 水平方向平移 100 像素float ty = 50.0;   // 垂直方向平移 50 像素// 定義平移矩陣Mat M = (Mat_<double>(2, 3) << 1, 0, tx, 0, 1, ty);// 計算輸出圖像的大小Size dsize(src.cols + tx, src.rows + ty);// 應用平移變換Mat dst;warpAffine(src, dst, M, dsize);// 顯示結果imshow("Translated", dst);
}

?

?2.5?圖像透射變換

????????透射變換(Perspective Transformation) 是一種將圖像從一個視角投影到另一個視角的幾何變換。它通常用于校正圖像的透視畸變,例如將傾斜拍攝的文檔圖像轉換為正面視角,或者將圖像投影到另一個平面上。

????????透射變換是一種非線性變換,它通過一個 3x3 的變換矩陣 將圖像中的像素映射到新的位置。與仿射變換(Affine Transformation)不同,透射變換可以處理透視效果,因此更適合處理傾斜或變形的圖像。

透射變換的數學形式如下:

透射變換的作用包括:

  • 圖像校正:將傾斜拍攝的圖像(如文檔、車牌)轉換為正面視角。

  • 虛擬視角生成:將圖像投影到另一個平面,生成新的視角。

  • 增強現實:將虛擬物體投影到真實場景中

透射變化實現步驟:

  1. 確定原圖像和目標圖像的對應點:通常需要手動或自動選擇 4 個對應點。

  2. 計算透射變換矩陣:使用 cv::getPerspectiveTransform 函數。

  3. 應用透射變換:使用 cv::warpPerspective 函數。

void function1(Mat& src)
{// 原圖像的點Point2f p1(102, 9);Point2f p2(91, 348);Point2f p3(503, 382);Point2f p4(492, 4);vector<Point2f> vps = { p1,p2,p3,p4 };// 目標圖像的點float w = cv::norm(vps[1] - vps[0]);  // 計算寬度float h = cv::norm(vps[2] - vps[0]); // 計算高度// 變換后的四個點Point2f pf1(10, 10);Point2f pf2(50, h - 30);Point2f pf3(w - 50, h - 70);Point2f pf4(w - 40, 50);vector<Point2f> vp2 = { pf1,pf2,pf3,pf4 };// 計算透射變換矩陣Mat M = getPerspectiveTransform(vps, vp2);// 應用透射變換Mat dst;warpPerspective(src, dst, M, src.size());imshow("透射變換", dst);
}

?

第四章?圖像灰度化和二值化

1. 灰度化

????????灰度化是將彩色圖像轉換為灰度圖像的過程,將RGB三個通道的信息合并為一個亮度通道,每個像素值范圍為0(黑)到255(白)

void function1(Mat& src)
{Mat dst = Mat::zeros(src.size(), src.type());cvtColor(src, dst, COLOR_BGR2GRAY);imshow("灰度圖像", dst);
}

?

2.?threshold二值化

????????二值化是將灰度圖像轉換為只有黑白兩種顏色的圖像,通過設定閾值將像素分為前景(255)背景(0)

固定閾值:
cv::Mat binaryImage;
cv::threshold(grayImage, binaryImage, 128, 255, cv::THRESH_BINARY);自適應閾值:
cv::Mat adaptiveBinary;
cv::adaptiveThreshold(grayImage, adaptiveBinary, 255, cv::ADAPTIVE_THRESH_GAUSSIAN_C,cv::THRESH_BINARY, 11, 2);Otsu大津算法(自動確定最佳閾值)cv::Mat otsuBinary;
cv::threshold(grayImage, otsuBinary, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);

使用案例?

void function1(Mat& src)
{Mat dst = Mat::zeros(src.size(), src.type());cvtColor(src, dst, COLOR_BGR2GRAY);imshow("灰度圖像", dst);// 二值化-固定閾值Mat dst1;threshold(dst, dst1, 128, 255, THRESH_BINARY);//imshow("二值化", dst1);Mat dst2;threshold(dst, dst2, 128, 255, THRESH_BINARY_INV);//imshow("反二值化", dst2);// 自適應閾值:Mat dst3;threshold(dst, dst3, 0, 255, THRESH_BINARY | THRESH_OTSU);imshow("自適應閾值", dst3);
}

?

第五章 形態學

1. 連通性

????????連通性(Connectivity)是指判斷圖像中像素之間是否相互連接的一種規則,常用于二值圖像的分析(如查找連通區域、輪廓檢測、形態學操作等)。它定義了像素之間的鄰接關系,直接影響算法的結果。

1.1 鄰接關系

????????在圖像中,最?的單位是像素,每個像素周圍有8個鄰接像素,常?的鄰接關系有3種:4鄰接D鄰接,8鄰接和,分別如下圖所示:

說明一下:

  • 4鄰接:像素p(x,y)的4鄰域是:(x+1,y);(x-1,y);(x,y+1);(x,y-1),?N4(p)表示像素p的4鄰接
  • D鄰接:像素p(x,y)的D鄰域是:對?上的點 (x+1,y+1);(x+1,y-1);(x-1,y+1);(x-1,y-1),?ND(p)表示像素p的D鄰域 ?
  • 8鄰接:像素p(x,y)的8鄰域是: 4鄰域的點 + D鄰域的點,?N8(p)表示像素p的8鄰域

1.2 連通性

????????連通性決定了哪些相鄰像素被視為“連通的”(屬于同一區域),連通性是描述區域和邊界的重要概念,opencv的連通性規則有:

  • 4連通(4-Connectivity):只考慮像素的上下左右4個直接相鄰的像素(水平和垂直方向)。
  • 8連通(8-Connectivity):考慮像素的所有8個鄰居(包括對角線方向)。

????????M連通(Mixed Connectivity,混合連通性) 是介于 4連通(4-Connectivity) 和 8連通(8-Connectivity) 之間的一種折中策略,旨在解決8連通可能導致的過度連接問題(如斜向像素誤判為同一物體),同時避免4連通對某些合理連接的漏判。

具體規則如下: ?

  • 如果兩個像素是水平或垂直相鄰(4連通方向):直接視為連通。 ?
  • 如果兩個像素是斜向相鄰(對角線方向):只有當它們的至少一個共同4連通鄰居屬于同一區域時,才視為連通。

2. 形態學操作

????????形態學轉換是基于圖像形狀的?些簡單操作。它通常在?進制圖像上執?。腐蝕和膨脹是兩個基本的形態學運算符。然后它的變體形式還包括開運算,閉運算,禮帽?帽等

2.1 erode腐蝕

????????腐蝕和膨脹是最基本的形態學操作,腐蝕和膨脹都是針對??部分(?亮部分)??的。

腐蝕的作?是消除物體邊界點,可以消除?于結構元素的噪聲點

原理?

????????腐蝕就是??個結構元素掃描圖像中的每?個像素,?結構元素中的每?個像素與其覆蓋的像素做“與”操作,如果都為1,則該像素為1,否則為0。如下所示: ?

void cv::erode(InputArray  src,         // 輸入圖像OutputArray dst,         // 輸出圖像InputArray  kernel,      // 結構元素Point       anchor = Point(-1,-1),  // 錨點int         iterations = 1,  // 腐蝕次數int         borderType = BORDER_CONSTANT,const Scalar& borderValue = morphologyDefaultBorderValue()
);

案例?

void function1(Mat& src)
{// 創建一個5*5的矩形Mat mask = getStructuringElement(MORPH_RECT, Size(5, 5));Mat dst;// 將矩形和原圖做腐蝕操作erode(src, dst, mask);imshow("腐蝕圖像", dst);
}

?

2.2 dilate膨脹

?????????膨脹就是使圖像中?亮部分(白色區域)擴張,效果圖擁有?原圖更?的?亮區域(白色區域);膨脹是求局部最?值的操作,腐蝕是求局部最?值的操作。

????????膨脹的作?是將與物體接觸的所有背景點合并到物體中,可添補?標中的孔洞

原理?

??????????個結構元素掃描圖像中的每?個像素,?結構元素中的每?個像素與其覆蓋的像素做“或”操作,如果都為0,則該像素為0,否則為1。如下圖所示,結構A被結構B膨脹后

void function1(Mat& src)
{// 創建一個5*5的矩形Mat mask = getStructuringElement(MORPH_RECT, Size(5, 5));Mat dst;// 將矩形和原圖做膨脹操作dilate(src, dst, mask);imshow("膨脹圖像", dst);
}

2.3 開閉運算

????????開運算和閉運算是將腐蝕和膨脹按照?定的次序進?處理。 但這兩者不可逆的,即先開后閉并不能得到原來的圖像。

開運算是先腐蝕后膨脹

  • 作?是:分離物體,消除?區域。對“白色噪聲”有效,但會犧牲物體連通性(可能斷開細長部分)。
  • 特點:可以消除小噪聲或孤立點。平滑物體邊緣,斷開狹窄的連接。保留原始物體的大致形狀。
  • 應用場景:去除指紋圖像中的細小噪聲。

閉運算與開運算相反,是先膨脹后腐蝕

  • 作?是消除/“閉合”物體??的孔洞,對“黑色噪聲”(如孔洞、裂縫)的修復能力更強,因為先膨脹能直接填充它們。
  • 特點:可以填充小孔或裂縫,連接鄰近的物體,平滑物體輪廓。
  • 應用場景:填充醫學圖像中的血管斷裂。修復文檔中的筆畫缺失。
void cv::morphologyEx(InputArray  src,               // 輸入圖像(二值或灰度圖)OutputArray dst,              // 輸出圖像int         op,               // 形態學操作類型(如MORPH_OPEN)InputArray  kernel,           // 結構元素(核)Point       anchor = Point(-1,-1),  // 錨點(默認中心)int         iterations = 1,   // 操作次數int         borderType = BORDER_CONSTANT,  // 邊界處理方式const Scalar& borderValue = morphologyDefaultBorderValue()  // 邊界填充值
);
void test1(Mat& src)
{Mat dstOpen;Mat mask = getStructuringElement(MORPH_RECT, Size(5, 5));morphologyEx(src, dstOpen, MORPH_OPEN, mask);imshow("開運算", dstOpen);
}void test2(Mat& src)
{Mat dstClose;Mat mask = getStructuringElement(MORPH_RECT, Size(5, 5));morphologyEx(src, dstClose, MORPH_CLOSE, mask);imshow("閉運算", dstClose);
}

?

2.4 如何選擇開閉運算

2.5 禮帽黑帽運算

?禮帽運算

禮帽運算:原圖像與“運算“的結果圖之差

輸入:一張帶有白色噪聲的黑色背景圖像。 ?

輸出:僅保留噪聲和亮細節(原背景變全黑)

void function1(Mat& src)
{Mat gray;cvtColor(src, gray, COLOR_BGR2GRAY);imshow("灰度圖像", gray);Mat dstOpen;Mat mask = getStructuringElement(MORPH_RECT, Size(5, 5));morphologyEx(gray, dstOpen, MORPH_OPEN, mask);Mat dstTopHat;morphologyEx(gray, dstTopHat, MORPH_TOPHAT, dstOpen);imshow("禮帽運算", dstTopHat);
}

黑帽運算?

黑帽運算:為”運算“的結果圖與原圖像之差 ?

輸入:一張帶有黑色小孔的白色物體圖像

輸出:僅保留孔洞和暗細節(原物體變全白)

第六章 圖像噪聲和平滑?

1. 圖像噪聲

1.1?椒鹽噪聲

特征說明
表現形式隨機分布的白色(255)黑色(0)像素點
成因傳感器故障、傳輸錯誤、存儲介質損壞
影響破壞圖像局部細節,干擾邊緣檢測、目標識別等任務
典型場景老式掃描儀、受干擾的無線傳輸圖像、低質量JPEG壓縮

如下圖所示:

生成椒鹽噪聲

// 添加椒鹽噪聲
void addSaltPepperNoise(Mat& image, double noise_ratio = 0.05) {Mat dst;cvtColor(image, dst, COLOR_BGR2GRAY);int num_noise = static_cast<int>(dst.total() * noise_ratio);for (int i = 0; i < num_noise; i++) {int x = rand() % dst.cols;int y = rand() % dst.rows;// 隨機選擇鹽(白)或椒(黑)dst.at<uchar>(y, x) = (rand() % 2) ? 255 : 0;}imshow("椒鹽噪聲", dst);
}

?

1.2 高斯噪聲

?????????在圖像上呈現為 細密的顆粒狀干擾,類似電視雪花屏效果

高斯噪聲效果如圖:

給圖片生成高斯噪聲

void addGaussianNoise(Mat& image, double mean = 0, double stddev = 30) {Mat noise = Mat(image.size(), CV_32F);randn(noise, mean, stddev);  // 生成高斯噪聲矩陣if (image.type() == CV_8UC1) {  // 灰度圖Mat temp;image.convertTo(temp, CV_32F);temp += noise;temp.convertTo(image, CV_8U);}else if (image.type() == CV_8UC3) {  // 彩色圖Mat channels[3];split(image, channels);for (int i = 0; i < 3; i++) {channels[i].convertTo(channels[i], CV_32F);channels[i] += noise;channels[i].convertTo(channels[i], CV_8U);}merge(channels, 3, image);}imshow("高斯噪聲", image);
}

?

?說明一下:

  • 噪聲除了椒鹽噪聲和高斯噪聲外,還有泊松噪聲均勻噪聲量化噪聲等多種噪聲

2. 圖像平滑

?圖像平滑的作用:

  • 抑制噪聲:消除高斯噪聲、椒鹽噪聲等隨機干擾。
  • ?模糊細節:弱化邊緣或紋理,用于預處理(如邊緣檢測前的降噪)。 ?
  • 圖像增強:平滑光照不均或背景波動。

常見的圖像平滑的處理方式有:

  • 均值濾波
  • 高斯濾波
  • 中值濾波
  • 雙邊濾波
  • 非局部均值去噪等 ?

2.1 均值濾波

????????均值濾波: 可以抑制高斯噪聲等噪聲,但是副作用也比較明顯,就是邊緣和細節也會模糊

void cv::blur(InputArray ?src, ? ? ? ? ? ? ?// 輸入圖像(支持單通道或多通道)OutputArray dst, ? ? ? ? ? ? ?// 輸出圖像(與src尺寸和類型相同)Size ? ? ? ?ksize, ? ? ? ? ? ?// 濾波核大小(如Size(3,3))Point ? ? ? anchor = Point(-1,-1), ?// 錨點(默認核中心)int ? ? ? ? borderType = BORDER_DEFAULT ?// 邊界填充方式
);?
void test(Mat& src)
{Mat dst;blur(src, dst, Size(3, 3));imshow("均值濾波", dst);
}

?

?2.2 高斯濾波

高斯濾波的用途

  • 圖像降噪:消除圖像中的高頻噪聲
  • 預處理:在邊緣檢測等操作前減少圖像中的細節
  • 模糊效果:創建藝術效果或保護隱私時模糊部分圖像
  • 尺度空間表示:在特征檢測(如SIFT)中構建圖像金字塔

高斯濾波的大概實現原理:

  • 準備濾鏡模板:先制作一個高斯核(比如5×5的網格,中心數字大,邊緣數字小)

  • 滑動窗口操作:把這個小網格放在圖像的每一個像素點上,就像用這個"加權放大鏡"看圖像的每一個小區域

  • 計算新像素值:把網格覆蓋的像素值乘以對應的網格中的數字(權重)把所有乘積加起來,然后除以權重的總和,這個結果就是當前位置新的像素值

  • 整體效果:因為中心權重高,周圍權重低,所以相當于"保留大致模樣,但把細節磨平"就像近視眼摘掉眼鏡看東西 大致形狀還在,但邊緣變柔和了

void GaussianBlur(InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY = 0, int borderType = BORDER_DEFAULT
)

說明一下:?

  • ?ksize選擇
    • 3×3:輕微模糊
    • 5×5:中等模糊
    • 7×7及以上:強模糊效果
    • 必須為正奇數(如3,5,7...)
  • X方向標準差:控制水平方向的模糊程度
  • Y方向標準差:控制垂直方向的模糊程度(通常設為與X相同)
  • 當sigma=0時,OpenCV會根據ksize自動計算sigma,sigma越大,模糊效果越明顯
    • 典型值:0.5-2.0(輕度模糊),2.0-5.0(明顯模糊)
    • 根據ksize和sigma生成二維高斯核
void test(Mat& src)
{Mat dst = Mat::zeros(src.size(), src.type());GaussianBlur(src, dst, Size(3, 3), 0.0);imshow("3*3的高斯模糊", dst);Mat dst5 = Mat::zeros(src.size(), src.type());GaussianBlur(src, dst5, Size(5, 5), 0.0);imshow("5*5的高斯模糊", dst5);Mat dst7 = Mat::zeros(src.size(), src.type());GaussianBlur(src, dst7, Size(7, 7), 0.0);imshow("7*7的高斯模糊", dst7);
}

?

2.3?中值濾波

中值濾波的特點:

  • 有效去除椒鹽噪聲(圖像上的黑白噪點)

  • 保護邊緣信息(不像均值濾波會使邊緣模糊)

  • 計算相對耗時(因為需要排序操作)

中值濾波的實現原理:

  • 定義一個濾波窗口(通常是3×3、5×5等奇數尺寸的正方形)

  • 窗口遍歷圖像,在每個位置:收集窗口內所有像素值,將這些值按大小排序,取排序后的中間值作為當前像素的新值

  • 邊界處理:通常通過復制邊緣像素或鏡像等方式處理

void medianBlur(InputArray src, OutputArray dst, int ksize);?
void test(Mat& src)
{Mat dst;medianBlur(src, dst, 3);imshow("3*3的中值濾波", dst);Mat dst5;medianBlur(src, dst5, 5);imshow("5*5的中值濾波", dst5);
}

?

?2.4?圖像平滑總結

方法優點缺點適用場景
均值濾波計算快邊緣模糊實時性要求高的簡單去噪
高斯濾波邊緣保留較好對椒鹽噪聲無效通用降噪(如高斯噪聲)
中值濾波消除脈沖噪聲對高斯噪聲效果一般椒鹽噪聲、醫學影像
雙邊濾波邊緣清晰計算慢人像美化、細節保留
非局部均值去噪效果最佳極耗資源高精度靜態圖像處理

?3. 直方圖

????????統計每個區間內像素的數量并繪制成柱狀圖。其中橫軸:像素值(如0-255)縱軸:該像素值出現的頻率(像素數量)

直方圖的作用:

  • 圖像分析:一眼看出圖像是偏亮(右側高)、偏暗(左側高)還是對比度低(集中在中間)
  • 圖像增強:通過直方圖均衡化改善圖像對比度
  • 目標檢測:用于顏色匹配、背景建模等高級應用

術語說明:

  • Bin(區間):將0-255的強度范圍劃分的區間(如每10個強度值一個bin)

  • 頻數(bins):每個bin中像素的數量

  • 范圍(range):通常為0-255(8位圖像)

void calcHist(const Mat* images, int nimages, const int* channels, InputArray mask, OutputArray hist, int dims, const int* histSize, const float** ranges, bool uniform = true, bool accumulate = false
);

參數說明:

  • images:輸入圖像數組(指針),可以是單幅或多幅圖像
  • nimages:輸入圖像的數量
  • channels:需要計算直方圖的通道列表(數組)
  • mask:可選的操作掩碼,指定要計算直方圖的圖像區域
  • hist:輸出的直方圖
  • dims:直方圖的維度(通常為1)
  • histSize:每個維度上直方圖的大小(bin的數量)
  • ranges:每個維度上像素值的范圍
  • uniform:直方圖是否均勻(默認true)
  • accumulate:是否累積計算結果(默認false)

3.1 灰度圖像?

void test(Mat& src)
{// 將圖片轉為灰度圖像Mat gray;cvtColor(src, gray, COLOR_BGR2GRAY);imshow("灰度圖像", gray);// 計算直方圖Mat hist;int histSize = 256;float range[] = { 0,256 };const float* histRange = { range };calcHist(&gray, 1, 0, Mat(), hist, 1, &histSize, &histRange);// 繪制直方圖// 定義圖像的高和寬int w = 512, h = 400;// 定義每一個bin的寬度  cvRound()做四舍五入的int bin_w = cvRound((double)w / histSize);Mat dst = Mat(Size(w, h), CV_8UC3, Scalar(0, 0, 0));// 歸一化normalize(hist, hist, 0, dst.rows, NORM_MINMAX, -1, Mat());// 繪制for (int i = 1; i < histSize; i++) {line(dst,Point(bin_w * (i - 1), h - cvRound(hist.at<float>(i - 1))),Point(bin_w * (i), h - cvRound(hist.at<float>(i))),Scalar(255, 0, 0), 2, 8, 0);}// 顯示imshow("灰度直方圖", dst);
}

?

?補充一下:

  • 繪制直方圖的時候一定要做歸一化,為了解決數值范圍不匹配的問題,同時也為了可視化的友好性
  • 除了這種方法:ploat::plotHist()繪制-了解

?3.2 彩色直方圖

????????彩色圖像的直方圖需要分別計算每個顏色通道(通常是B、G、R)的像素分布。與灰度直方圖不同,彩色直方圖可以:單獨顯示各通道分布;展示通道間的顏色關系;通過三維直方圖表現顏色空間分布

#include <vector>
void test(Mat& src)
{//1 分離彩色圖像的通道vector<Mat> channels;split(src, channels);//2 設置直方圖參數int histSize = 256;float range[] = { 0,256 };const float* histRange = { range };//3 分別計算各通道的直方圖Mat b_hist, g_hist, r_hist;calcHist(&channels[0], 1, 0, Mat(), b_hist, 1, &histSize, &histRange);calcHist(&channels[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange);calcHist(&channels[2], 1, 0, Mat(), r_hist, 1, &histSize, &histRange);//4 繪制直方圖int w = 512, h = 400;int bin_w = cvRound((double)w / histSize);Mat dst = Mat(Size(w, h), CV_8UC3, Scalar(0, 0, 0));//5 將三個通道都進行歸一化normalize(b_hist, b_hist, 0, dst.rows, NORM_MINMAX);normalize(g_hist, g_hist, 0, dst.rows, NORM_MINMAX);normalize(r_hist, r_hist, 0, dst.rows, NORM_MINMAX);//6 繪制各通道直方圖for (int i = 1; i < histSize; i++) {// 藍色通道line(dst,Point(bin_w * (i - 1), h - cvRound(b_hist.at<float>(i - 1))),Point(bin_w * i, h - cvRound(b_hist.at<float>(i))),Scalar(255, 0, 0), 2);// 綠色通道line(dst,Point(bin_w * (i - 1), h - cvRound(g_hist.at<float>(i - 1))),Point(bin_w * i, h - cvRound(g_hist.at<float>(i))),Scalar(0, 255, 0), 2);// 紅色通道line(dst,Point(bin_w * (i - 1), h - cvRound(r_hist.at<float>(i - 1))),Point(bin_w * i, h - cvRound(r_hist.at<float>(i))),Scalar(0, 0, 255), 2);}imshow("彩色直方圖", dst);
}

?

3.4 掩膜的應用

????????在 OpenCV 中,掩膜(Mask)是一種用于限定圖像處理區域的二進制矩陣,它通過選擇性地屏蔽或保留像素來控制操作的范圍

?????????掩膜的作用: 提取感興趣區域:?預先制作的感興趣區掩模與待處理圖像進?”與“操作,得到感興趣區圖像,感興趣區內圖像值保持不變,?區外圖像值都為0

void fun3(Mat& src)
{//1 創建掩膜Mat mask = Mat::zeros(src.size(), CV_8UC1);rectangle(mask, Rect(35, 14, 196, 183), Scalar(255), FILLED); // (x,y,width,height)//2. 應用掩膜Mat masked_img;bitwise_and(src, src, masked_img, mask);imshow("掩膜", masked_img);//3 只統計掩膜部分的直方圖//1 分離彩色圖像的通道vector<Mat> channels;split(src, channels);//2 設置直方圖參數int histSize = 256;float range[] = { 0,256 };const float* histRange = { range };//3 分別計算各通道的直方圖-只計算指定區域Mat b_hist, g_hist, r_hist;calcHist(&channels[0], 1, 0, mask, b_hist, 1, &histSize, &histRange);calcHist(&channels[1], 1, 0, mask, g_hist, 1, &histSize, &histRange);calcHist(&channels[2], 1, 0, mask, r_hist, 1, &histSize, &histRange);//4 繪制直方圖int w = 512, h = 400;int bin_w = cvRound((double)w / histSize);Mat dst = Mat(Size(w, h), CV_8UC3, Scalar(0, 0, 0));//5 將三個通道都進行歸一化normalize(b_hist, b_hist, 0, dst.rows, NORM_MINMAX);normalize(g_hist, g_hist, 0, dst.rows, NORM_MINMAX);normalize(r_hist, r_hist, 0, dst.rows, NORM_MINMAX);//6 繪制各通道直方圖for (int i = 1; i < histSize; i++) {// 藍色通道line(dst,Point(bin_w * (i - 1), h - cvRound(b_hist.at<float>(i - 1))),Point(bin_w * i, h - cvRound(b_hist.at<float>(i))),Scalar(255, 0, 0), 2);// 綠色通道line(dst,Point(bin_w * (i - 1), h - cvRound(g_hist.at<float>(i - 1))),Point(bin_w * i, h - cvRound(g_hist.at<float>(i))),Scalar(0, 255, 0), 2);// 紅色通道line(dst,Point(bin_w * (i - 1), h - cvRound(r_hist.at<float>(i - 1))),Point(bin_w * i, h - cvRound(r_hist.at<float>(i))),Scalar(0, 0, 255), 2);}imshow("掩膜直方圖", dst);
}

?

3.4 直方圖均衡化

????????直方圖均衡化(Histogram Equalization)是一種用于增強圖像對比度的圖像處理技術,通過重新分配像素強度值來擴展圖像的動態范圍。

?直方圖均衡化的應用場景:

場景作用
醫學影像增強病灶區域的可見性
衛星圖像改善地表特征識別
人臉識別提升光照不變性
老舊照片修復恢復褪色區域的細節

c++ opencv中可以使用equalizeHist()實現直方圖均衡化 ?

處理灰度圖像: ?

void test(Mat& src)
{Mat gray;cvtColor(src, gray, COLOR_BGR2GRAY);imshow("灰度圖像", gray);Mat dst;equalizeHist(gray, dst);imshow("直方圖均衡化", dst);
}

?

處理彩色圖像 ?

Mat color_img = imread("color.jpg");
vector<Mat> channels;
split(color_img, channels);// 僅對亮度通道處理(YCrCb/YUV色彩空間)
Mat ycrcb;
cvtColor(color_img, ycrcb, COLOR_BGR2YCrCb);
split(ycrcb, channels);
equalizeHist(channels[0], channels[0]); // 處理Y通道
merge(channels, ycrcb);
cvtColor(ycrcb, dst, COLOR_YCrCb2BGR);

?3.5 自適應的直方圖均衡化????????

????????傳統的直方圖均衡化方法存在一些局限性,特別是在處理局部對比度較低的圖像時表現不佳。因此我們需要使用自適應直方圖均衡化

灰度圖像做一個自適應直方圖均衡化

void test(Mat& src)
{Mat image;cvtColor(src, image, COLOR_BGR2GRAY);Ptr<CLAHE> clahe = createCLAHE();Mat dst;clahe->apply(image, dst);imshow("自適應直方圖均衡化", dst);
}

?

3.6 直方圖比較

????????直方圖比較(Histogram Comparison)是一種用于衡量兩幅圖像的直方圖相似性的方法

?????????在OpenCV中,提供了專門的函數 cv::compareHist() 來實現直方圖比較。該函數支持多種比較方法,每種方法都基于不同的數學公式來計算兩個直方圖之間的相似性或差異

cv::compareHist() 支持以下幾種比較方法:

  • ?相關性 (cv::HISTCMP_CORREL)衡量兩個直方圖的線性相關性。值范圍:[-1, 1],值越大表示越相似。
  • 卡方距離 (cv::HISTCMP_CHISQR)衡量兩個直方圖的卡方距離。值范圍:[0, ∞),值越小表示越相似。
  • 交集 (cv::HISTCMP_INTERSECT)計算兩個直方圖的交集面積。值范圍:[0, 1],值越大表示越相似。
  • 巴氏距離 (cv::HISTCMP_BHATTACHARYYA)衡量兩個直方圖的概率分布之間的距離。值范圍:[0, 1],值越小表示越相似。
void test(Mat& src1,Mat& src2)
{//1 設置直方圖參數int histSize = 256;float range[] = { 0,256 };const float* histRange = { range };//2 計算直方圖Mat hist1, hist2;calcHist(&src1, 1, 0, Mat(), hist1, 1, &histSize, &histRange);calcHist(&src2, 1, 0, Mat(), hist2, 1, &histSize, &histRange);//3 歸一化直方圖normalize(hist1, hist1);normalize(hist2, hist2);//4 直方圖比較double c1 = compareHist(hist1, hist2, HISTCMP_CORREL);double c2 = compareHist(hist1, hist2, HISTCMP_CHISQR);double c3 = compareHist(hist1, hist2, HISTCMP_INTERSECT);double c4 = compareHist(hist1, hist2, HISTCMP_BHATTACHARYYA);cout << "Correlation: " << c1 << " (越大越相似)" << endl;cout << "Chi-Square: " << c2 << " (越小越相似)" << endl;cout << "Intersection: " << c3 << " (越大越相似)" << endl;cout << "Bhattacharyya: " << c4 << " (越小越相似)" << endl;
}

4. 邊緣檢測

????????邊緣檢測(Edge Detection)是圖像處理和計算機視覺中的一項基本任務,用于識別圖像中亮度、顏色或紋理發生顯著變化的區域

4.1?Sobel 算子

????????Sobel算子是一種離散微分算子,用于圖像處理中的邊緣檢測

void Sobel(InputArray src,         // 輸入圖像OutputArray dst,        // 輸出圖像int ddepth,             // 輸出圖像深度int dx,                 // x方向導數階數int dy,                 // y方向導數階數int ksize = 3,          // 核大小double scale = 1,       // 縮放因子double delta = 0,       // 偏移量int borderType = BORDER_DEFAULT  // 邊界處理方式
)
void test(Mat& src)
{//1 將圖像轉換為灰度圖像Mat gray;cvtColor(src, gray, COLOR_BGR2GRAY);//2 計算x和y方向的梯度Mat grad_x, grad_y;Sobel(gray, grad_x, CV_16S, 1, 0, 3);Sobel(gray, grad_y, CV_16S, 1, 0, 3);//3 將圖像轉為8位無符號整型// 數據類型轉換(16S → 8U) 取絕對值(處理負梯度值) 比例縮放(自適應歸一化)Mat abs_grad_x, abs_grad_y;convertScaleAbs(grad_x, abs_grad_x);convertScaleAbs(grad_y, abs_grad_y);//4 合并梯度Mat dst;addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, dst);//5 展示結果imshow("sobe邊緣檢測", dst);
}

?

如果將上述ksize的值改為-1,就是使用Scharr進行邊緣檢測,它的精度更高 ?

  • Sobel(gray, grad_x, CV_16S,1, ?0, -1);
  • Sobel(gray, grad_y, CV_16S, 1, 0, -1);

4.2?Laplacian 算子

????????Laplacian 算子是一種基于二階導數的邊緣檢測算子,用于檢測圖像中亮度或顏色變化劇烈的區域(即邊緣)

????????在opencv中,我們可以通過Laplacian()實現Laplacian邊緣檢測

void Laplacian(InputArray src,         // 輸入圖像OutputArray dst,        // 輸出圖像int ddepth,             // 輸出圖像深度(推薦CV_16S)int ksize = 1,          // 核大小(1/3/5/7)double scale = 1,       // 縮放因子double delta = 0,       // 偏移量int borderType = BORDER_DEFAULT  // 邊界處理方式
)

不帶高斯模糊Laplacian邊緣檢測

void test(Mat& src)
{//1 轉為灰度圖像Mat gray;cvtColor(src,gray,COLOR_BGR2GRAY);//2 使用Laplacian邊緣檢測Mat dst;Laplacian(src, dst, CV_16S,3);//3 轉換絕對值-將16位轉為8位無符號整數Mat abs_dst;convertScaleAbs(dst, abs_dst);//4 展示圖像imshow("Laplacian邊緣檢測",abs_dst);
}

帶高斯模糊的Laplacian邊緣檢測

void test(Mat& src)
{//1 轉為灰度圖像Mat gray;cvtColor(src, gray, COLOR_BGR2GRAY);// 高斯模糊-高斯濾波Mat blurDst;GaussianBlur(gray, blurDst, Size(3, 3), 0);//2 使用Laplacian邊緣檢測Mat dst;Laplacian(blurDst, dst, CV_16S, 3);//3 轉換絕對值Mat abs_dst;convertScaleAbs(dst, abs_dst);//4 展示圖像imshow("Laplacian邊緣檢測", abs_dst);
}

?

說明一下:?

  • ?Laplacian算子特別適合需要檢測細微特征各向同性邊緣的場景,但通常需要配合高斯模糊等預處理來獲得更好的效果

4.3 Canny 邊緣檢測

????????Canny 邊緣檢測是一種多階段的邊緣檢測算法,由 John F. Canny 于 1986 年提出。它被認為是最優的邊緣檢測方法之一,能夠在保證高精度的同時減少噪聲的影響。Canny 算法的目標是找到圖像中亮度變化劇烈的區域,并生成清晰、連續且單一像素寬的邊緣

void Canny(InputArray image,                // 輸入圖像OutputArray edges,               // 輸出邊緣圖double threshold1,               // 低閾值double threshold2,               // 高閾值int apertureSize = 3,            // Sobel核大小(3/5/7)bool L2gradient = false          // 梯度計算方式
)

?參數說明:

  • threshold1:低閾值,建議取高閾值的1/2~1/3
  • threshold2:高閾值,通常設為低閾值的2~3倍
  • apertureSize:Sobel核尺寸(3/5/7)
  • L2gradient
    • false:使用L1范數(|Gx|+|Gy|)
    • true:使用L2范數(√(Gx2+Gy2)),更精確但計算量更大

?基本使用

void test(Mat& src)
{//1 轉為灰度圖像Mat gray;cvtColor(src, gray, COLOR_BGR2GRAY);//2 使用Canny()做邊緣檢測Mat dst;Canny(gray, dst, 50, 150);//3 展示imshow("canny邊緣檢測", dst);
}

?

自定義閾值 + 高斯模糊 ?

// 計算圖像中值
double calculateMedian(cv::Mat image) {// 將圖像數據轉換為一維數組std::vector<uchar> pixelValues;pixelValues.assign((uchar*)image.data, (uchar*)image.data + image.total());// 排序像素值std::sort(pixelValues.begin(), pixelValues.end());// 計算中值size_t size = pixelValues.size();if (size % 2 == 0) {return (pixelValues[size / 2 - 1] + pixelValues[size / 2]) / 2.0;}else {return pixelValues[size / 2];}
}
void test(Mat& src)
{//1 轉為灰度圖像Mat gray;cvtColor(src, gray, COLOR_BGR2GRAY);//2 高斯模糊Mat blur;GaussianBlur(gray, blur, Size(3, 3), 0.0);//3 自定義閾值// 計算圖像的中值double middle = calculateMedian(blur);double lower = max(0.0, (1 - 0.3) * middle);	// 0.7*中值 = 最低閾值double upper = min(255.0, (1 + 0.3) * middle);	// 1.3*中值 = 最高閾值//4 Canny()邊緣檢測Mat dst;Canny(blur,dst,lower,upper);//5 展示imshow("canny邊緣檢測",dst);}

?

?5. 邊緣檢測算法比較

第七章?圖像進階處理

1. 模板匹配

????????模板匹配(Template Matching)是一種在大圖像中查找小模板圖像位置的技術

應用場景:

  • a. 目標檢測(如尋找圖標、數字、物體) ?
  • b. 工業檢測(如零件定位) ?
  • c. 視頻監控(運動物體跟蹤)

原理?

?模板匹配的核心是通過滑動窗口的方式,在目標圖像中逐像素地比較模板圖像局部區域相似度。具體實現步驟如下:

1. 輸入

  • 目標圖像:需要搜索的大圖。

  • 模板圖像:需要查找的小圖

2.?相似度計算

  • 模板圖像在目標圖像上滑動,逐個位置計算模板與目標圖像局部區域的相似度。

  • 常見的相似度度量方法包括:

    • 平方差匹配 (TM_SQDIFF):計算模板與目標區域的平方差,值越小表示匹配度越高。

    • 歸一化平方差匹配 (TM_SQDIFF_NORMED):對平方差進行歸一化處理。

    • 相關匹配 (TM_CCORR):計算模板與目標區域的相關值,值越大表示匹配度越高。

    • 歸一化相關匹配 (TM_CCORR_NORMED):對相關值進行歸一化處理。

    • 相關系數匹配 (TM_CCOEFF):計算模板與目標區域的相關系數,值越大表示匹配度越高。

    • 歸一化相關系數匹配 (TM_CCOEFF_NORMED):對相關系數進行歸一化處理。

?

3.輸出:

  • 返回一個結果矩陣,矩陣中每個元素表示模板在對應位置的匹配度。
  • 通過尋找結果矩陣中的最大值或最小值(取決于所選方法),可以確定模板的最佳匹配位置。

?1.1?matchTemplate模板匹配

void matchTemplate(InputArray image,         // 目標圖像(搜索區域)InputArray templ,         // 模板圖像(要匹配的小圖像)OutputArray result,       // 匹配結果矩陣(存放相似度)int method,               // 匹配方法/相關度計算方法(如 TM_CCOEFF_NORMED)InputArray mask = noArray() // 可選掩碼(僅對某些方法有效)
);

result:

  • 尺寸計算:
  • 寬度:image.cols - templ.cols + 1
  • 高度:image.rows - templ.rows + 1
  • 數據類型:32位浮點型(CV_32FC1)

mask:可選掩碼(僅對 TM_SQDIFF 和 TM_CCORR_NORMED 有效),需與 templ 同尺寸。

OpenCV 提供 6 種匹配方法: ?

展示案例?

void test(Mat& targetImage,Mat& templateImage)
{//1 創建結果矩陣/*寬度:image.cols - templ.cols + 1高度:image.rows - templ.rows + 1數據類型:32位浮點型(CV_32FC1)*/int cols = targetImage.cols - templateImage.cols + 1;int rows = targetImage.rows - templateImage.rows + 1;Mat result = Mat::zeros(Size(cols, rows), CV_32FC1);//2 使用模板匹配matchTemplate(targetImage, templateImage, result, TM_CCOEFF_NORMED);//3 獲取最佳匹配位置double minVal, maxVal;Point minPoint, maxPoint;// 在矩陣中查找最小值和最大值及其對應的位置minMaxLoc(result, &minVal, &maxVal, &minPoint, &maxPoint);//4 在圖像上繪制矩形,標記結果rectangle(targetImage,maxPoint,Point(maxPoint.x + templateImage.cols, maxPoint.y + templateImage.rows),Scalar(0, 255, 0),2);//5 顯示結果imshow("模板圖像", templateImage);imshow("模板匹配", targetImage);
}

?

2.?輪廓檢測

?????????輪廓檢測(Contour Detection)是計算機視覺中用于提取圖像中物體邊界的技術。它通過分析像素的連通性,找到物體的連續邊緣點集合

2.1?findContours()提取輪廓? &&?drawContours()繪制輪廓

void findContours(InputArray image,                // 輸入二值圖像(8-bit 單通道)OutputArrayOfArrays contours,     // 輸出的輪廓點集OutputArray hierarchy,            // 輪廓的層級關系(可選)int mode,                        // 輪廓檢索模式int method,                      // 輪廓近似方法Point offset = Point()            // 輪廓點的偏移量(可選)
);

?contours:輸出的輪廓集合,每個輪廓存儲為 vector<Point>,所以輪廓是vector<vector<Point>>
hieraychy:

  • 每個輪廓對應一個 cv::Vec4i,包含以下四個值:
  • hierarchy[i][0]:當前輪廓的下一個同級輪廓索引(如果不存在,則為 -1)。
  • hierarchy[i][1]:當前輪廓的上一個同級輪廓索引(如果不存在,則為 -1)。
  • hierarchy[i][2]:當前輪廓的第一個子輪廓索引(如果不存在,則為 -1)。
  • hierarchy[i][3]:當前輪廓的父輪廓索引(如果不存在,則為 -1)

mode:

  • cv::RETR_EXTERNAL:僅提取最外層輪廓。
  • cv::RETR_LIST:提取所有輪廓,但不建立層次關系。
  • cv::RETR_CCOMP:提取所有輪廓,并將它們分為兩層(外部輪廓和內部輪廓)。
  • cv::RETR_TREE:提取所有輪廓,并建立完整的層次關系。

method:

  • cv::CHAIN_APPROX_NONE:保存所有輪廓點,不做任何壓縮。
  • cv::CHAIN_APPROX_SIMPLE:壓縮水平、垂直和對角線段,僅保留端點。
  • cv::CHAIN_APPROX_TC89_L1 和 cv::CHAIN_APPROX_TC89_KCOS:使用 Teh-Chin 鏈近似算法。
  • offset:輪廓點的偏移量(默認 Point(0,0))
void drawContours(InputOutputArray image,            // 目標圖像(繪制在此圖上)InputArrayOfArrays contours,       // 輪廓點集(來自 `findContours()`)int contourIdx,                    // 要繪制的輪廓索引(-1 表示所有)const Scalar& color,               // 輪廓顏色int thickness = 1,                 // 線寬(-1 表示填充輪廓)int lineType = LINE_8,             // 線型(如 `LINE_AA` 抗鋸齒)InputArray hierarchy = noArray(),  // 層級關系(可選)int maxLevel = INT_MAX,            // 最大繪制層級(默認全部)Point offset = Point()             // 輪廓點偏移(可選)
);

?演示案例

????????輸入圖像必須二值化 ,對小物體,可先使用形態學操作(如膨脹/腐蝕)去除噪聲。 對復雜場景,建議先邊緣檢測(如Canny)再輪廓查找

3. 圖像分割

????????圖像分割是計算機視覺中的一項基本任務,其目標是將圖像劃分為多個區域或部分,使得每個區域具有某種特定的屬性(如顏色、紋理、形狀等),或者屬于同一個對象。簡單來說,圖像分割就是將圖像中的像素分組為有意義的區域。將圖像劃分為語義或實例級區域,每個像素屬于唯一類別(如“人”“車”“天空”) ?

3.1 閾值分割

??? ??? ?閾值分割是一種基于像素灰度值顏色值的圖像分割方法。它的基本思想是通過設定一個或多個閾值,將圖像中的像素分為不同的類別(例如前景和背景)。每個像素根據其灰度值或顏色值與閾值的關系,被分配到某個類別

?全局閾值法&固定閾值分割

double cv::threshold(InputArray src,      // 輸入圖像,必須是單通道灰度圖像OutputArray dst,     // 輸出圖像,與輸入圖像大小和類型相同double thresh,       // 閾值double maxval,       // 當使用 THRESH_BINARY 或 THRESH_BINARY_INV 時的最大值int type             // 閾值類型
);

?type: 閾值處理的類型,可以是以下幾種之一:

  • THRESH_BINARY: 二值化閾值處理。如果像素值大于閾值,則設為 maxval;否則設為0。
  • THRESH_BINARY_INV: 反二值化閾值處理。如果像素值大于閾值,則設為0;否則設為 maxval。
  • THRESH_TRUNC: 截斷閾值處理。如果像素值大于閾值,則設為閾值;否則保持原樣。
  • THRESH_TOZERO:零閾值處理 將小于閾值的像素設置為0。如果像素值大于閾值,則保持不變;否則設為0。

THRESH_TOZERO_INV: 反向零閾值處理。如果像素值大于閾值,則設為0;否則保持不變。
特殊類型:
THRESH_OTSU: 使用 Otsu 算法自動選擇最佳閾值,并結合 THRESH_BINARY 或THRESH_BINARY_INV 使用。
THRESH_TRIANGLE: 使用三角算法自動選擇最佳閾值,并結合 THRESH_BINARY 或 THRESH_BINARY_INV 使用。
返回值:返回的是計算得到的最佳閾值(僅當使用 THRESH_OTSU 或 THRESH_TRIANGLE 時有效),對于其他類型的閾值處理,返回的值等于 thresh。

演示案例

void test(Mat& src)
{//1 將圖像轉為灰度圖像Mat gray;cvtColor(src, gray, COLOR_BGR2GRAY);//2 固定閾值處理  thresholdvector<Mat> dsts;Mat dst1,dst2,dst3,dst4,dst5;// 二值化閾值threshold(gray, dst1, 127, 255, THRESH_BINARY);dsts.push_back(dst1);// 反二值化閾值threshold(gray, dst2, 127, 255, THRESH_BINARY_INV);dsts.push_back(dst2);// 截斷閾值處理threshold(gray, dst3, 127, 255, THRESH_TRUNC);dsts.push_back(dst3);// 零閾值處理threshold(gray, dst4, 127, 255, THRESH_TOZERO);dsts.push_back(dst4);// 反向零閾值處理threshold(gray, dst5, 127, 255, THRESH_TOZERO_INV);dsts.push_back(dst5);// 展示string titles[5] = {"二值化閾值","反二值化閾值","截斷閾值","零閾值","反向零閾值"};for (int i = 0;i < 5;i++){imshow(titles[i], dsts.at(i));}
}

?

自動閾值分割 - Otsu算法 ?

?????????Otsu算法(大津算法)是一種自動確定圖像二值化閾值的方法

void test(Mat& src)
{Mat gray;cvtColor(src,gray,COLOR_BGR2GRAY);Mat dst;//THRESH_BINARY | THRESH_OTSU做位或(or)操作 既要執行二值化,又要用Otsu算法自動確定閾值threshold(gray, dst, 0, 255, THRESH_BINARY | THRESH_OTSU);imshow("otsu算法", dst);
}

?

自適應閾值分割 ?

??????????適應閾值分割也叫做局部閾值化,它是根據像素的鄰域塊的像素值分布來確定該像素位置上的閾值

void adaptiveThreshold(InputArray src,     // 輸入圖像(必須為8位單通道)OutputArray dst,    // 輸出圖像double maxValue,    // 滿足條件的像素賦予的值(通常255)int adaptiveMethod, // 自適應方法:ADAPTIVE_THRESH_MEAN_C 或 ADAPTIVE_THRESH_GAUSSIAN_Cint thresholdType,  // 閾值類型:THRESH_BINARY 或 THRESH_BINARY_INVint blockSize,      // 鄰域大小(奇數,如3,5,7,...)double C           // 從均值/加權和中減去的常數
);

blockSize

  • 定義用于計算局部閾值的鄰域大小,必須是奇數(如 3、5、7 等)。
  • 較大的鄰域會平滑更多的噪聲,但可能會丟失細節。

C:從計算出的局部閾值中減去的常數值。如果 C 太大,可能會導致分割效果過于保守;如果 C 太小,可能會引入噪聲。
cv::ADAPTIVE_THRESH_MEAN_C:使用鄰域內像素的平均值作為局部閾值。
cv::ADAPTIVE_THRESH_GAUSSIAN_C:使用鄰域內像素的高斯加權平均值作為局部閾值。
cv::THRESH_BINARY:如果像素值大于局部閾值,則設為最大值(如 255);否則設為 0。

?演示案列

void test(Mat& src)
{//1 轉為灰度圖像Mat gray;cvtColor(src, gray, COLOR_BGR2GRAY);Mat dst1, dst2;//2 使用均值法做自適應閾值分割adaptiveThreshold(gray, dst1, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 5, 2);//3 使用高斯法做自適應閾值分割adaptiveThreshold(gray, dst2, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 5, 2);//4 展示imshow("均值法", dst1);imshow("高斯法", dst2);
}

?

?3.2 邊緣分割

????????邊緣分割是一種圖像處理技術,其核心思想是通過檢測圖像中的邊緣(即灰度值發生顯著變化的區域)來實現對圖像的分割

邊緣分割的實現步驟:

  • 邊緣檢測:使用邊緣檢測算子(如 Sobel、Canny 等)提取圖像中的邊緣信息。 ?
  • 邊緣連接:由于噪聲或其他因素,檢測到的邊緣可能不連續,需要通過一定的算法(如霍夫變換、形態學操作等)將斷開的邊緣連接起來。 ?
  • 區域劃分:根據檢測到的邊緣,將圖像劃分為不同的區域。每個區域內部具有相似的特征,而區域之間存在明顯的差異。 ?
  • 后處理:對分割結果進行優化,例如去除小的噪聲區域或填充空洞。

以下是使用Canny算法實現邊緣分割:

void test(Mat& src)
{//1 轉為灰度圖像Mat gray;cvtColor(src, gray, COLOR_BGR2GRAY);//2 高斯模糊Mat blur;GaussianBlur(gray, blur, Size(3, 3), 1.5);//3 canny()邊緣檢測Mat edges;Canny(blur, edges, 50, 150);imshow("canny邊緣檢測", edges);//4 用邊緣檢測結果做掩碼,分割圖像Mat dst = Mat::zeros(src.size(), src.type());src.copyTo(dst, edges);	// 只保留邊緣部分imshow("邊緣分割", dst);
}

?

?3.3?分水嶺算法

????????分水嶺算法是一種基于形態學區域生長的圖像分割方法,它把圖像看作地形表面,將灰度值視為海拔高度,通過模擬“洪水”從低洼處逐漸淹沒整個地形的過程,找到不同區域之間的邊界。是通過模擬"水漫過程"來實現圖像分割

?

distanceTransform()函數用于計算二值圖像中每個像素到最近的零像素(背景)的距離,并生成距離圖 ?

void cv::distanceTransform(InputArray src,        // 輸入圖像(8位單通道二值圖像,非零像素視為前景)OutputArray dst,       // 輸出距離圖(32位浮點型或8位無符號整型)OutputArray labels,    // 可選:輸出標簽圖(用于DIST_LABEL_*類型)int distanceType,      // 距離類型(DIST_L1、DIST_L2、DIST_C等)int maskSize,          // 掩模大小(3、5或DIST_MASK_PRECISE)int labelType = DIST_LABEL_CCOMP // 標簽類型(僅當需要labels時使用)
);

connectedComponents()函數用于對二值圖像中的連通區域(Connected Components)進行標記和統計

int cv::connectedComponents(InputArray image,          // 輸入圖像(8位單通道二值圖像)OutputArray labels,        // 輸出標簽圖(每個像素的連通區域ID)int connectivity = 8,      // 連通性(4或8鄰域)int ltype = CV_32S         // 標簽圖數據類型(通常為CV_32S)
);
labels(輸出標簽圖)
存儲每個像素所屬的連通區域ID(從0開始)。
標簽規則:
0:背景區域。
1, 2, 3,...:不同的前景連通區域。
connectivity:
選擇建議:
需嚴格區分水平和垂直連接時用4(如文本字符分割)。
需更寬松的連通性時用8(如自然場景中的目標檢測)。

演示案例

void test(Mat& src)
{//1 轉為灰度圖像,做模糊處理(可選)Mat gray;cvtColor(src, gray, COLOR_BGR2GRAY);//2 二值化(用otsu算法做自動閾值處理)Mat binary;threshold(gray, binary, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);//3 形態學去除噪聲Mat mark = getStructuringElement(MORPH_RECT, Size(5, 5));morphologyEx(binary, binary, MORPH_OPEN, mark, Point(-1, -1), 2);//4 確定背景區域(遠離對象的區域) - 膨脹Mat screBg;dilate(binary, screBg, mark, Point(-1, -1), 3);//5 確定前景區域(位置變換+閾值處理)Mat distTransform;distanceTransform(binary, distTransform, cv::DIST_L2, 5);Mat sureForeground;threshold(distTransform, sureForeground, 0.7 * distTransform.at<float>(cv::Point(0, 0)), 255, cv::THRESH_BINARY);//6 獲取未知區域sureForeground.convertTo(sureForeground, CV_8U);Mat unkown;subtract(screBg,sureForeground , unkown);//7 創建標記圖像(核心圖像)其中背景為1  未知區域為0Mat marks;connectedComponents(sureForeground, marks);marks = marks + 1;	// 背景標記為1marks.setTo(0, unkown);	// 未知區域標記為0//8 應用分水嶺算法watershed(src, marks);//9 可視化分割結果Mat markerImage = Mat::zeros(marks.size(), CV_8UC3);for (int i = 0; i < marks.rows; ++i) {for (int j = 0; j < marks.cols; ++j) {int index = marks.at<int>(i, j);if (index == -1) {markerImage.at<cv::Vec3b>(i, j) = cv::Vec3b(255, 0, 0); // 分水嶺線}else {markerImage.at<cv::Vec3b>(i, j) = cv::Vec3b(index * 10 % 255, index * 20 % 255, index * 30 % 255);}}}//10 展示分割結果imshow("分水嶺分割", markerImage);
}

?

4.?圖像特征提取

4.1?cornerHarris角點檢測 ?

void cv::cornerHarris(InputArray src,   // 輸入圖像,必須是單通道、8位或浮點型圖像。OutputArray dst,  // 輸出圖像,通常是一個與輸入圖像大小相同的灰度圖像(32位浮點)。int blockSize,    // 計算導數自相關矩陣時使用的鄰域大小。int ksize,        // 使用Sobel算子計算偏導數時的孔徑大小。double k,         // Harris檢測器自由參數,用于響應函數的計算。int borderType = BORDER_DEFAULT // 邊界填充類型。
);
blockSize:計算特征值時考慮的鄰域窗口大小(典型值:2-5)
ksize:Sobel算子的孔徑大小,用于計算圖像的導數(梯度)。常見的取值為3,5等。
k: Harris角點檢測器的自由參數,用來調整響應函數的靈敏度。通常取值范圍在0.04到0.06之間。
borderType: 邊界處理的方式,默認為 BORDER_DEFAULT。其他選項如 BORDER_CONSTANT, BORDER_REPLICATE 等可根據需要選擇。
void test(Mat& src)
{//1 轉換為灰度圖像Mat gray;cvtColor(src, gray, COLOR_BGR2GRAY);//2 設置參數int blockSize = 2;int ksize = 3;double k = 0.04;//3 計算harris響應Mat dst = Mat::zeros(src.size(), CV_32FC1);cornerHarris(gray, dst, blockSize, ksize, k);//4 歸一化Mat dst_norm;normalize(dst, dst_norm, 0, 255,NORM_MINMAX,CV_32FC1);//5 繪制角點Mat result = src.clone();for (int i = 0; i < dst_norm.rows; i++) {for (int j = 0; j < dst_norm.cols; j++) {if ((int)dst_norm.at<float>(i, j) > 150) {	// 閾值篩選// 繪制紅色圓圈標記角點circle(result, Point(j, i), 5, Scalar(0, 0, 255), 2);}}}imshow("harris角點檢測", result);
}

4.2?goodFeaturesToTrack角點檢測

void cv::goodFeaturesToTrack(InputArray image,           // 輸入圖像,必須是單通道灰度圖像。OutputArray corners,        // 輸出角點數組。int maxCorners,             // 最大角點數量。double qualityLevel,        // 質量水平(閾值乘數)。double minDistance,         // 角點之間的最小距離。InputArray mask = noArray(),// 可選掩碼,指定感興趣區域。int blockSize = 3,          // 計算導數自相關矩陣時使用的鄰域大小。bool useHarrisDetector = false, // 是否使用 Harris 檢測器。double k = 0.04             // Harris 檢測器自由參數(僅當 useHarrisDetector 為 true 時有效)。
);
image:輸入圖像(必須為單通道)	灰度圖
corners:存儲檢測到的角點(vector<Point2f>
maxCorners:	返回的角點最大數量(≤0表示無限制)	100-500
qualityLevel:角點質量閾值(與最大響應值的比例)	0.01-0.1
minDistance	:角點間最小像素距離	10-20
mask:可選ROI掩碼(指定檢測區域)	二值圖
blockSize:	計算特征值的鄰域大小	3-7
useHarrisDetector:是否使用Harris算法(默認false用Shi-Tomasi)	true/false
k:Harris算法的k值(僅useHarris=true時有效)	0.04-0.06

演示案例

void test(Mat& src)
{//1 轉換為灰度圖像Mat gray;cvtColor(src, gray, COLOR_BGR2GRAY);//2 檢測角點// 2.1 設置角點檢測的參數vector<Point2f> corners;int maxCorners = 100;double qualityLevel = 0.01;double minDistance = 10;goodFeaturesToTrack(gray, corners, maxCorners, qualityLevel, minDistance, Mat());//3 繪制角點for (Point2f cor : corners){circle(src, cor, 5, Scalar(0, 0, 255), 1);}imshow("Shi-Tomasi角點檢測", src);
}

?

4.3 FastFeatureDetector特征檢測

void test(Mat& src)
{//1 轉為灰度圖像Mat gray;cvtColor(src, gray, COLOR_BGR2GRAY);//2 創建Fast特征檢測器auto fast = FastFeatureDetector::create();//3 檢測特征點vector<KeyPoint> points;fast->detect(src, points);//4 繪制特征點Mat dst;drawKeypoints(src, points, dst, Scalar(0, 0, 255));//5 顯示結果imshow("fast特征點", dst);
}

4.4??ORB算法

void test(Mat& src1,Mat& src2)
{//1 轉為灰度圖像Mat img1;cvtColor(src1, img1, COLOR_BGR2GRAY);Mat img2;cvtColor(src2, img2, COLOR_BGR2GRAY);//2 創建ORB檢測器auto orb = ORB::create();//3 檢測特征點和描述子vector<KeyPoint> point1, point2;Mat des1, des2;orb->detectAndCompute(img1, Mat(),point1,des1);orb->detectAndCompute(img2, Mat(),point2,des2);//4 匹配特征點// 創建 BFMatcher(暴力匹配器)cv::BFMatcher matcher(cv::NORM_HAMMING);std::vector<cv::DMatch> matches;matcher.match(des1, des2, matches);double max_dist = 0, min_dist = 100;for (const auto& match : matches) {double dist = match.distance;if (dist < min_dist) min_dist = dist;if (dist > max_dist) max_dist = dist;}std::vector<cv::DMatch> good_matches;for (const auto& match : matches) {if (match.distance <= std::max(2 * min_dist, 30.0)) {good_matches.push_back(match);}}//5 繪制匹配結果Mat dst;drawMatches(img1, point1, img2, point2, good_matches, dst);imshow("orb結果", dst);
}

說明一下:

  • cv::BFMatcher:暴力匹配器,適用于小規模特征點匹配。

  • cv::FlannBasedMatcher:基于 FLANN 的近似最近鄰匹配器,適用于大規模特征點匹配。

?

4.5 LBP算法

// 計算 LBP 特征
Mat computeLBP(Mat& src) {// 創建一個與輸入圖像大小相同的矩陣,用于存儲 LBP 結果Mat dst = Mat::zeros(src.rows, src.cols, CV_8UC1);// 遍歷圖像的每個像素(忽略邊界像素)for (int i = 1; i < src.rows - 1; i++) {for (int j = 1; j < src.cols - 1; j++) {// 獲取中心像素值uchar center = src.at<uchar>(i, j);// 計算 3x3 鄰域的 LBP 碼uchar code = 0;code |= (src.at<uchar>(i - 1, j - 1) > center) << 7; // 左上角code |= (src.at<uchar>(i - 1, j) > center) << 6;     // 正上方code |= (src.at<uchar>(i - 1, j + 1) > center) << 5; // 右上角code |= (src.at<uchar>(i, j + 1) > center) << 4;     // 右側code |= (src.at<uchar>(i + 1, j + 1) > center) << 3; // 右下角code |= (src.at<uchar>(i + 1, j) > center) << 2;     // 正下方code |= (src.at<uchar>(i + 1, j - 1) > center) << 1; // 左下角code |= (src.at<uchar>(i, j - 1) > center) << 0;     // 左側// 將 LBP 碼存儲到目標圖像中dst.at<uchar>(i, j) = code;}}return dst;
}void test(Mat& src)
{Mat dst = computeLBP(src);// 歸一化 LBP 圖像到 [0, 255] 范圍Mat normalizedDst;normalize(dst, normalizedDst, 0, 255, NORM_MINMAX, CV_8UC1);imshow("LBP結果", normalizedDst);imwrite("C:/Users/Administrator/Desktop/images/test.png", dst);
}

2.6?HOG算法

void test(Mat& src)
{//1 轉為灰度圖像Mat gray;cvtColor(src, gray, COLOR_BGR2GRAY);//2 創建HOG描述符對象HOGDescriptor hog;hog.setSVMDetector(HOGDescriptor::getDefaultPeopleDetector());	// 使用默認的行人檢測器//3 檢測行人vector<Rect> detections;// 每個檢測框的置信度,置信度越高,說明該檢測框越可能是真正的目標(例如行人)。置信度低的結果可能對應于誤檢或背景區域。vector<double> weights;//hog.detectMultiScale(gray, detections, weights);hog.detectMultiScale(gray, detections, weights, 0, Size(8, 8), Size(16, 16), 1.03, 0.7, true);vector<Rect> filteredDetections;nonMaximumSuppression(detections, weights, filteredDetections, 0.3);//4 繪制檢測結果for (int i = 0;i < filteredDetections.size();i++){Rect detec = filteredDetections[i];rectangle(src, detec, Scalar(0, 255, 0));}imshow("行人檢測", src);
}

5.?綜合練習-車牌識別

5.1?車牌區域檢測

#include <opencv2/opencv.hpp>
#include <iostream>using namespace cv;
using namespace std;// 1 車牌區域檢測
Mat detectLicensePlate(Mat& src)
{// 一 對圖像做預處理// 1. 轉換為HSV顏色空間(便于基于顏色篩選)Mat hsv;cvtColor(src, hsv, COLOR_BGR2HSV);// 2. 顏色篩選(藍色車牌范圍,可根據實際調整)Mat blue_mask;inRange(hsv, Scalar(100, 70, 70), Scalar(140, 255, 255), blue_mask);// 3. 形態學操作(連接邊緣)Mat kernel = getStructuringElement(MORPH_RECT, Size(15, 3));morphologyEx(blue_mask, blue_mask, MORPH_CLOSE, kernel);//4 查找輪廓vector<vector<Point>> contours;vector<Vec4i>  hierarchy;findContours(blue_mask, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);//5 篩選車牌區域vector<Rect> possibleRect;for (int i = 0; i < contours.size(); i++){// 篩選可能的車牌區域double area = contourArea(contours[i]);double length = arcLength(contours[i], true);if (area > 1000 && length < 500){Rect rect = boundingRect(contours[i]);double ratio = rect.width / rect.height;if (ratio >= 2 && ratio <= 3){/*cout << "area:" << area << "    length:" << length << "    ratio:" << ratio << endl;rectangle(src, rect, Scalar(0, 255, 0), 2);*/possibleRect.push_back(rect);}}}if (possibleRect.empty()){return Mat();}Rect rect = possibleRect.at(0);Mat plate = src(rect);return plate;
}int main() {Mat src = imread("C:\\Users\\46285\\Desktop\\qt_code\\images\\licensePlate3.jpeg");if (src.empty()){cout << "image read fail!!!" << endl;return -1;}Mat plate = detectLicensePlate(src);imshow("test", plate);waitKey(0);destroyAllWindows();// 銷毀所有窗口return 0;
}

第八章?深度學習&目標檢測(了解)

?????????深度學習(Deep Learning, DL)是一種特殊的機器學習方法,它模仿人腦的工作機制來處理數據,特別是通過多層的神經網絡模型進行學習。這些網絡能夠自動從大量數據中學習復雜的特征表示。主要依賴于人工神經網絡,尤其是深層架構,具有強大的特征提取能力

????????機器學習(Machine Learning, ML)是實現人工智能的一種方法,它使計算機系統能夠通過經驗(即數據)自動改進和適應,而無需明確編程來執行特定任務。使用算法解析數據,從中學習,并根據學到的信息對新數據做出決定或預測

1.?MobileNet預訓練模型+CIFAR-10數據集

#include <opencv2/opencv.hpp>
#include <opencv2/dnn.hpp>
#include <iostream>
#include <vector>
#include <string>
#include <fstream>using namespace cv;
using namespace cv::dnn;
using namespace std;// 加載類標簽的函數
// 加載圖像類別文件,將類別文件中的內容,轉成Vector<>返回
vector<string> loadClassLabels(const string& labelPath) {vector<string> classes;ifstream file(labelPath);if (file.is_open()) {string line;while (getline(file, line)) {classes.push_back(line);}file.close();}return classes;
}int fun1()
{try {// 1. 加載模型和標簽string modelPath = "C:/software/test/mobilenetv2_cifar10.onnx";string labelPath = "C:/software/test/cifar-100-binary/batches.meta.txt";string imagePath = "C:/Users/Administrator/Desktop/images/dog1.jpeg";// 加載模型文件Net net = readNetFromONNX(modelPath);if (net.empty()) {cerr << "Error: Failed to load ONNX model." << endl;return -1;}// 加載數據集的類別標簽vector<string> classes = loadClassLabels(labelPath);if (classes.empty()) {cout << "label path load fail......." << endl;return -1;}else {for (string className : classes){cout << className << "\t";}cout << endl;}// 2. 加載要做圖像分裂的圖片.并預處理圖像Mat image = imread(imagePath);if (image.empty()) {cerr << "Error: Failed to load image." << endl;return -1;}// MobileNet 的預處理:調整大小、歸一化、均值減法Mat blob = blobFromImage(image,1.0 / 255.0,                     // 縮放因子Size(32, 32),                    // 輸入尺寸Scalar(),                        // 不減均值false,                           // 不交換RB通道false);                          // 不裁剪// 調整 blob 形狀為 (batch_size, height, width, channels)Mat blob_transposed;transposeND(blob, { 0, 2, 3, 1 }, blob_transposed);net.setInput(blob_transposed);// 3. 執行推理Mat output = net.forward();// 打印輸出結果用于調試cout << "Model output: " << output << endl;// 4. 解析結果Point classIdPoint;double confidence;minMaxLoc(output.reshape(1, 1), 0, &confidence, 0, &classIdPoint);int classId = classIdPoint.x;cout << "Output shape: " << output.size() << endl;// 5. 顯示結果if (classId < 0 || classId >= classes.size()) {cerr << "Error: Invalid class ID: " << classId << endl;return -1;}cout << "classId:" << classId << endl;string label = format("%s: %.2f%%", classes[classId].c_str(), confidence * 100);cout << "Predicted: " << label << endl;// 在圖像上繪制結果resize(image, image, Size(600, 600));putText(image, label, Point(10, 30), FONT_HERSHEY_SIMPLEX, 0.8, Scalar(0, 255, 0), 2);imshow("Classification Result", image);waitKey(0);destroyAllWindows();}catch (exception& e){cout << e.what() << endl;}return 0;
}

?第九章?視頻處理&攝像頭操作

1.?VideoCapture讀取視頻

????????在C++中使用OpenCV讀取視頻文件攝像頭實時流,主要通過 cv::VideoCapture 類實現。

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;int main()
{//1 指定視頻文件路徑創建VideoCapture VideoCapture cap("C:/Users/Administrator/Desktop/video/cat.mp4");//2 判斷是否成功打開視頻if (!cap.isOpened()){cout << "視頻打開失敗!" << endl;return -1;}//3 獲取視頻基本信息double width = cap.get(CAP_PROP_FRAME_WIDTH);double height = cap.get(CAP_PROP_FRAME_HEIGHT);double count = cap.get(CAP_PROP_FRAME_COUNT);double fps = cap.get(CAP_PROP_FPS);cout << "視頻的寬=" << width << endl;cout << "視頻的高=" << height << endl;cout << "視頻的總幀數=" << count << endl;//FPS是視頻或游戲畫面的幀率,單位為“幀/秒”(frames per second)。例如,24FPS表示每秒顯示24張圖像。cout << "視頻的fps=" << fps << endl;//4 逐幀讀取視頻并展示Mat frame;width = width * 0.2;height = height * 0.2;while (true){// 讀取視頻的每一幀cap >> frame;	//等價于cap.read(frame);// 檢測當前幀是否為空,一般如果為空,說明已經到達視頻末尾if (frame.empty()){break;}// 如果幀的高和寬過大,導致展示的圖片不全,可以調整寬和高Mat dst;resize(frame, dst, Size(width,height));// 對每一幀做處理,比如展示,或者將當前幀轉換為灰度圖像等等Mat gray;cvtColor(dst, gray, COLOR_BGR2GRAY);imshow("video", dst);imshow("gray video", gray);// 根據視頻的幀率延遲,并按esc鍵退出循環if (waitKey(1000/fps) == 27){break;}}//5 釋放資源cap.release();destroyAllWindows();return 0;
}

?2.?常用VideoCapture視頻屬性

CAP_PROP_FRAME_WIDTH視頻幀的寬度(像素)double1920
CAP_PROP_FRAME_HEIGHT視頻幀的高度(像素)double1080
CAP_PROP_FPS視頻的幀率(Frames Per Second)double30.0
CAP_PROP_FRAME_COUNT視頻的總幀數double2500(需注意:部分攝像頭流返回0)
CAP_PROP_POS_FRAMES當前幀的索引(從0開始)double100
CAP_PROP_POS_MSEC當前時間戳(毫秒)double3500.0
CAP_PROP_FOURCC視頻編解碼器的FourCC代碼double1196444237(對應'MJPG'
CAP_PROP_BRIGHTNESS攝像頭亮度設置(僅攝像頭有效)double0.5(范圍依賴設備)
CAP_PROP_CONTRAST攝像頭對比度設置double0.3
CAP_PROP_SATURATION攝像頭飽和度設置double0.8
CAP_PROP_EXPOSURE攝像頭曝光值double-4.0(依賴設備)
CAP_PROP_AUTOFOCUS攝像頭自動對焦是否開啟double1.0(開啟)

3.?VideoWriter保存視頻

在C++中使用OpenCV的VideoWriter類可以輕松實現視頻的錄制和保存。

FourCC 是一種用于標識視頻編碼格式的四字符代碼。常用的編碼格式包括:

  • 'M', 'J', 'P', 'G':Motion-JPEG 編碼(常用于 .avi 文件)。
  • 'X', 'V', 'I', 'D':XVID 編碼(適用于 .avi 文件)。
  • 'H', '2', '6', '4':H.264 編碼(適用于 .mp4 文件)。

????????使用H.264編碼,OpenCV 是要加載 OpenH264 動態鏈接庫的,因此要使用這種模式,還需要額外安裝OpenH264動態鏈接庫。

????????.avi:通常與 MJPG 或 XVID 編碼配合使用。 .mp4:通常與 H.264 編碼配合使用。 確保系統支持所選編碼格式,否則可能無法生成視頻文件

void writeVideo(VideoCapture& cap)
{//1 創建VideoWriter對象,用于寫視頻文件cv::String fileName = "C:/Users/Administrator/Desktop/video/temp.avi";// 設置視頻編碼格式int fourcc = VideoWriter::fourcc('M', 'J', 'P', 'G');double width = cap.get(CAP_PROP_FRAME_WIDTH);double height = cap.get(CAP_PROP_FRAME_HEIGHT);double fps = cap.get(CAP_PROP_FPS);// isColor:是否為彩色視頻(默認為 true)。如果為 false,則生成灰度視頻VideoWriter writer(fileName,fourcc,fps,Size(width,height),false);//2 打開文件if (!writer.isOpened()){cout << "視頻文件打開失敗!" << endl;return;}//3 向視頻文件寫入幀Mat src;while (true){// 讀取幀cap >> src;// 讀完視頻退出if (src.empty()){break;}// 操作幀// 將視頻灰度化后重新保存Mat gray;cvtColor(src, gray, COLOR_BGR2GRAY);// 寫入幀writer.write(gray);// 按下Esc鍵退出循環if (waitKey(30) == 27){break;}}cout << "已經成功保存視頻" << endl;//4 釋放資源writer.release();
}

?4.?視頻追蹤

????????視頻追蹤的實現通常包括以下幾個步驟:

  1. 目標檢測:在初始幀中識別并定位感興趣的目標。
  2. 特征提取:從目標區域提取可用于區分該目標與其他對象的特征(如顏色、形狀、紋理等)
  3. 模型更新:隨著視頻幀的變化,可能需要更新目標模型以適應外觀變化。
  4. 位置預測與校正:根據前一幀中的目標位置和速度,預測當前幀中的位置,并通過搜索或匹配算法進行校正

4.1?Meanshift 算法

????????Meanshift 是一種基于密度估計的非參數化算法,最初用于聚類分析。在計算機視覺中,它被廣泛應用于目標追蹤任務。Meanshift 的核心思想是通過迭代地將搜索窗口向目標區域的顏色直方圖分布中心移動,從而實現對目標的定位和追蹤

void meanshiftTest(VideoCapture& cap)
{//1 獲取第一幀Mat frame;cap >> frame;if (frame.empty()){cout << "無法讀取視頻幀" << endl;return;}//2 定義初始的目標窗口(ROI)Rect roi = selectROI("請選擇追蹤目標",frame,false);	// 手動選擇目標cout << roi.width << "   " << roi.height << endl;if (roi.width <= 0 || roi.height <= 0){cout << "已取消選擇roi區域" << endl;return;}Mat target = frame(roi).clone();if (target.empty()){cout << "目標區域為空" << endl;return;}imshow("target", target);//3 轉換目標窗口的顏色空間Mat hsv_target;cvtColor(target, hsv_target, COLOR_BGR2HSV);//4 過濾掉低亮度值的像素,較少噪聲Mat mask;inRange(hsv_target, Scalar(0, 30, 0), Scalar(180, 255, 255), mask);//5 計算目標區域的顏色直方圖int histSize = 180;float range[] = { 0,180 };const float* histRange = { range };Mat hist_target;int channels[] = { 0 };// 計算直方圖calcHist(&hsv_target, 1, channels, mask, hist_target, 1, &histSize, &histRange);// 歸一化normalize(hist_target, hist_target, 0,255, NORM_MINMAX);//6 利用meanshift逐幀循環追蹤TermCriteria criteria(TermCriteria::EPS | TermCriteria::COUNT, 10, 1);  // 迭代終止條件while (true){// 讀取幀cap >> frame;if (frame.empty()){break;}// 轉換為hsv色彩空間Mat hsvFrame;cvtColor(frame, hsvFrame, COLOR_BGR2HSV);// 計算反向投影Mat back;calcBackProject(&hsvFrame, 1, channels, hist_target, back,&histRange);// 執行MeanShift算法meanShift(back, roi, criteria);// 繪制結果rectangle(frame, roi, Scalar(0, 255, 0));imshow("視頻追蹤", frame);if (waitKey(30) == 27){break;}}
}

4.2?Camshift 算法

????????Camshift(Continuously Adaptive Mean Shift)是 Meanshift 算法的改進版本,專門用于目標追蹤。與 Meanshift 不同,Camshift 能夠自適應地調整搜索窗口的大小和方向,從而更好地處理目標的尺度變化和旋轉

void camshiftTest(VideoCapture& cap)
{//1 獲取第一幀Mat frame;cap >> frame;if (frame.empty()){cout << "無法讀取視頻幀" << endl;return;}//2 定義初始的目標窗口(ROI)Rect roi = selectROI("請選擇追蹤目標", frame, false);	// 手動選擇目標cout << roi.width << "   " << roi.height << endl;if (roi.width <= 0 || roi.height <= 0){cout << "已取消選擇roi區域" << endl;return;}Mat target = frame(roi).clone();if (target.empty()){cout << "目標區域為空" << endl;return;}imshow("target", target);//3 轉換目標窗口的顏色空間Mat hsv_target;cvtColor(target, hsv_target, COLOR_BGR2HSV);//4 過濾掉低亮度值的像素,較少噪聲Mat mask;inRange(hsv_target, Scalar(0, 60, 32), Scalar(180, 255, 255), mask);//5 計算目標區域的顏色直方圖int histSize = 180;float range[] = { 0,180 };const float* histRange = { range };Mat hist_target;int channels[] = { 0 };// 計算直方圖calcHist(&hsv_target, 1, channels, mask, hist_target, 1, &histSize, &histRange);// 歸一化normalize(hist_target, hist_target, 0, 255, NORM_MINMAX);//6 利用meanshift逐幀循環追蹤TermCriteria criteria(TermCriteria::EPS | TermCriteria::COUNT, 10, 1);  // 迭代終止條件while (true){// 讀取幀cap >> frame;if (frame.empty()){break;}// 轉換為hsv色彩空間Mat hsvFrame;cvtColor(frame, hsvFrame, COLOR_BGR2HSV);// 計算反向投影Mat back;calcBackProject(&hsvFrame, 1, channels, hist_target, back, &histRange);// 執行CamShift算法RotatedRect trackBox = CamShift(back, roi, criteria);// 繪制結果rectangle(frame, roi, Scalar(0, 255, 0));//ellipse(frame, trackBox, Scalar(0, 0, 255), 2);  // 繪制橢圓表示目標形狀imshow("視頻追蹤", frame);if (waitKey(30) == 27){break;}}
}

4.3?MeanShift和CamShift算法的比較

優先MeanShift:

  • 目標大小和方向基本不變(如靜態攝像頭下的人臉追蹤)。
  • 對實時性要求極高(>30 FPS)。

優先CamShift:

  • 目標尺度或方向變化明顯(如車輛靠近/遠離、手勢旋轉)。
  • 需要輸出目標朝向信息(如AR應用)。

?5.?攝像頭實時處理

?5.1?攝像頭實時捕獲&處理

實現攝像頭實時處理的基本步驟

  1. 打開攝像頭:使用 OpenCV 的 cv::VideoCapture 打開攝像頭設備或讀取視頻文件。
  2. 逐幀讀取視頻流:通過循環不斷從攝像頭讀取每一幀圖像。 ?
  3. 對每一幀進行處理:應用計算機視覺算法(如目標檢測、邊緣檢測、顏色分割等)對圖像進行處理。 ?
  4. 顯示處理結果:使用 OpenCV 的 cv::imshow() 函數將處理后的圖像實時顯示在窗口中。 ?
  5. 釋放資源:處理完成后,關閉攝像頭并釋放相關資源。
#include <opencv2/opencv.hpp>
#include <iostream>using namespace cv;
using namespace std;void cameraTest()
{//1 打開攝像頭// 參數 0 表示默認攝像頭(通常為內置攝像頭)。如果有多臺攝像頭,可以嘗試使用 1, 2 等索引值。VideoCapture cap(0);//2 設置攝像頭分辨率,也就是設置寬和高cap.set(CAP_PROP_FRAME_WIDTH, 680);cap.set(CAP_PROP_FRAME_HEIGHT, 480);//3 存儲攝像頭傳輸的每一幀圖像Mat frame;while (true){// 讀取每一幀cap >> frame;if (frame.empty()){cout << "無法讀取攝像頭幀" << endl;break;}// 對幀做處理,比如灰度處理Mat gray;cvtColor(frame, gray, COLOR_BGR2GRAY);// 顯示實時幀和處理后的幀imshow("實時幀", frame);imshow("灰度幀", gray);if (waitKey(30) == 27){break;}}// 釋放cap.release();destroyAllWindows();
}int main() {cameraTest();return 0;
}

?5.2?實時跟蹤

????????使用meanShiftcamShift算法對攝像頭捕獲到的內容做實時跟蹤。整個操作和上面視頻跟蹤一模一樣,只是不提供已存在的視頻,而是通過攝像頭實時捕獲

void camShiftCameraTest()
{VideoCapture cap(0);//1 獲取第一幀Mat frame;cap >> frame;if (frame.empty()){cout << "無法讀取視頻幀" << endl;return;}//2 定義初始的目標窗口(ROI)Rect roi = selectROI("請選擇追蹤目標", frame, false);	// 手動選擇目標cout << roi.width << "   " << roi.height << endl;if (roi.width <= 0 || roi.height <= 0){cout << "已取消選擇roi區域" << endl;return;}Mat target = frame(roi).clone();if (target.empty()){cout << "目標區域為空" << endl;return;}imshow("target", target);//3 轉換目標窗口的顏色空間Mat hsv_target;cvtColor(target, hsv_target, COLOR_BGR2HSV);//4 過濾掉低亮度值的像素,較少噪聲Mat mask;inRange(hsv_target, Scalar(0, 60, 32), Scalar(180, 255, 255), mask);//5 計算目標區域的顏色直方圖int histSize = 180;float range[] = { 0,180 };const float* histRange = { range };Mat hist_target;int channels[] = { 0 };// 計算直方圖calcHist(&hsv_target, 1, channels, mask, hist_target, 1, &histSize, &histRange);// 歸一化normalize(hist_target, hist_target, 0, 255, NORM_MINMAX);//6 利用meanshift逐幀循環追蹤TermCriteria criteria(TermCriteria::EPS | TermCriteria::COUNT, 10, 1);  // 迭代終止條件while (true){// 讀取幀cap >> frame;if (frame.empty()){break;}// 轉換為hsv色彩空間Mat hsvFrame;cvtColor(frame, hsvFrame, COLOR_BGR2HSV);// 計算反向投影Mat back;calcBackProject(&hsvFrame, 1, channels, hist_target, back, &histRange);// 執行MeanShift算法RotatedRect trackBox = CamShift(back, roi, criteria);// 繪制結果rectangle(frame, roi, Scalar(0, 255, 0));//ellipse(frame, trackBox, Scalar(0, 0, 255), 2);  // 繪制橢圓表示目標形狀imshow("視頻追蹤", frame);if (waitKey(30) == 27){break;}}destroyAllWindows();
}

5.3?實時邊緣檢測

????????通過攝像頭實時捕獲視頻,使用邊緣檢測算法(如 Canny 邊緣檢測)提取圖像中的邊緣

void cameraCannyTest()
{//1 打開攝像頭// 參數 0 表示默認攝像頭(通常為內置攝像頭)。如果有多臺攝像頭,可以嘗試使用 1, 2 等索引值。VideoCapture cap(0);//2 設置攝像頭分辨率,也就是設置寬和高cap.set(CAP_PROP_FRAME_WIDTH, 680);cap.set(CAP_PROP_FRAME_HEIGHT, 480);//3 存儲攝像頭傳輸的每一幀圖像Mat frame;while (true){// 讀取每一幀cap >> frame;if (frame.empty()){cout << "無法讀取攝像頭幀" << endl;break;}// 對幀做處理,比如灰度處理Mat gray;cvtColor(frame, gray, COLOR_BGR2GRAY);// 高斯模糊Mat blur;GaussianBlur(gray, blur, Size(3, 3),1.5);// Canny邊緣檢測Mat dst;Canny(blur, dst, 50, 150);// 顯示實時幀和處理后的幀imshow("實時幀", frame);imshow("邊緣檢測", dst);if (waitKey(30) == 27){break;}}// 釋放cap.release();destroyAllWindows();
}

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/78942.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/78942.shtml
英文地址,請注明出處:http://en.pswp.cn/web/78942.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Python自學第5天:字符串相關操作

1.字符串運算符 作符描述字符串連接*重復輸出字符串[]通過索引獲取字符串中字符[ : ]截取字符串中的一部分&#xff0c;遵循左閉右開原則&#xff0c;str[0:2] 是不包含第 3 個字符的。in成員運算符 - 如果字符串中包含給定的字符返回 Truenot in成員運算符 - 如果字符串中不包…

RabbitMq(尚硅谷)

RabbitMq 1.RabbitMq異步調用 2.work模型 3.Fanout交換機&#xff08;廣播模式&#xff09; 4.Diret交換機&#xff08;直連&#xff09; 5.Topic交換機&#xff08;主題交換機&#xff0c;通過路由匹配&#xff09; 6.Headers交換機&#xff08;頭交換機&#xff09; 6…

分庫分表后復雜查詢的應對之道:基于DTS實時性ES寬表構建技術實踐

1 問題域 業務發展的初期&#xff0c;我們的數據庫架構往往是單庫單表&#xff0c;外加讀寫分離來快速的支撐業務&#xff0c;隨著用戶量和訂單量的增加&#xff0c;數據庫的計算和存儲往往會成為我們系統的瓶頸&#xff0c;業界的實踐多數采用分而治之的思想&#xff1a;分庫…

CVE-2024-4577:Windows 編碼錯誤

CVE-2024-4577是一個 PHP-CGI 漏洞,就是其中一種情況:雖然有這個版本,但由于 PHP 經常被反向移植,因此無法可靠地使用。 這篇博文詳細介紹了如何研究 CVE-2024-4577 以及當前用于檢測它的方法。 CVE-2024-4577 CVE-2024-4577 是 Windows 版 PHP 安裝中的一個高危漏洞,會…

NetBox Docker 全功能部署方案(Ubuntu 22.04 + Docker)

環境準備 檢查操作系統版本&#xff1a; 本方案使用 Ubuntu 22.04&#xff0c;并在 VMware 虛擬機中運行。通過以下命令檢查系統版本&#xff1a; lsb_release -a 如果未安裝 Ubuntu 22.04&#xff0c;請下載并安裝一個全新的系統。 更新系統軟件源&#xff1a; 更新軟件包列表…

DeepSeek Copilot idea插件推薦

&#x1f30c; DeepSeek Copilot for IntelliJ IDEA 讓 AI 成為你的編程副駕駛&#xff0c;極速生成單元測試 & 代碼注釋驅動開發&#xff01; &#x1f680; 簡介 DeepSeek Copilot 是一款為 IntelliJ IDEA 打造的 AI 編程助手插件&#xff0c;它能夠智能分析你的代碼邏輯…

QT中的JSON

1.JSON的兩種數據格式 JSON有兩種數據格式:JSON對象和JSON數組 JSON數組&#xff1a; JSON數組格式&#xff1a;[元素1&#xff0c;元素2&#xff0c;元素3&#xff0c;......元素n] JSON數組中的元素可以是同一類型&#xff0c;也可以使不同類型&#xff0c;可以嵌套JSON數組…

詳細剖析傳輸層協議(TCP和UDP)

詳細講解傳輸層的網絡協議&#xff0c;為什么TCP是可靠連接協議&#xff0c;憑什么能做到不丟包&#xff0c;有哪些機制保證可靠呢&#xff1f; TCP/UDP UDPTCP**三次握手和四次揮手****滑動窗口****擁塞控制**&#xff08;socket套接字&#xff09;**listen的第二個參數** UD…

數據可視化:藝術與科學的交匯點,如何讓數據“開口說話”?

數據可視化&#xff1a;藝術與科學的交匯點&#xff0c;如何讓數據“開口說話”&#xff1f; 數據可視化&#xff0c;是科技與藝術的結合&#xff0c;是讓冰冷的數字變得生動有趣的橋梁。它既是科學——講究準確性、邏輯性、數據處理的嚴謹性&#xff1b;又是藝術——強調美感…

解決使用lettuce連接Redis超時的問題(tcpUserTimeout 參數失效問題)

問題背景 lettuce 連接Redis的主從實例&#xff0c;當主節的主機異常下電重啟后&#xff0c;由于沒有發送RST 包&#xff0c;導致 lettuce 一直在復用之前的TCP鏈接&#xff0c;然后會出現連接超時的情況。一直出現io.lettuce.core.RedisCommandTimeoutException: Command tim…

如何使用python保存字典

在Python中&#xff0c;可以通過多種方式將字典&#xff08;dict&#xff09;保存到文件中&#xff0c;并能夠隨時讀取恢復。以下是幾種常見的方法&#xff1a; 1. 使用 json 模塊&#xff08;推薦&#xff09; 適用場景&#xff1a;需要人類可讀的文件格式&#xff0c;且數據不…

SQL 與 Python:日期維度表創建的不同選擇

文章目錄 一、日期維度表概述日期維度表結構 二、使用 SQL 創建日期維度表2.1 表結構設計2.2 數據插入2.3 SQL 創建方式的優勢與局限 三、使用 Python 創建日期維度表3.1 依賴庫引入3.2 代碼實現3.3 Python 創建方式的優勢與局限 四、應用場景與選擇建議4.1 應用場景4.2 選擇建…

如何用postman進行批量操作

業務場景&#xff1a; 有些時候&#xff0c;我們會需要批量的將SAP B1系統中的幾千條的數據刪除或者取消單據&#xff0c;這個時候&#xff0c;一條條去操作&#xff0c;指定是到猴年馬月了。SAP Business One本身提供了DTW這個工具&#xff0c;但是這個更新&#xff0c;可以操…

Mysql如何完成數據的增刪改查(詳解從0到1)

前言&#xff1a; Mysql可能是每個程序員的必修課&#xff0c;可以說是使用起來是沒有什么問題的&#xff0c;但是作為一名合格的程序猿&#xff0c;深入學習Mysql的內部工作原理是非常有必要的&#xff0c;主要是理解和學習Mysql的底層思想&#xff0c;希望在日后如遇到一些&…

單片機嵌入式按鍵庫

kw_btn庫說明 本庫主要滿足嵌入式按鍵需求&#xff0c;集成了常用的按鍵響應事件&#xff1a;高電平、低電平、上升沿、下降沿、單擊、雙擊、長按鍵事件。可以裸機運行&#xff0c;也可以配合實時操作系統運行。 本庫開源連接地址&#xff1a;連接 實現思路 本庫采用C語言進行…

Qt—鼠標移動事件的趣味小程序:會移動的按鈕

1.項目目標 本次根據Qt的鼠標移動事件實現一個趣味小程序&#xff1a;當鼠標移動到按鈕時&#xff0c;按鈕就會隨機出現在置&#xff0c;以至于根本點擊不到按鈕。????? 2.項目步驟 首先現在ui界面設計控件(也可以用代碼的方式創建&#xff0c;就不多說了) 第一個按鈕不需…

MySQL的information_schema在SQL注入中的關鍵作用與防御策略

目錄 一、information_schema的核心價值 二、攻擊利用場景與示例 1. 聯合查詢注入&#xff08;Union-Based&#xff09; 2. 報錯注入&#xff08;Error-Based&#xff09; 3. 布爾盲注&#xff08;Boolean Blind&#xff09; 4. 時間盲注&#xff08;Time-Based&#xff0…

c語言 關鍵字--目錄

下面是詳細介紹的鏈接 1.c語言 關鍵字 2.typedef 關鍵字 3.volatile 關鍵字 4.register 關鍵字 5.const關鍵字用法 6.extern關鍵字 7.sizeof關鍵字

python爬蟲爬取網站圖片出現403解決方法【僅供學習使用】

基于CSDN第一篇文章&#xff0c;Python爬蟲之入門保姆級教程&#xff0c;學不會我去你家刷廁所。 這篇文章是2021年作者發表的&#xff0c;由于此教程&#xff0c;網站添加了反爬機制&#xff0c;有作者通過添加cookie信息來達到原來的效果&#xff0c;Python爬蟲添加Cookies以…

docker創建一個centOS容器安裝軟件(以寶塔為例)的詳細步驟

備忘&#xff1a;后續偶爾忘記了docker虛擬機與宿主機的端口映射關系&#xff0c;來這里查看即可&#xff1a; docker run -d \ --name baota \ --privilegedtrue \ -p 8888:8888 \ -p 8880:80 \ -p 8443:443 \ -p 8820:20 \ -p 8821:21 \ -v /home/www:/www/wwwroot \ centos…