一、邊緣檢測
在數字圖像處理領域,邊緣檢測是一項至關重要的基礎技術。它如同為圖像賦予 “骨架”,幫助計算機快速識別圖像中的物體輪廓、形狀與結構,廣泛應用于目標識別、圖像分割、圖像配準等多個領域。
1.1 概念
邊緣檢測的核心目標是找出圖像中像素灰度發生劇烈變化的區域邊界。這些邊界往往對應著圖像中物體的輪廓、不同物體的交界處或紋理變化明顯的地方。通過提取這些邊緣信息,可以有效減少圖像數據量,同時保留圖像中最關鍵的結構特征,為后續的高級圖像處理任務奠定基礎。
1.2 邊緣定義和類型
邊緣定義
在圖像中,邊緣可以看作是局部區域內像素灰度值的不連續性。從數學角度理解,灰度值的變化率在邊緣處會出現顯著變化。例如,在一幅黑白圖像中,從黑色區域過渡到白色區域的邊界處,像素灰度值會從較低值突然躍升至較高值,這個過渡區域就是邊緣所在之處。
邊緣類型
-
階躍型邊緣:像素灰度值在邊緣兩側呈現明顯的階梯狀變化,一側灰度值較低,另一側較高,中間過渡區域極窄,類似于一個理想的階躍函數。
-
屋頂型邊緣:灰度值從較低水平逐漸上升至峰值,然后又迅速下降到較低水平,邊緣像素位于灰度值變化的峰值位置,形狀類似屋頂。
1.3 梯度概念
為了準確檢測出圖像中的邊緣,需要引入梯度的概念。在二維圖像 f ( x , y ) f(x, y) f(x,y) 中,梯度是一個向量,它的方向指向圖像灰度增長最快的方向,其幅值反映了灰度變化的劇烈程度。
圖像在點 ( x , y ) (x, y) (x,y) 處的梯度向量 ? f ( x , y ) \nabla f(x, y) ?f(x,y) 可表示為:
? f ( x , y ) = [ ? f ? x ? f ? y ] \nabla f(x, y) = \begin{bmatrix} \frac{\partial f}{\partial x} \\ \frac{\partial f}{\partial y} \end{bmatrix} ?f(x,y)=[?x?f??y?f??]
其中, ? f ? x \frac{\partial f}{\partial x} ?x?f? 和 ? f ? y \frac{\partial f}{\partial y} ?y?f? 分別是圖像在 x x x 方向和 y y y 方向上的偏導數。在實際計算中,通常使用有限差分來近似偏導數,常用的算子有Roberts 算子、Prewitt 算子和Sobel 算子。
Roberts 算子
原理
Roberts 算子是一種基于一階差分的簡單邊緣檢測算子,它利用局部差分來尋找邊緣,對具有陡峭的低噪聲圖像效果較好。該算子采用 2×2 的卷積核,通過計算圖像在對角線方向上的灰度差分來近似梯度。
假設圖像在點 ( x , y ) (x,y) (x,y)處的灰度值為 f ( x , y ) f(x,y) f(x,y),Roberts 算子通過以下兩個卷積核分別計算兩個對角線方向的梯度:
G x = [ 1 0 0 ? 1 ] ? f ( x , y ) G_x = \begin{bmatrix} 1 & 0 \\ 0 & -1 \end{bmatrix} * f(x, y) Gx?=[10?0?1?]?f(x,y)
G y = [ 0 1 ? 1 0 ] ? f ( x , y ) G_y = \begin{bmatrix} 0 & 1 \\ -1 & 0 \end{bmatrix} * f(x, y) Gy?=[0?1?10?]?f(x,y)
這里的 “*” 表示卷積運算。以計算 G x G_x Gx?為例,它會將卷積核覆蓋在圖像對應位置,將卷積核元素與圖像像素值對應相乘后求和,得到該位置在 G x G_x Gx?方向上的梯度近似值。然后通過公式 G = G x 2 + G y 2 G = \sqrt{G_x^2 + G_y^2} G=Gx2?+Gy2??計算梯度幅值,通過 θ = arctan ? ( G y G x ) \theta = \arctan(\frac{G_y}{G_x}) θ=arctan(Gx?Gy??)計算梯度方向 。
假設現在有圖像為:
[ p 1 p 2 p 3 p 4 p 5 p 6 p 7 p 8 p 9 ] \begin{bmatrix} p1 & p2 & p3 \\ p4 & p5 & p6 \\ p7 & p8 & p9 \end{bmatrix} ?p1p4p7?p2p5p8?p3p6p9? ?
像素點p5處,x,y方法向上的梯度:
g x = p 9 ? p 5 g_x = p9-p5 gx?=p9?p5
g y = p 8 ? p 6 g_y = p8-p6 gy?=p8?p6
當圖像邊緣接近±45°時,該算法效果最好。
Prewitt 算子
原理
Prewitt 算子同樣基于一階差分,它使用 3×3 的卷積核來計算圖像在水平和垂直方向上的梯度。Prewitt 算子的卷積核考慮了像素點周圍鄰域的信息,相比 Roberts 算子能更好地抑制噪聲。其兩個卷積核如下:
G x = [ ? 1 0 1 ? 1 0 1 ? 1 0 1 ] ? f ( x , y ) G_x = \begin{bmatrix} -1 & 0 & 1 \\ -1 & 0 & 1 \\ -1 & 0 & 1 \end{bmatrix} * f(x, y) Gx?= ??1?1?1?000?111? ??f(x,y)
G y = [ ? 1 ? 1 ? 1 0 0 0 1 1 1 ] ? f ( x , y ) G_y = \begin{bmatrix} -1 & -1 & -1 \\ 0 & 0 & 0 \\ 1 & 1 & 1 \end{bmatrix} * f(x, y) Gy?= ??101??101??101? ??f(x,y)
G x G_x Gx?卷積核用于檢測水平方向的邊緣, G y G_y Gy?卷積核用于檢測垂直方向的邊緣。計算過程與 Roberts 算子類似,通過卷積運算得到水平和垂直方向的梯度,再計算梯度幅值和方向。
Sobel 算子
原理
Sobel 算子是一種更為常用的邊緣檢測算子,它在 Prewitt 算子的基礎上進行了改進,通過對鄰域像素賦予不同的權重,能更準確地計算圖像梯度。Sobel 算子也使用 3×3 的卷積核,分別計算水平和垂直方向的梯度:
G x = [ ? 1 0 1 ? 2 0 2 ? 1 0 1 ] ? f ( x , y ) G_x = \begin{bmatrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{bmatrix} * f(x, y) Gx?= ??1?2?1?000?121? ??f(x,y)
G y = [ ? 1 ? 2 ? 1 0 0 0 1 2 1 ] ? f ( x , y ) G_y = \begin{bmatrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\ 1 & 2 & 1 \end{bmatrix} * f(x, y) Gy?= ??101??202??101? ??f(x,y)
從卷積核可以看出,在水平方向的卷積核 G x G_x Gx?中,中間一行的系數絕對值更大,意味著在計算水平方向梯度時,中間像素的權重更高;同理,在垂直方向的卷積核 G y G_y Gy?中,中間一列的系數絕對值更大。這種加權方式使得 Sobel 算子對邊緣的響應更加準確,同時對噪聲也有一定的抑制能力。
使用方法
在 OpenCV 中,提供了Sobel
函數方便地實現 Sobel 算子:
#include <opencv2/opencv.hpp>
#include <iostream>using namespace cv;
using namespace std;int main() {Mat image = imread("lena.jpg", IMREAD_GRAYSCALE);if (image.empty()) {cout << "Could not open or find the image" << endl;return -1;}Mat gradient_x, gradient_y;Sobel(image, gradient_x, CV_16S, 1, 0, 3);Sobel(image, gradient_y, CV_16S, 0, 1, 3);Mat sobel_edges;convertScaleAbs(gradient_x, gradient_x);convertScaleAbs(gradient_y, gradient_y);addWeighted(gradient_x, 0.5, gradient_y, 0.5, 0, sobel_edges);imshow("Original Image", image);imshow("Sobel Edge Detection", sobel_edges);waitKey(0);return 0;
}
Sobel
函數參數說明:
-
image
:輸入圖像。 -
gradient_x/gradient_y
:輸出的梯度圖像,CV_16S
表示輸出圖像的數據類型為 16 位有符號整數,這是為了防止在計算梯度過程中出現溢出。 -
1, 0
或0, 1
:分別表示在 x x x方向或 y y y方向上計算梯度,若為1
則表示該方向上進行求導,0
表示不求導。 -
3
:表示 Sobel 算子的孔徑大小,即卷積核大小為 3×3。
這三種算子各有特點:Roberts 算子計算簡單、速度快,但對噪聲敏感;Prewitt 算子通過擴大鄰域考慮更多像素,有一定抗噪能力;Sobel 算子通過加權計算,在檢測精度和抗噪性能上表現更優,是實際應用中較為常用的選擇。在具體的邊緣檢測任務中,可根據圖像特點和需求靈活選擇合適的算子 。
1.4 Canny 原理
Canny 邊緣檢測算法是目前應用最廣泛的邊緣檢測算法之一,由 John F. Canny 在 1986 年提出。它通過一系列步驟,在有效檢測邊緣的同時,最大程度地減少噪聲干擾和虛假邊緣,其核心步驟如下:
1. 高斯濾波
為了減少圖像中的噪聲對邊緣檢測的影響,首先使用高斯濾波器對圖像進行平滑處理。高斯濾波的原理是利用高斯函數對圖像進行加權平均,通過對鄰域內像素值賦予不同權重,使得距離中心越近的像素影響越大,從而在平滑圖像的同時保留一定的邊緣信息。其公式為:
G ( x , y ) = 1 2 π σ 2 e ? x 2 + y 2 2 σ 2 G(x,y) = \frac{1}{2\pi\sigma^2} e^{-\frac{x^2+y^2}{2\sigma^2}} G(x,y)=2πσ21?e?2σ2x2+y2?
其中, σ \sigma σ 是高斯分布的標準差,決定了高斯濾波器的平滑程度。
2. 計算梯度幅值和方向
使用 Sobel 算子等方法計算圖像在 x x x 方向和 y y y 方向的梯度分量 G x G_x Gx? 和 G y G_y Gy?,進而得到梯度幅值 G G G 和梯度方向 θ \theta θ。梯度幅值反映了邊緣的強度,梯度方向則指示了邊緣的走向。
3. 非極大值抑制
經過梯度計算后,圖像中的每個像素都有了對應的梯度幅值和方向。非極大值抑制的目的是將梯度幅值圖像中潛在的邊緣細化為單像素寬的邊緣。具體做法是:對于每個像素,沿著其梯度方向,比較該像素與其兩側相鄰像素的梯度幅值。如果該像素的梯度幅值不是局部最大值,則將其賦值為 0,即抑制該像素,認為它不是真正的邊緣點;只有梯度幅值為局部最大值的像素才被保留,作為可能的邊緣點。
4. 雙閾值檢測和邊緣連接
設置兩個閾值:高閾值 T h T_h Th? 和低閾值 T l T_l Tl?(通常 T h T_h Th? 是 T l T_l Tl? 的 2 - 3 倍)。將梯度幅值大于高閾值 T h T_h Th? 的像素點確定為強邊緣點;將梯度幅值小于低閾值 T l T_l Tl? 的像素點直接排除,認為它們不是邊緣點;對于梯度幅值介于 T l T_l Tl? 和 T h T_h Th? 之間的像素點,若其與強邊緣點相連,則保留為弱邊緣點,否則也將其排除。最后,通過邊緣連接操作,將弱邊緣點與其相連的強邊緣點合并,形成完整的邊緣。
1.5 Canny 實現
函數原型(OpenCV)
void Canny(InputArray image, OutputArray edges, double threshold1, double threshold2, int apertureSize = 3, bool L2gradient = false);
參數說明:
-
image
:輸入圖像,必須是單通道 8 位圖像。 -
edges
:輸出的邊緣圖像,與輸入圖像大小相同,也是單通道 8 位圖像。 -
threshold1
:低閾值。 -
threshold2
:高閾值。 -
apertureSize
:用于計算梯度的 Sobel 算子的孔徑大小,默認為 3。 -
L2gradient
:表示是否使用更精確的 L2 范數計算梯度幅值,若為false
,則使用 L1 范數,默認為false
。
示例代碼
#include <opencv2/opencv.hpp>
#include <iostream>using namespace cv;
using namespace std;int main() {Mat image = imread("lena.jpg", IMREAD_GRAYSCALE);if (image.empty()) {cout << "Could not open or find the image" << endl;return -1;}Mat edges;// 使用Canny邊緣檢測,低閾值為50,高閾值為150Canny(image, edges, 50, 150);imshow("Original Image", image);imshow("Canny Edge Detection", edges);waitKey(0);return 0;
}
二、圖像分割
在數字圖像處理領域,圖像分割是一項關鍵技術,其目標是將圖像劃分為若干個具有獨特特性的區域,每個區域內的像素在某種特征(如灰度、顏色、紋理等)上較為相似,而不同區域之間的差異明顯。通過圖像分割,能夠將復雜的圖像簡化為更易于分析和處理的結構,提取出感興趣的目標,為后續的圖像分析、目標識別、場景理解等任務奠定基礎。
2.1 灰度圖的閾值分割
灰度圖像的閾值分割是最基礎且常用的圖像分割方法之一,它的核心作用是根據圖像中目標與背景在灰度值上的差異,通過設定一個或多個閾值,將圖像中的像素劃分為不同的類別(通常為目標和背景兩類),從而實現目標區域的提取和背景的分離。這種方法計算簡單、效率高,適用于目標和背景灰度差異明顯的圖像。
閾值分割
原理
閾值分割的基本原理是:對于一幅灰度圖像 f ( x , y ) f(x,y) f(x,y),選取一個合適的閾值 T T T,將圖像中的每個像素點根據其灰度值 I ( x , y ) I(x,y) I(x,y)與閾值 T T T的大小關系進行分類:
-
若 I ( x , y ) ≥ T I(x,y) \geq T I(x,y)≥T,則將該像素點歸為一類(通常視為目標區域);
-
若 I ( x , y ) < T I(x,y) < T I(x,y)<T,則將該像素點歸為另一類(通常視為背景區域)。
通過這種方式,將灰度圖像轉換為二值圖像,突出目標與背景的差異。通過這種方式,將灰度圖像轉換為二值圖像,突出目標與背景的差異。
函數原型
double threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type);
參數說明:
-
src
:輸入的單通道圖像(灰度圖像)。 -
dst
:輸出的二值化圖像,與輸入圖像大小和類型相同。 -
thresh
:設定的閾值。 -
maxval
:當像素值滿足條件時,賦予的最大值(通常在二值化中為 255)。 -
type
:閾值類型,常見的有:-
THRESH_BINARY
:二值化閾值處理,大于等于閾值的像素設為maxval
,小于閾值的設為 0。 -
THRESH_BINARY_INV
:反二值化閾值處理,大于等于閾值的像素設為 0,小于閾值的設為maxval
。 -
THRESH_TRUNC
:截斷閾值處理,大于閾值的像素設為閾值,小于等于閾值的保持不變。 -
THRESH_TOZERO
:大于閾值的像素保持不變,小于閾值的設為 0。 -
THRESH_TOZERO_INV
:大于閾值的像素設為 0,小于等于閾值的保持不變。
-
示例代碼
#include <opencv2/opencv.hpp>
#include <iostream>using namespace cv;
using namespace std;int main() {Mat image = imread("lena.jpg", IMREAD_GRAYSCALE);if (image.empty()) {cout << "Could not open or find the image" << endl;return -1;}Mat binary_image;// 使用THRESH_BINARY類型進行閾值分割,閾值設為127threshold(image, binary_image, 127, 255, THRESH_BINARY);imshow("Original Image", image);imshow("Binary Image (Thresholding)", binary_image);waitKey(0);return 0;
}
自適應閾值分割
原理
在實際應用中,圖像的光照條件、背景復雜度可能不均勻,固定閾值的分割方法往往無法取得理想效果。自適應閾值分割能夠根據圖像局部區域的特征,動態地計算每個像素點的閾值,從而在復雜場景下實現更準確的分割。常見的自適應閾值計算方法是基于局部鄰域的平均灰度值或高斯加權平均灰度值。
以基于局部鄰域平均灰度值的方法為例,對于圖像中的每個像素點 ( x , y ) (x,y) (x,y),計算其周圍一個大小為 k × k k \times k k×k鄰域內像素的平均灰度值 m ( x , y ) m(x,y) m(x,y),然后以 m ( x , y ) m(x,y) m(x,y)為基礎,通過減去一個常數 C C C得到該像素點的閾值 T ( x , y ) T(x,y) T(x,y):
T ( x , y ) = m ( x , y ) ? C T(x,y) = m(x,y) - C T(x,y)=m(x,y)?C
再根據上述閾值分割原理,將像素點進行分類。
函數原型
void adaptiveThreshold(InputArray src, OutputArray dst, double maxValue, int adaptiveMethod, int thresholdType, int blockSize, double C);
參數說明:
-
src
:輸入的單通道圖像(灰度圖像)。 -
dst
:輸出的二值化圖像,與輸入圖像大小和類型相同。 -
maxValue
:當像素值滿足條件時,賦予的最大值(通常在二值化中為 255)。 -
adaptiveMethod
:自適應閾值計算方法,常見的有:-
ADAPTIVE_THRESH_MEAN_C
:基于局部鄰域的平均灰度值計算閾值。 -
ADAPTIVE_THRESH_GAUSSIAN_C
:基于局部鄰域的高斯加權平均灰度值計算閾值。
-
-
thresholdType
:閾值類型,通常使用THRESH_BINARY
或THRESH_BINARY_INV
。 -
blockSize
:計算局部閾值時的鄰域大小,必須為奇數。 -
C
:從平均灰度值或高斯加權平均灰度值中減去的常數。
示例代碼
#include <opencv2/opencv.hpp>
#include <iostream>using namespace cv;
using namespace std;int main() {Mat image = imread("text_image.jpg", IMREAD_GRAYSCALE);if (image.empty()) {cout << "Could not open or find the image" << endl;return -1;}Mat adaptive_binary_image;// 使用ADAPTIVE_THRESH_GAUSSIAN_C方法,THRESH_BINARY類型,鄰域大小為11,常數C為2adaptiveThreshold(image, adaptive_binary_image, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 11, 2);imshow("Original Image", image);imshow("Adaptive Binary Image", adaptive_binary_image);waitKey(0);return 0;
}
2.2 彩色圖像分割
彩色圖像包含豐富的顏色信息,彩色圖像分割旨在利用這些顏色信息,將圖像中的不同物體或區域分割開來。相比于灰度圖像分割,彩色圖像分割能夠更準確地提取目標,在物體識別、圖像編輯、醫學圖像分析等領域有著廣泛的應用。
HSV 分割
原理
HSV(Hue, Saturation, Value)色彩空間相較于常見的 RGB 色彩空間,更符合人類對顏色的感知方式。其中,H
(色調)表示顏色的種類,如紅色、綠色、藍色等;S
(飽和度)表示顏色的鮮艷程度;V
(明度)表示顏色的明亮程度。
HSV 分割的基本思路是:根據目標顏色在 HSV 空間中的取值范圍,將圖像從 RGB 色彩空間轉換到 HSV 色彩空間,然后通過設定H
、S
、V
三個通道的閾值范圍,篩選出符合條件的像素點,從而實現對特定顏色目標的分割。
例如,對于紅色物體的分割,紅色在 HSV 空間中的色調值通常分布在兩個區間(因為紅色跨越了 0 度和 360 度邊界): [ 0 , 10 ] [0, 10] [0,10]和 [ 160 , 180 ] [160, 180] [160,180],同時可以設定飽和度和明度的合理范圍,如 S ∈ [ 50 , 255 ] S \in [50, 255] S∈[50,255], V ∈ [ 50 , 255 ] V \in [50, 255] V∈[50,255],將滿足這些條件的像素點提取出來。
void cvtColor(InputArray src, OutputArray dst, int code);
用于將圖像從一種色彩空間轉換到另一種色彩空間,在 HSV 分割中,使用COLOR_BGR2HSV
參數將 RGB 圖像轉換為 HSV 圖像。
inRange(InputArray src, InputArray lowerb, InputArray upperb, OutputArray dst);
參數說明:
-
src
:輸入圖像(在 HSV 分割中為 HSV 圖像)。 -
lowerb
:設定的下限閾值(包含三個通道的下限值)。 -
upperb
:設定的上限閾值(包含三個通道的上限值)。 -
dst
:輸出的二值圖像,符合閾值范圍的像素設為 255,否則設為 0。
示例代碼
#include <opencv2/opencv.hpp>
#include <iostream>using namespace cv;
using namespace std;int main() {Mat image = imread("fruits.jpg");if (image.empty()) {cout << "Could not open or find the image" << endl;return -1;}Mat hsv_image;// 將圖像從BGR色彩空間轉換到HSV色彩空間cvtColor(image, hsv_image, COLOR_BGR2HSV);Mat mask;// 設定紅色的HSV閾值范圍Scalar lower_red = Scalar(0, 50, 50);Scalar upper_red = Scalar(10, 255, 255);Scalar lower_red2 = Scalar(160, 50, 50);Scalar upper_red2 = Scalar(180, 255, 255);Mat mask1, mask2;inRange(hsv_image, lower_red, upper_red, mask1);inRange(hsv_image, lower_red2, upper_red2, mask2);bitwise_or(mask1, mask2, mask);Mat segmented_image;// 根據掩碼提取紅色物體bitwise_and(image, image, segmented_image, mask);imshow("Original Image", image);imshow("Segmented Image (HSV)", segmented_image);waitKey(0);return 0;
}
GrabCut
原理
GrabCut 算法全稱是 “Grab and Cut”,即 “抓取并分割”,它是一種交互式的圖像分割算法,旨在通過用戶少量的交互操作,實現對圖像中前景目標和背景的精準分割.
-
初始化:用戶需要在圖像上標記一個包含目標的矩形框,這個矩形框劃定了算法處理的大致范圍。此時,矩形框內的區域被初始化為 “可能的前景” 和 “可能的背景”,而矩形框外的區域則直接被認定為 “確定的背景”。這就好比在一幅畫中,先圈出想要分割的大致區域,框外的部分直接排除在目標之外 。
-
模型訓練 - 高斯混合模型(GMM):高斯混合模型是一種概率模型,它假設前景和背景中的像素分布都可以由多個高斯分布組合而成。算法會利用 GMM 分別對前景和背景進行參數估計,通過計算每個像素點與不同高斯分布的匹配程度,來判斷該像素屬于前景或背景的概率。簡單來說,就是為前景和背景分別建立 “模板”,看每個像素更符合哪個 “模板” 。
-
能量函數最小化:GrabCut 定義了一個能量函數,這個函數綜合考慮了數據項和光滑項。數據項衡量的是像素與 GMM 模型的匹配程度,即像素屬于前景或背景的可能性大小;光滑項則關注相鄰像素之間的一致性,希望相鄰像素盡可能地屬于同一類別(前景或背景),避免出現雜亂的分割結果。算法通過圖割算法(Graph Cut)來迭代地最小化這個能量函數,不斷調整每個像素的標記(前景或背景),使得整體的能量達到最小,也就意味著分割結果達到最優 。
-
重復迭代:不斷重復模型訓練和能量函數最小化的過程,每一次迭代都會讓前景和背景的標記更加準確,直到前景和背景的標記不再發生顯著變化,此時就得到了最終的分割結果。這個過程就像反復打磨一件藝術品,讓分割邊界越來越精準 。
函數原型
void grabCut(InputArray src, InputOutputArray mask, Rect rect, InputOutputArray bgdModel, InputOutputArray fgdModel, int iterCount, int mode=GC_EVAL);
參數說明:
-
src
:輸入的 8 位 3 通道彩色圖像,是待分割的原始圖像。 -
mask
:輸入輸出的單通道 8 位掩碼圖像,用于存儲每個像素的分類標記。初始時需設置部分區域,算法運行中不斷更新。其取值有GC_BGD
(確定背景)、GC_FGD
(確定前景)、GC_PR_BGD
(可能背景)、GC_PR_FGD
(可能前景) 。 -
rect
:包含目標的矩形框,用于劃定算法處理范圍,格式為Rect(x, y, width, height)
,x
、y
是矩形左上角坐標,width
、height
是矩形寬和高。 -
bgdModel
:背景的高斯混合模型參數,算法內部使用和更新,是一個Mat
類型變量。 -
fgdModel
:前景的高斯混合模型參數,同樣由算法內部使用和更新,也是Mat
類型變量。 -
iterCount
:算法執行的迭代次數,數值越大,分割結果越精細,但計算耗時也越長。 -
mode
:運行模式,常用GC_INIT_WITH_RECT
(使用矩形框初始化)和GC_INIT_WITH_MASK
(使用掩碼初始化) ,默認值GC_EVAL
用于在已有初始化基礎上進行評估和更新。
#include <opencv2/opencv.hpp>
#include <iostream>using namespace cv;
using namespace std;int main() {Mat image = imread("person.jpg");if (image.empty()) {cout << "Could not open or find the image" << endl;return -1;}// 初始化掩碼圖像,大小與輸入圖像相同,初始值全為GC_BGD(確定背景)Mat mask = Mat::zeros(image.size(), CV_8UC1); // 手動設置包含目標的矩形框Rect rect(100, 100, 300, 400); Mat bgdModel, fgdModel;// 使用矩形框初始化,迭代5次進行GrabCut分割grabCut(image, mask, rect, bgdModel, fgdModel, 5, GC_INIT_WITH_RECT); // 根據掩碼生成最終分割結果,將前景像素保留,背景像素設為黑色Mat result;compare(mask, GC_PR_FGD, result, CMP_EQ); //GC_PR_FGD:這是一個常量值 (3),代表 "可能的前景" 標記;CMP_EQ:比較操作類型,表示 "相等" 比較Mat foreground(image.size(), CV_8UC3, Scalar(0, 0, 0));image.copyTo(foreground, result);imshow("Original Image", image);imshow("GrabCut Segmented Image", foreground);waitKey(0);return 0;
}
在上述代碼中,首先讀取圖像,創建并初始化掩碼圖像,設置包含目標的矩形框。接著調用grabCut
函數進行分割,迭代 5 次優化分割結果。最后通過比較掩碼圖像,將屬于 “可能前景” 的像素從原圖復制到結果圖像中,實現前景目標的提取與顯示。
FloodFill
原理
FloodFill(漫水填充)算法,從名字就能直觀地理解其原理 —— 像洪水淹沒一樣,從一個起始點開始,逐漸填充滿足條件的區域。它不僅可以用于填充封閉區域,還能根據顏色相似性實現圖像分割,在圖像編輯、簡單目標提取等場景中應用廣泛。
-
選擇種子點:首先,需要在圖像中指定一個 “種子點” ( x , y ) (x,y) (x,y),這個點就像是洪水的源頭,后續的填充操作都從這里開始擴散。種子點的選擇可以由用戶手動指定,也可以通過程序根據一定規則自動確定,比如圖像的中心位置,或者某個顏色特征明顯的點 。
-
鄰域檢查與填充:算法會檢查種子點的像素值,并與周圍鄰域(通常是 4 連通鄰域或 8 連通鄰域)的像素值進行比較。這里的 “4 連通鄰域” 指的是種子點上下左右四個方向的相鄰像素,“8 連通鄰域” 則還包括四個對角方向的像素 。若鄰域像素值與種子點像素值的差異在設定的容差范圍內,就認為這些鄰域像素與種子點屬于同一區域,將它們標記為填充區域,并把這些像素點加入到待處理隊列中。這就好比洪水會向周圍地勢相近的區域蔓延 。
-
迭代填充:從待處理隊列中取出一個像素點,將其作為新的 “種子點”,重復步驟 2 的操作,繼續檢查它的鄰域像素并進行填充,直到待處理隊列為空。此時,整個與初始種子點顏色相近且滿足容差條件的連通區域都被填充完畢,實現了該區域的分割。整個過程就像洪水不斷擴散,直到沒有可以淹沒的新區域為止 。
函數原型
int floodFill(InputOutputArray image, InputOutputArray mask, Point seedPoint, Scalar newVal, Rect* rect=0, Scalar loDiff=Scalar(), Scalar upDiff=Scalar(), int flags=4);
參數說明:
-
image
:輸入輸出的 8 位單通道或 3 通道圖像,填充操作會直接修改該圖像。 -
mask
:輸入輸出的單通道 8 位掩碼圖像,用于記錄填充區域,其大小需比image
大一圈(寬和高各增加 2),且邊緣需初始化為 0。 -
seedPoint
:填充操作的起始種子點,類型為Point
,指定x
、y
坐標。 -
newVal
:填充的新顏色值,對于單通道圖像是一個標量,對于 3 通道圖像是包含三個通道值的Scalar
類型 。 -
rect
:可選參數,用于記錄填充區域的外接矩形,類型為Rect*
。 -
loDiff
:當前像素值與種子點像素值之間的下限容差,即像素值可以比種子點像素值小的最大差值。 -
upDiff
:當前像素值與種子點像素值之間的上限容差,即像素值可以比種子點像素值大的最大差值。 -
flags
:填充方式和操作標志,低 8 位表示鄰域連通性(4 連通4
或 8 連通8
);高 8 位常用FLOODFILL_FIXED_RANGE
(基于種子點顏色固定容差范圍)或FLOODFILL_MASK_ONLY
(只填充掩碼圖像,不修改原圖) 。
#include <opencv2/opencv.hpp>
#include <iostream>using namespace cv;
using namespace std;int main() {Mat image = imread("colorful_image.jpg");if (image.empty()) {cout << "Could not open or find the image" << endl;return -1;}// 創建掩碼圖像,比原圖大一圈,初始值為0Mat mask(image.rows + 2, image.cols + 2, CV_8UC1, Scalar(0)); Point seed(100, 100); // 設定種子點Scalar new_color(0, 255, 0); // 綠色// 進行漫水填充,8連通,容差設為50,基于種子點顏色固定容差范圍floodFill(image, mask, seed, new_color, 0, Scalar(50), Scalar(50), 8 | FLOODFILL_FIXED_RANGE); imshow("Original Image", image);imshow("FloodFill Segmented Image", image);waitKey(0);return 0;
}
代碼中先讀取圖像,創建合適的掩碼圖像并設定種子點、填充顏色。調用floodFill
函數時,設置為 8 連通填充,上下容差均為 50,采用基于種子點顏色固定容差范圍的方式。執行后,圖像中與種子點顏色相近的連通區域被填充為綠色,實現了基于顏色相似性的區域分割與顯示。