OpenCV數(shù)字圖像處理基于C++:圖像分割
1、基于閾值的分割
圖像閾值化分割是一種常用的、傳統(tǒng)的圖像分割技術,因其實現(xiàn)簡單、計算量小、性能比較穩(wěn)定而成為圖像分割中基本和應用廣泛的分割技術。特別適合于目標和背景占據(jù)不同灰度級范圍的圖像。不僅可以極大地壓縮數(shù)據(jù)量,而且大大簡化了分析和處理的步驟,是進行圖像分析、特征提取與模式識別之前所必要的圖像預處理步驟。
圖像閾值化的目的是要按照灰度級,對像素集合進行一個劃分,得到的每個子集形成一個與現(xiàn)實景物相對應的區(qū)域,各個區(qū)域內部具有一致的屬性,而相鄰區(qū)域不具有這種一致屬性。這樣的劃分可以通過從灰度級出發(fā)選取一個或多個閾值來實現(xiàn)。
基本原理:通過設定不同的特征閾值,把圖像象素點分為若干類。
1.1 固定閾值分割
給定一個全局的固定的閾值,整張圖片的每個像素的像素值都與該值進行比較,若小于該閾值則將像素值改為一個固定的值(常用0),若大于該閾值則將像素值改為另一個固定的值(常用255),則可以將圖像進行二值分割,得到一張二值圖。
1.1.1 手工實現(xiàn)固定閾值分割
Mat mythreshold(Mat src, double T) {
Mat dst(src.size(),src.type());
//獲取原圖像行列信息
int nRows = src.rows;
int nCols = src.cols;
for (int i = 0; i < nRows; i++) {
for (int j = 0; j < nCols; j++) {
//二值化,小于閾值賦0,大于閾值賦255
if (src.at<uchar>(i, j) < T) {
dst.at<uchar>(i, j) = 0;
}
else {
dst.at<uchar>(i, j) = 255;
}
}
}
return dst;
}
int main()
{
//------------【1】讀取源圖像并檢查圖像是否讀取成功------------
Mat srcImage = imread("E:\\4.jpg");
if (!srcImage.data)
{
puts("讀取圖片錯誤,請重新輸入正確路徑!");
system("pause");
return -1;
}
imshow("【源圖像】", srcImage);
//------------【2】灰度轉換------------
Mat srcGray;
cvtColor(srcImage, srcGray, CV_RGB2GRAY);
imshow("【灰度圖】", srcGray);
//------------【3】固定閾值分割---------------
//1 在這里使用圖像的平均值作為閾值T
Scalar T = mean(srcGray);
Mat dst;
dst = mythreshold(srcGray,T[0]);
imshow("【固定閾值分割圖】", dst);
waitKey(0);
return 0;
}
1.1.2 函數(shù)實現(xiàn)固定閾值分割
threshold(srcGray, dst, T[0], 255,0);
imshow("【函數(shù)固定閾值分割圖】", dst);
cv::threshold(
InputArray src,
OutputArray dst,
double thresh,
double maxval,
type
);
//參數(shù)1:輸入的灰度圖像
//參數(shù)2:輸出圖像
//參數(shù)3:進行閾值操作時閾值的大小
//參數(shù)4:設定的最大灰度值(該參數(shù)運用在二進制與反二進制閾值操作中)
//參數(shù)5:閾值的類型。從下面提到的5種中選擇出的結果
cv::THRESH_BINARY=0: 二進制閾值
cv::THRESH_BINARY_INV=1: 反二進制閾值
cv::THRESH_TRUNC=2: 截斷閾值
cv::THRESH_TOZERO=3: 0閾值
cv::THRESH_TOZERO_INV=4: 反0閾值
cv::THRESH_OTSU=8 自適應閾值
(1)正向二值化,THRESH_BINARY
正向二值化,如果當前的像素值大于設置的閾值(thresh),則將該點的像素值設置為maxval;否則,將該點的像素值設置為0;
(2)反向二值化,THRESH_BINARY_INV
反向二值化,如果當前的像素值大于設置的閾值(thresh),則將該點的像素值設置為0;否則,將該點的像素值設置為maxval
(3)THRESH_TRUNC
如果當前的像素值大于設置的閾值(thresh),則將該點的像素值設置為threshold;否則,將該點的像素值不變
(4)THRESH_TOZERO
如果當前的像素值大于設置的閾值(thresh),則將該點的像素值不變;否則,將該點的像素值設置為0
THRESH_TOZERO_INV
如果當前的像素值大于設置的閾值(thresh),則將該點的像素值設置為0;否則,將該點的像素值不變
1.2 自適應閾值分割
在不均勻照明或者灰度值分布不均的情況下,如果使用全局閾值分割,那么得到的分割效果往往會很不理想,這個時候就要用到自適應閾值算法了。
1.2.1 手工實現(xiàn)自適應閾值分割
enum adaptiveMethod { meanFilter, gaaussianFilter, medianFilter };
void myAdaptiveThreshold(Mat& src, Mat& dst, double Maxval, int Subsize, double c, adaptiveMethod method = meanFilter) {
if (src.channels() > 1)
cvtColor(src, src, CV_RGB2GRAY);
Mat smooth;
switch (method)
{
case meanFilter:
blur(src, smooth, Size(Subsize, Subsize)); //均值濾波
break;
case gaaussianFilter:
GaussianBlur(src, smooth, Size(Subsize, Subsize), 0, 0); //高斯濾波
break;
case medianFilter:
medianBlur(src, smooth, Subsize); //中值濾波
break;
default:
break;
}
smooth = smooth - c;
//閾值處理
src.copyTo(dst);
for (int r = 0; r < src.rows; ++r) {
const uchar* srcptr = src.ptr<uchar>(r);
const uchar* smoothptr = smooth.ptr<uchar>(r);
uchar* dstptr = dst.ptr<uchar>(r);
for (int c = 0; c < src.cols; ++c) {
if (srcptr[c] > smoothptr[c]) {
dstptr[c] = Maxval;
}
else
dstptr[c] = 0;
}
}
}
int main() {
Mat src = imread("E:\\test.jpg");
if (src.empty()) {
return -1;
}
if (src.channels() > 1)
cvtColor(src, src, CV_RGB2GRAY);
Mat dst, dst2;
double t2 = (double)getTickCount();
myAdaptiveThreshold(src, dst, 255, 21, 10, meanFilter); //
t2 = (double)getTickCount() - t2;
double time2 = (t2 * 1000.) / ((double)getTickFrequency());
std::cout << "my_process=" << time2 << " ms. " << std::endl << std::endl;
adaptiveThreshold(src, dst2, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 21, 10);
imshow("原圖", src);
imshow("手工自適應", dst);
imshow("函數(shù)自適應", dst2);
waitKey(0);
}
1.2.2 函數(shù)實現(xiàn)自適應閾值分割
int main()
{
//------------【1】讀取源圖像并檢查圖像是否讀取成功------------
Mat srcImage = imread("E:\\4.jpg");
if (!srcImage.data)
{
puts("讀取圖片錯誤,請重新輸入正確路徑!");
system("pause");
return -1;
}
imshow("【源圖像】", srcImage);
//------------【2】灰度轉換------------
Mat srcGray;
cvtColor(srcImage, srcGray, CV_RGB2GRAY);
imshow("【灰度圖】", srcGray);
//------------【3】初始化相關變量---------------
Mat dstImage; //初始化自適應閾值參數(shù)
const int maxVal = 255;
int blockSize = 3; //取值3、5、7....等
int constValue = 10;
int adaptiveMethod = 0;
int thresholdType = 1;
//---------------【4】圖像自適應閾值操作-------------------------
adaptiveThreshold(srcGray, dstImage, maxVal, adaptiveMethod, thresholdType, blockSize, constValue);
imshow("【自適應閾值】", dstImage);
waitKey(0);
return 0;
}
void adaptiveThreshold(
InputArray src,
OutputArray dst,
double maxValue,
int adaptiveMethod,
int thresholdType,
int blockSize,
double C
);
第一個參數(shù),InputArray src,原圖,即輸入圖像,是一個8位單通道的圖像;
第二個參數(shù),OutputArray dst,目標圖像,與原圖像具有同樣的尺寸與類型;
第三個參數(shù),double maxValue,分配給滿足條件的像素的非零值;
第四個參數(shù),int adaptiveMethod,自適應閾值的方法,通常有以下幾種方法;
ADAPTIVE_THRESH_MEAN_C,閾值T(x,y)是(x,y)減去C的Blocksize×Blocksize鄰域的平均值。
ADAPTIVE_THRESH_GAUSSIAN_C ,閾值T(x,y)是(x,y)減去C的Blocksize×Blocksize鄰域的加權和(與高斯相關),默認sigma(標準差)用于指定的Blocksize;具體的情況可以參見getGaussianKernel函數(shù);
第五個參數(shù),int thresholdType,閾值的類型必須是以下兩種類型,
THRESH_BINARY,正向二值化
THRESH_BINARY_INV ,反向二值化
第六個參數(shù),int blockSize,計算blocksize x blocksize大小的領域內的閾值,必須為奇數(shù),例如,3,5,7等等,一般二值化使用21,31,41;
第七個參數(shù),double C,從平均數(shù)或加權平均數(shù)減去常量。通常,它是正的,但也可能是零或負數(shù)。,二值化時使用的7。
補充
函數(shù)cvAdaptiveThreshold的確可以將灰度圖像二值化,但它的主要功能應該是邊緣提取,關鍵是里面的block_size參數(shù),該參數(shù)是決定局部閾值的block的大小
1)當block很小時,如block_size=3 or 5 or 7時,“自適應”的程度很高,即容易出現(xiàn)block里面的像素值都差不多,這樣便無法二值化,而只能在邊緣等梯度大的地方實現(xiàn)二值化,結果顯得它是邊緣提取函數(shù);
2)當把block_size設為比較大的值時,如block_size=21 or 31 or 41時,cvAdaptiveThreshold便是二值化函數(shù)了;
3)src與dst 這兩個都要是單通道的圖像。
1.3 對比固定閾值和自適應閾值分割
int main()
{
Mat img = imread("E:\\test.jpg");
Mat dst1;
Mat dst2;
Mat dst3;
cv::cvtColor(img, img, COLOR_RGB2GRAY);//進行,灰度處理
medianBlur(img, img, 5);//中值濾波
threshold(img, dst1, 127, 255, THRESH_BINARY);//閾值分割
adaptiveThreshold(img, dst2, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 11, 2);//自動閾值分割,鄰域均值
adaptiveThreshold(img, dst3, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 11, 2);//自動閾值分割,高斯鄰域
imshow("dst1", dst1);
imshow("dst2", dst2);
imshow("dst3", dst3);
imshow("img", img);
waitKey(0);
}
1.4 迭代法閾值分割
(1)求出圖像的最大灰度值和最小灰度值,分別記為Zmax和Zmin,另初始閾值為T0 = (Zmax + Zmin) / 2。
(2)根據(jù)閾值Tk將圖像分割為前景和背景,分別求出兩者的平均灰度值Zo和Zb。
(3)求出新的閾值Tk+1 = (Zo + Zb) / 2。
(4)若Tk == Tk+1,則即為所求的閾值,否則轉到步驟2繼續(xù)迭代。
(5)使用計算后的閾值進行閾值分割。
其實迭代法就是將固定閾值分割里手動給定閾值改為了迭代計算閾值,可以適用的范圍更多一些,但是本質還是固定閾值變換。
//聲明全局閾值分割函數(shù),輸入圖像,輸出圖像,T0,初始閾值T
void global_threshold_segmentation(Mat& input_image, Mat& output_image, int T0, int T);
int main()
{
Mat image, image_gray, image_bw;
image = imread("Cameraman.bmp");
if (image.empty())
{
cout << "讀取圖像出錯" << endl;
return -1;
}
cvtColor(image, image_gray, 6);
namedWindow("image_gray", 0);
imshow("image_gray", image_gray);
//1 在這里使用圖像的平均值作為初始值T, T0=5
Scalar image_meam = cv::mean(image_gray); //使用opencv的mean函數(shù)求平均值
int T = (int)image_meam[0];//圖像的平均值作為初始值T
global_threshold_segmentation(image_gray, image_bw, 5, T);
namedWindow("image_bw", 0);
imshow("image_bw", image_bw);
//2 使用任意值作為初始閾值
Mat image_bw2;
int T1 = 5; //任意值
global_threshold_segmentation(image_gray, image_bw2, 5, T1);
namedWindow("image_bw2", 0);
imshow("image_bw2", image_bw2);
waitKey();
return 0;
}
//定義全局閾值分割函數(shù)
void global_threshold_segmentation(Mat& input_image, Mat& output_image, int T0, int T)
{
//使用初始值T進行分組并求每組的平均值m1和m2,并計算新的閾值T2
int width = input_image.cols; //圖像列數(shù)
int height = input_image.rows; //圖像行數(shù)
uchar* Img = input_image.data; //圖像指針
int G1_mean, G2_mean; //定義每組像素的均值
int G1_num = 1, G2_num = 1; //定義每組像素的數(shù)量,初始值設為1,以免后面出現(xiàn)除以0的問題
int G1_sum = 0, G2_sum = 0; //定義每組灰度值之和
for (int i = 0; i < height; i++)
{
uchar* Img = input_image.ptr(i); //圖像每行數(shù)據(jù)的指針
for (int j = 0; j < width; j++)
{
if (Img[j] < T)
{
G1_sum += Img[j];
G1_num += 1;
}
else
{
G2_sum += Img[j];
G2_num += 1;
}
}
}
G1_mean = G1_sum / G1_num;
G2_mean = G2_sum / G2_num;
int T2 = (G1_mean + G2_mean) * 0.5; //新閾值
//迭代計算T
if (abs(T2 - T) > T0)
{
global_threshold_segmentation(input_image, output_image, T0, T2);
}
else
{
threshold(input_image, output_image, T2, 255, 1);
}
}
2、基于邊緣分割
基于邊緣的分割代表了一大類基于圖像邊緣信息的方法?;谶吘壍姆指钜蕾囉谟?strong>邊緣檢測算子找到的圖像邊緣,這些邊緣表示除了圖像在灰度、彩色、紋理等方面不連續(xù)的位置。在分割處理中可獲得的先驗信息越多,能達到的分割效果越好。常見的邊緣檢測方法有Roberts算子,Laplance算子,Sobel算子,LoG算子以及Canny算子等。可參考:(53條消息) OpenCV數(shù)字圖像處理基于C++:邊緣檢測_qq_43784519的博客-CSDN博客和(53條消息) OpenCV數(shù)字圖像處理基于C++:Canny邊緣檢測_qq_43784519的博客-CSDN博客
2.1 輪廓函數(shù)
輪廓函數(shù)
OpenCV 中,可在圖像的邊緣檢測之后,使用 findContours() 尋找到輪廓,該函數(shù)參數(shù)如下: image 一般為二值化圖像,可由 compare, inRange, threshold , adaptiveThreshold, Canny 等函數(shù)獲得。
Mat src, src_gray;
int thresh = 100;
int max_thresh = 255;
RNG rng(12345);
void thresh_callback(int, void*);
int main()
{
// 讀圖
src = imread("E:\\la.jpg", IMREAD_COLOR);
if (src.empty())
return -1;
// 轉化為灰度圖
cvtColor(src, src_gray, COLOR_BGR2GRAY);
//均值濾波
blur(src_gray, src_gray, Size(3, 3));
// 顯示
namedWindow("Source", WINDOW_AUTOSIZE);
imshow("Source", src);
// 滑動條
createTrackbar("Canny thresh:", "Source", &thresh, max_thresh, thresh_callback);
// 回調函數(shù)
thresh_callback(0, 0);
waitKey(0);
}
// 回調函數(shù)
void thresh_callback(int, void*)
{
Mat canny_output;
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
// canny 邊緣檢測
Canny(src_gray, canny_output, thresh, thresh * 2, 3);
// 尋找輪廓
findContours(canny_output, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
Mat drawing = Mat::zeros(canny_output.size(), CV_8UC3);
// 畫出輪廓
for (size_t i = 0; i < contours.size(); i++) {
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
drawContours(drawing, contours, (int)i, color, 2, 8, hierarchy, 0, Point());
}
namedWindow("Contours", WINDOW_AUTOSIZE);
imshow("Contours", drawing);
}
void findContours (
InputOutputArray image, // 輸入圖像
OutputArrayOfArrays contours, // 檢測到的輪廓
OutputArray hierarchy, // 可選的輸出向量
int mode, // 輪廓獲取模式 (RETR_EXTERNAL, RETR_LIST, RETR_CCOMP,RETR_TREE, RETR_FLOODFILL)
int method, // 輪廓近似算法 (CHAIN_APPROX_NONE, CHAIN_APPROX_SIMPLE, CHAIN_APPROX_TC89_L1, CHAIN_APPROX_TC89_KCOS)
Point offset = Point() // 輪廓偏移量
)
hierarchy 為可選的參數(shù),如果不選擇該參數(shù),則可得到 findContours 函數(shù)的第二種形式
void findContours (
InputOutputArray image,
OutputArrayOfArrays contours,
int mode,
int method,
Point offset = Point()
)
drawContours() 函數(shù)如下:
void drawContours (
InputOutputArray image, // 目標圖像
InputArrayOfArrays contours, // 所有的輸入輪廓
int contourIdx, //
const Scalar & color, // 輪廓顏色
int thickness = 1, // 輪廓線厚度
int lineType = LINE_8, //
InputArray hierarchy = noArray(), //
int maxLevel = INT_MAX, //
Point offset = Point() //
)
3、基于區(qū)域分割
3.1 分水嶺算法
分水嶺算法的基本原理為:將任意的灰度圖像視為地形圖表面,其中灰度值高的部分表示山峰和丘陵,而灰度值低的地方表示山谷。用不同顏色的水(標簽)填充每個獨立的山谷(局部最小值);隨著水平面的上升,來自不同山谷(具有不同顏色)的水將開始合并。為了避免出現(xiàn)這種情況,需要在水匯合的位置建造水壩;持續(xù)填充水和建造水壩,直到所有的山峰和丘陵都在水下。整個過程中建造的水壩將作為圖像分割的依據(jù)。
使用分水嶺算法執(zhí)行圖像分割操作時通常包含下列步驟:
(1) 將原圖轉換為灰度圖像
(2) 應用形態(tài)變換中的開運算和膨脹操作,去除圖像噪聲,獲得圖像邊緣信息,確定圖像背景
(3) 進行距離轉換,再進行閾值處理,確定圖像前景
(4) 確定圖像的未知區(qū)域(用圖像的背景減去前景剩余的部分)
(5) 標記背景圖像
(6) 執(zhí)行分水嶺算法分割圖像
Vec3b RandomColor(int value); //生成隨機顏色函數(shù)
int main(int argc, char* argv[])
{
Mat image = imread("test2.jpg"); //載入RGB彩色圖像
imshow("Source Image", image);
//灰度化,濾波,Canny邊緣檢測
Mat imageGray;
cvtColor(image, imageGray, CV_RGB2GRAY);//灰度轉換
GaussianBlur(imageGray, imageGray, Size(5, 5), 2); //高斯濾波
imshow("Gray Image", imageGray);
Canny(imageGray, imageGray, 80, 150);
imshow("Canny Image", imageGray);
//查找輪廓
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(imageGray, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());
Mat imageContours = Mat::zeros(image.size(), CV_8UC1); //輪廓
Mat marks(image.size(), CV_32S); //Opencv分水嶺第二個矩陣參數(shù)
marks = Scalar::all(0);
int index = 0;
int compCount = 0;
for (; index >= 0; index = hierarchy[index][0], compCount++)
{
//對marks進行標記,對不同區(qū)域的輪廓進行編號,相當于設置注水點,有多少輪廓,就有多少注水點
drawContours(marks, contours, index, Scalar::all(compCount + 1), 1, 8, hierarchy);
drawContours(imageContours, contours, index, Scalar(255), 1, 8, hierarchy);
}
//我們來看一下傳入的矩陣marks里是什么東西
Mat marksShows;
convertScaleAbs(marks, marksShows);
imshow("marksShow", marksShows);
imshow("輪廓", imageContours);
watershed(image, marks);
//我們再來看一下分水嶺算法之后的矩陣marks里是什么東西
Mat afterWatershed;
convertScaleAbs(marks, afterWatershed);
imshow("After Watershed", afterWatershed);
//對每一個區(qū)域進行顏色填充
Mat PerspectiveImage = Mat::zeros(image.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 (marks.at<int>(i, j) == -1)
{
PerspectiveImage.at<Vec3b>(i, j) = Vec3b(255, 255, 255);
}
else
{
PerspectiveImage.at<Vec3b>(i, j) = RandomColor(index);
}
}
}
imshow("After ColorFill", PerspectiveImage);
//分割并填充顏色的結果跟原始圖像融合
Mat wshed;
addWeighted(image, 0.4, PerspectiveImage, 0.6, 0, wshed);
imshow("AddWeighted Image", wshed);
waitKey();
}
Vec3b RandomColor(int value) //生成隨機顏色函數(shù)
{
value = value % 255; //生成0~255的隨機數(shù)
RNG rng;
int aa = rng.uniform(0, value);
int bb = rng.uniform(0, value);
int cc = rng.uniform(0, value);
return Vec3b(aa, bb, cc);
}
法二:
實現(xiàn)步驟:
1.輸入圖像
2.灰度化
3.二值化
4.執(zhí)行距離變換
5.歸一化
6.二值化
7.生成marker:通過findContours+drawContours來創(chuàng)建一個marker
8.將7生成的marker放入分水嶺函數(shù):watershed
9.給marker著色
10.輸出著色后的圖像
此算法關鍵點在于生成marker。生成marker之后其實已經(jīng)完成了算法,后面的著色只是為了讓輸出更加好看。
int main() {
Mat src = imread("E:\\la.jpg");//輸入原圖
if (src.empty()) {
//
cout<< "圖像為空";
return 0;
}
//圖像灰度化
Mat gray;
cvtColor(src, gray, COLOR_BGR2GRAY);
//圖像二值化
Mat binary;
threshold(gray, binary, 0, 255, THRESH_BINARY | cv::THRESH_OTSU);
imshow("binary", binary);
cout<< "binary....";
//執(zhí)行距離變換
Mat dist;
distanceTransform(binary, dist, DistanceTypes::DIST_L2, 3, CV_32F);
cout << "distanceTransform....";
normalize(dist, dist, 0.0, 1.0, NORM_MINMAX);//歸一化0~1之間
cout << "normalize....";
//重新二值化預值
threshold(dist, dist, 0.1, 1.0, THRESH_BINARY);
cout << "threshold....";
normalize(dist, dist, 0, 255, NORM_MINMAX);
cout << "normalize....";
dist.convertTo(dist, CV_8UC1);//
cout << "convertTo....";
//開始生成marker并繪制出來
vector<vector<Point>> contours;
vector<Vec4i> heri;
findContours(dist, contours, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
cout << "findContours....";
Mat marker = Mat::zeros(dist.size(), CV_32S);
for (size_t i = 0; i < contours.size(); i++) {
drawContours(marker, contours, i, Scalar(i + 1), -1, 8, heri, INT_MAX);
}
cout << "drawContours...." << contours.size();
circle(marker, Point(5, 5), 3, Scalar(255), -1);
watershed(src, marker);
cout << "watershed....";
// marker.convertTo(marker,CV_8UC1);//ps:此處需要注意(到這里實際上已經(jīng)完成了算法)。一旦不轉換就不能用imshow,一旦轉換了后面的marker著色就會出現(xiàn)異常
// imshow("marker",marker);
cout << "imshow(marker,marker);....";
//生成顏色數(shù)組
vector<Vec3b> colors;
for (size_t i = 0; i < contours.size(); i++) {
int r = theRNG().uniform(0, 255);
int g = theRNG().uniform(0, 255);
int b = theRNG().uniform(0, 255);
colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
}
//給marker著色
Mat finalResult = Mat::zeros(dist.size(), CV_8UC3);//三通道彩色圖像
int index = 0;
for (int row = 0; row < marker.rows; row++) {
for (int col = 0; col < marker.cols; col++) {
index = marker.at<int>(row, col);
if (index > 0 && index <= contours.size()) {
finalResult.at<Vec3b>(row, col) = colors[index - 1];
}
else {
finalResult.at<Vec3b>(row, col) = Vec3b(255, 255, 255);
}
}
}
imshow("finalResult", finalResult);
waitKey(0);
return 0;
}
distanceTransform()距離變換的定義是計算一個圖像中非零像素點到最近的零像素點的距離,也就是到零像素點的最短距離。即距離變換的定義是計算一個圖像中非零像素點到最近的零像素點的距離,也就是到零像素點的最短距離。
通常處理的是一個二值化的圖,所以求距離可以歸一化,距離(像素距離)單位為1。
void distanceTransform(
InputArray src,
OutputArray dst,
int distanceType,
int maskSize,
int dstType=CV_32F )
void distanceTransform(
InputArray src,
OutputArray dst,
OutputArray labels,
int distanceType,
int maskSize,
int labelType=DIST_LABEL_CCOMP )
src:源矩陣
dst:目標矩陣
distanceType:距離類型??梢缘念愋褪荂V_DIST_L1、CV_DIST_L2、CV_DIST_C,具體各類型的意義,請查閱相關算法文檔。
maskSize:距離變換運算時的掩碼大小。值可以是3、5或CV_DIST_MASK_PRECISE(5或CV_DIST_MASK_PRECISE只能用在第一個原型中)。當distanceType=CV_DIST_L1 或 CV_DIST_C時,maskSize只能為3。
dstType:輸出圖像(矩陣)的類型,可以是CV_8U 或 CV_32F。CV_8U只能用在第一個原型中,而且distanceType只能為CV_DIST_L1。
labels:輸出二維陣列標簽。
labelType:標簽數(shù)組類型??蛇x值為DIST_LABEL_CCOMP和DIST_LABEL_PIXEL
3.2 grab算法分割圖像
Graphcut是一種基于圖論的分割方法,在計算機視覺領域中應用于前景分割、醫(yī)學處理、紋理分割及立體視覺等方面,類似于PS中的摳圖功能?;緢D論的分割技術是圖像分割領域中新的研究熱點,該方法基于能量優(yōu)化算法,將圖像分割問題轉換為圖的最小割優(yōu)化問題。
Grabcut是Graphcut算法的改進,Graphcut是一種直接基于圖切算法的圖像分割技術,僅僅需要確認前景與背景輸入,該算法就可以完成背景與前景相似督導賦權圖,并通過最優(yōu)切割來實現(xiàn)圖像分割。Grabcut算法可以不需要用戶交互,僅僅需要輸入包含目標前景的區(qū)域就可以完成前景與前景的分離。
Graphcut的目標和背景模型是灰度直方圖,Grabcut采用的是RGB三通道混合高斯模型;Graphcut的能量最小化分割是能通過一次計算實現(xiàn)的,而Grabcut是根據(jù)分割模型參數(shù)更新完成學的學習過程;Graphcut需要用戶輸入前景與背景區(qū)域點集,而Grabcut只需要提供含有背景的區(qū)域像素集就可以完成分割。
算法流程:
(1)在圖片中定義含有(一個或多個)物體的矩形;
(2)矩形外的區(qū)域被自動認為是背景;
(3)對于用戶定義的矩形區(qū)域,可用背景中數(shù)據(jù)來區(qū)分是前景還是背景;
(4)用高斯混合模型(GMM)來對被禁和前景見面,并將未定義的像素標記為可能的前景或背景;
(5)圖像中的每一個像素都被看作通過通過虛擬變與周圍像素連接,而每條邊都有一個屬于前景或背景的概率這基于它和周圍像素顏色上的相似性;
(6)每一個像素(即算法中的節(jié)點)會與前一各前景或背景節(jié)點連接;
(7)在節(jié)點連接完成后,用圖論中最大流最小割的方法來分割。
int main()
{
Mat src = imread("E:\\la.jpg");
Rect rect(24, 24, 206, 218);//左上坐標(X,Y)和長寬
Mat result, bg, fg;
imshow("src", src);
grabCut(src, result, rect, bg, fg, 1, GC_INIT_WITH_RECT);
imshow("grab", result);
/*threshold(result, result, 2, 255, CV_THRESH_BINARY);
imshow("threshold", result);*/
compare(result, GC_PR_FGD, result, CMP_EQ);//result和GC_PR_FGD對應像素相等時,目標圖像該像素值置為255
imshow("result", result);
Mat foreground(src.size(), CV_8UC3, Scalar(255, 255, 255));
src.copyTo(foreground, result);//copyTo有兩種形式,此形式表示result為mask
imshow("foreground", foreground);
waitKey(0);
return 0;
}
grabCut(img, rect, mask,
bgdModel, fgdModel,
iterCount, mode = GC_EVAL)
void grabCut( InputArray img,
//輸入圖像,必須是8位3通道圖像,在處理過程中不會被修改
InputOutputArray mask,
//掩碼圖像,用來確定哪些區(qū)域是背景,前景,可能是背景,可能是前景等,mask既可以作為輸入也可以作為輸出。作為輸入時,mode要 選擇GC_INIT_WITH_MASK (=1);GCD_BGD (=0), 背景;GCD_FGD (=1),前景;GCD_PR_BGD (=2),可能是背景;GCD_PR_FGD(=3),可能是前景
Rect rect,
//包含前景的矩形,格式為(x, y, w, h)
InputOutputArray bgdModel,
//算法內部使用的數(shù)組,只需要創(chuàng)建大小為(1,65), 數(shù)據(jù)類型為np.float64的數(shù)組
InputOutputArray fgdModel,
//同上
int iterCount,
//算法迭代的次數(shù)
int mode = GC_EVAL
//用來指示grabCut函數(shù)進行什么操作
// GC_INIT_WITH_RECT (=0),用矩形窗初始化GrabCut;
// GC_INIT_WITH_MASK (=1),用掩碼圖像初始化GrabCut
);
法二:
//grabcut算法
bool setMouse = false; //判斷鼠標左鍵的狀態(tài)(up / down)
bool init;
Point pt;
Rect rect;
Mat srcImg, mask, bgModel, fgModel;
int numRun = 0;
void onMouse(int, int, int, int, void*);
void runGrabCut();
void showImage();
int main()
{
srcImg = imread("E:\\4.jpg");
if (srcImg.empty())
{
printf("could not load image...\n");
return -1;
}
imshow("源圖像", srcImg);
mask.create(srcImg.size(), CV_8U);
setMouseCallback("源圖像", onMouse, 0);
while (1)
{
char c = (char)waitKey(0);
if (c == ' ') {//選中矩形框后,按空格鍵執(zhí)行grabcut分割
runGrabCut();
numRun++;
showImage();
printf("current iteative times : %d\n", numRun);
}
if ((int)c == 27) {
break;
}
}
return 0;
}
void showImage()
{
Mat result, binmask;
binmask = mask & 1; //進一步掩膜
if (init) //進一步摳出無效區(qū)域。鼠標按下,init變?yōu)閒alse
{
srcImg.copyTo(result, binmask);
}
else
{
result = srcImg.clone();
}
rectangle(result, rect, Scalar(0, 0, 255), 2, 8);
imshow("源圖像", result);
}
void onMouse(int events, int x, int y, int flag, void*)
{
if (x < 0 || y < 0 || x > srcImg.cols || y > srcImg.rows) //無效區(qū)域
return;
if (events == EVENT_LBUTTONDOWN)
{
setMouse = true;
pt.x = x;
pt.y = y;
init = false;
}
else if (events == EVENT_MOUSEMOVE)//鼠標只要動,就執(zhí)行一次
{
if (setMouse == true) //鼠標左鍵按住,滑動
{
Point pt1;
pt1.x = x;
pt1.y = y;
rect = Rect(pt, pt1);//定義矩形區(qū)域
showImage();
mask.setTo(Scalar::all(GC_BGD));//背景
mask(rect).setTo(Scalar(GC_PR_FGD));//前景 //對rect內部設置為可能的前景,外部設置為背景
}
}
else if (events == EVENT_LBUTTONUP)
setMouse = false; //鼠標左鍵抬起
}
void runGrabCut()
{
if (init)//鼠標按下,init變?yōu)閒alse
grabCut(srcImg, mask, rect, bgModel, fgModel, 1);//第二次迭代,用mask初始化grabcut
else
{
grabCut(srcImg, mask, rect, bgModel, fgModel, 1, GC_INIT_WITH_RECT);//用矩形窗初始化GrabCut
init = true;
}
}
int event:
#define CV_EVENT_MOUSEMOVE 0 //滑動
#define CV_EVENT_LBUTTONDOWN 1 //左鍵點擊
#define CV_EVENT_RBUTTONDOWN 2 //右鍵點擊
#define CV_EVENT_MBUTTONDOWN 3 //中鍵點擊
#define CV_EVENT_LBUTTONUP 4 //左鍵放開
#define CV_EVENT_RBUTTONUP 5 //右鍵放開
#define CV_EVENT_MBUTTONUP 6 //中鍵放開
#define CV_EVENT_LBUTTONDBLCLK 7 //左鍵雙擊
#define CV_EVENT_RBUTTONDBLCLK 8 //右鍵雙擊
#define CV_EVENT_MBUTTONDBLCLK 9 //中鍵雙擊
int flags:
#define CV_EVENT_FLAG_LBUTTON 1 //左鍵拖曳
#define CV_EVENT_FLAG_RBUTTON 2 //右鍵拖曳
#define CV_EVENT_FLAG_MBUTTON 4 //中鍵拖曳
#define CV_EVENT_FLAG_CTRLKEY 8 //(8~15)按Ctrl不放事件
#define CV_EVENT_FLAG_SHIFTKEY 16 //(16~31)按Shift不放事件
#define CV_EVENT_FLAG_ALTKEY 32 //(32~39)按Alt不放事件
4、floodFill漫水填充分割
漫水填充法是一種用特定的顏色填充聯(lián)通區(qū)域,通過設置可連同像素的上下限以及聯(lián)通方式來達到不同的填充效果。
int main()
{
Mat src = imread("E:\\la.jpg");
imshow("【原始圖】", src);
Rect ccomp;
floodFill(src, Point(80, 200), Scalar(0, 0, 0), &ccomp, Scalar(20, 20, 20), Scalar(20, 20, 20));
imshow("【效果圖】", src);
waitKey(0);
return 0;
}
第二版本(功能更加強大)
//全局變量聲明部分
Mat g_srcImage, g_dstImage, g_grayImage, g_maskImage;//定義原始圖、目標圖、灰度圖、掩膜圖
int g_nFillMode = 1;//漫水填充的模式
int g_nLowDifference = 20, g_nUpDifference = 20;//負差最大值,正差最大值
int g_nConnectivity = 4;//表示floodFill函數(shù)標識符低八位的連通值
bool g_bIsColor = true;//是否為彩色圖的標識符布爾值
bool g_bUseMask = false;//是否顯示掩膜窗口的布爾值
int g_nNewMaskVal = 255;//新的重新繪制的像素值
//===============【onMouse()函數(shù)】=======================
static void onMouse(int event, int x, int y, int, void *) {
//若鼠標左鍵沒有按下,便返回
if (event != EVENT_LBUTTONDOWN)
return;
//-----------------【<1>調用floodFill函數(shù)之前的參數(shù)準備部分】-------------
Point seed = Point(x, y);
int LowDifference = g_nFillMode == 0 ? 0 : g_nLowDifference;
int UpDifference = g_nFillMode == 0 ? 0 : g_nUpDifference;
//標識符的0~7位為g_nConnectivity,8~15位為g_nNewMaskVal左移8位的值,16~23位為CV_FLOODFILL_FIXED_RANGE或者0
int flags = g_nConnectivity + (g_nNewMaskVal << 8) + (g_nFillMode == 1 ? FLOODFILL_FIXED_RANGE : 0);
//隨機生成BGR值
int b = (unsigned)theRNG() & 255;//隨機返回一個0~255之間的值
int g = (unsigned)theRNG() & 255;
int r = (unsigned)theRNG() & 255;
Rect ccomp;//定義重繪區(qū)域的最小邊界矩陣區(qū)域
Scalar newVal = g_bIsColor ? Scalar(b, g, r) : Scalar(r*0.299 + g * 0.587 + b * 0.114);
Mat dst = g_bIsColor ? g_dstImage : g_grayImage;//目標圖的賦值
int area;
//---------------------【<2>正式調用floodFill函數(shù)】------------------
if (g_bUseMask) {
threshold(g_maskImage, g_maskImage, 1, 128, THRESH_BINARY);
area = floodFill(dst, g_maskImage, seed, newVal, &ccomp, Scalar(LowDifference, LowDifference, LowDifference), Scalar(UpDifference, UpDifference, UpDifference), flags);
imshow("mask", g_maskImage);
}
else {
area = floodFill(dst, seed, newVal, &ccomp, Scalar(LowDifference, LowDifference, LowDifference), Scalar(UpDifference, UpDifference, UpDifference), flags);
}
imshow("效果圖", dst);
cout << area << " 個像素被重新繪制\n";
}
//main()函數(shù)
int main(int argc, char** argv) {
//載入原圖
g_srcImage = imread("test.jpg", 1);
if (!g_srcImage.data) {
printf("讀取g_srcImage錯誤!\n");
return false;
}
g_srcImage.copyTo(g_dstImage);//復制原圖到目標圖
cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);//轉為灰度圖到g_grayImage
g_maskImage.create(g_srcImage.rows + 2, g_srcImage.cols + 2, CV_8UC1);//用原圖尺寸初始化掩膜mask
namedWindow("效果圖", WINDOW_AUTOSIZE);
//創(chuàng)建Trackbar
createTrackbar("負差最大值", "效果圖", &g_nLowDifference, 255, 0);
createTrackbar("正差最大值", "效果圖", &g_nUpDifference, 255, 0);
//鼠標回調函數(shù)
setMouseCallback("效果圖", onMouse, 0);
//循環(huán)輪詢按鍵
while (1) {
//先顯示效果圖
imshow("效果圖", g_bIsColor ? g_dstImage : g_grayImage);
//獲取按鍵鍵盤
int c = waitKey(0);
//判斷ESC是否按下,按下退出
if (c == 27) {
cout << "程序退出........、\n";
break;
}
//根據(jù)按鍵不同進行不同的操作
switch ((char)c) {
//如果鍵盤1被按下,效果圖在灰度圖和彩色圖之間轉換
case '1':
if (g_bIsColor) {//若原來為彩色圖,轉換為灰度圖,并將掩膜mask所有元素設置為0
cout << "鍵盤‘1’按下,切換彩色/灰度模式,當前操作將【彩色模式】切換為【灰度模式】" << endl;
cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);
g_maskImage = Scalar::all(0);//將mask所有元素設置為0
g_bIsColor = false;
}
else {
cout << "鍵盤‘1’按下,切換彩色/灰度模式,當前操作將【灰度模式】切換為【彩色模式】" << endl;
g_srcImage.copyTo(g_dstImage);
g_maskImage = Scalar::all(0);
g_bIsColor = true;
}
case '2':
if (g_bUseMask) {
destroyWindow("mask");
g_bUseMask = false;
}
else {
namedWindow("mask", 0);
g_maskImage = Scalar::all(0);
imshow("mask", g_maskImage);
g_bUseMask = true;
}
break;
case '3'://如果鍵盤3被按下,恢復原始圖像
cout << "按下鍵盤‘3’,恢復原始圖像\n";
g_srcImage.copyTo(g_dstImage);
cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);
g_maskImage = Scalar::all(0);
break;
case '4':
cout << "鍵盤‘4’被按下,使用空范圍的漫水填充\n";
g_nFillMode = 0;
break;
case '5':
cout << "鍵盤‘5’被按下,使用漸變、固定范圍的漫水填充\n";
g_nFillMode = 1;
break;
case '6':
cout << "鍵盤‘6’被按下,使用漸變、浮動范圍的漫水填充\n";
g_nFillMode = 2;
break;
case '7':
cout << "鍵盤‘7’被按下,操作標識符的低八位使用4位的連接模式\n";
g_nConnectivity = 4;
break;
case '8':
cout << "鍵盤‘8’被按下,操作標識符的低八位使用8為的連接模式\n";
g_nConnectivity = 8;
break;
}
}
return 0;
}
鍵盤‘1’按下,切換彩色/灰度模式,當前操作將【彩色模式】切換為【灰度模式】
鍵盤‘1’按下,切換彩色/灰度模式,當前操作將【灰度模式】切換為【彩色模式】
按下鍵盤‘3’,恢復原始圖像
鍵盤‘4’被按下,使用空范圍的漫水填充
鍵盤‘5’被按下,使用漸變、固定范圍的漫水填充
鍵盤‘6’被按下,使用漸變、浮動范圍的漫水填充
鍵盤‘7’被按下,操作標識符的低八位使用4位的連接模式
鍵盤‘8’被按下,操作標識符的低八位使用8為的連接模式
void cvFloodFill (
IplImage * img, // 輸入圖像
CvPoint seedPoint, // 種子點
CvScalar newVal, // 像素點被染色的值
CvScalar loDiff = cvScalarAll(0), // 染色邊界判定
CvScalar upDiff = cvScalarAll(0), // 染色邊界判定
CvConnectedComp * comp = NULL, // 填充區(qū)域統(tǒng)計屬性
int flags = 4, // 連通性,相關性等參數(shù)設置。
CvArr * mask = NULL // 掩碼區(qū)域
);
image 【輸入/輸出】 1或者3通道、 8bit或者浮點圖像。僅當參數(shù)flags的FLOODFILL_MASK_ONLY標志位被設置時image不會被修改,否則會被修改。
mask 【輸入/輸出】 操作掩碼,必須為單通道、8bit,且比image寬2個像素、高2個像素。使用前必須先初始化。Flood-filling無法跨越mask中的非0像素。例如,一個邊緣檢測的結果可以作為mask來阻止邊緣填充。在輸出中,mask中與image中填充像素對應的像素點被設置為1,或者flags標志位中設置的值(詳見flags標志位的解釋)。此外,該函數(shù)還用1填充了mask的邊緣來簡化內部處理。因此,可以在多個調用中使用同一mask,以確保填充區(qū)域不會重疊。
seedPoint 起始像素點
newVal 重繪像素區(qū)域的新的填充值(顏色)
rect 可選輸出參數(shù),返回重繪區(qū)域的最小綁定矩形。
loDiff 當前選定像素與其連通區(qū)中相鄰像素中的一個像素,或者與加入該連通區(qū)的一個seedPoint像素,二者之間的最大下行差異值。
upDiff 當前選定像素與其連通區(qū)中相鄰像素中的一個像素,或者與加入該連通區(qū)的一個seedPoint像素,二者之間的最大上行差異值。
flags flags標志位是一個32bit的int類型數(shù)據(jù),其由3部分組成: 0-7bit表示鄰接性(4鄰接、8鄰接);8-15bit表示mask的填充顏色;16-31bit表示填充模式(詳見填充模式解釋)
5、彩色圖像分割
HSV是一種比較直觀的顏色模型,HSV 顏色空間的各通道分別表示色調(Hue)、飽和度(Saturation)和明度(Value),可以直觀地表達色彩的明暗、色調及鮮艷程度。由于RGB色彩控件是由三個通道來編碼顏色的,因此很難根據(jù)顏色來分割物體,而HSV中只有Hue一個通道表示顏色。
HSV 顏色空間可以用一個圓錐空間模型來描述。圓錐的頂點處 V=0,H 和 S 無定義,代表黑色;圓錐的頂面中心處V=max,S=0,H 無定義,代表白色。當 S=1, V=1 時,H 所代表的任何顏色被稱為純色;當 S=0 時,飽和度為 0,顏色最淺,最淺被描述為灰色,灰色的亮度由 V 決定,此時 H 無意義;當 V=0 時,顏色最暗,最暗被描述為黑色,此時 H 和 S 均無意義,無論如何取值均為黑色。
//輸入圖像
Mat img;
//灰度值歸一化
Mat bgr;
//HSV圖像
Mat hsv;
//色相
int hmin = 0;
int hmin_Max = 360;
int hmax = 180;
int hmax_Max = 180;
//飽和度
int smin = 0;
int smin_Max = 255;
int smax = 255;
int smax_Max = 255;
//亮度
int vmin = 106;
int vmin_Max = 255;
int vmax = 255;
int vmax_Max = 255;
//顯示原圖的窗口
string windowName = "原圖";
//輸出圖像的顯示窗口
string dstName = "dst";
//輸出圖像
Mat dst;
//回調函數(shù)
void callBack(int, void*)
{
//輸出圖像分配內存
dst = Mat::zeros(img.size(), img.type());
//掩碼
Mat mask;
inRange(hsv, Scalar(hmin, smin, vmin), Scalar(hmax, smax, vmax), mask);
//掩模到原圖的轉換
for (int r = 0; r < bgr.rows; r++)
{
for (int c = 0; c < bgr.cols; c++)
{
if (mask.at<uchar>(r, c) == 255)
{
dst.at<Vec3b>(r, c) = bgr.at<Vec3b>(r, c);
}
}
}
//輸出圖像
imshow(dstName, dst);
//保存圖像
//dst.convertTo(dst, CV_8UC3, 255.0, 0);
}
int main(int argc, char** argv)
{
//輸入圖像
img = imread("E:\\se.jpg");
if (!img.data || img.channels() != 3)
return -1;
imshow(windowName, img);
bgr = img.clone();
//顏色空間轉換
cvtColor(bgr, hsv, CV_BGR2HSV);
//cout << hsv << endl;
//定義輸出圖像的顯示窗口
namedWindow(dstName, WINDOW_GUI_EXPANDED);
//調節(jié)色相 H
createTrackbar("hmin", dstName, &hmin, hmin_Max, callBack);
createTrackbar("hmax", dstName, &hmax, hmax_Max, callBack);
//調節(jié)飽和度 S
createTrackbar("smin", dstName, &smin, smin_Max, callBack);
createTrackbar("smax", dstName, &smax, smax_Max, callBack);
//調節(jié)亮度 V
createTrackbar("vmin", dstName, &vmin, vmin_Max, callBack);
createTrackbar("vmax", dstName, &vmax, vmax_Max, callBack);
callBack(0, 0);
waitKey(0);
return 0;
}
void cv::inRange(
InputArray src,
InputArray lowerb,
InputArray upperb,
OutputArray dst
)
OpenCV中的函數(shù)inRange()用于將指定值范圍的像素選出來。如果像素的值滿足指定的范圍,則這個像素點的值被置為255,否則值被置為0。
src:輸入圖像,CV2常用Mat類型;
lowerb:lower boundary下限,scalar類型的像素值,單通道scalar取一個值就行,彩圖3通道scalar三個值;
upperb:上限,類型與lowerb同理;
dst:輸出圖像,尺寸與src一致,類型是CV_8U,但沒有指定通道數(shù)。
(int argc, char** argv)
{
//輸入圖像
img = imread(“E:\se.jpg”);
if (!img.data || img.channels() != 3)
return -1;
imshow(windowName, img);
bgr = img.clone();
//顏色空間轉換
cvtColor(bgr, hsv, CV_BGR2HSV);
//cout << hsv << endl;
//定義輸出圖像的顯示窗口
namedWindow(dstName, WINDOW_GUI_EXPANDED);
//調節(jié)色相 H
createTrackbar(“hmin”, dstName, &hmin, hmin_Max, callBack);
createTrackbar(“hmax”, dstName, &hmax, hmax_Max, callBack);
//調節(jié)飽和度 S
createTrackbar(“smin”, dstName, &smin, smin_Max, callBack);
createTrackbar(“smax”, dstName, &smax, smax_Max, callBack);
//調節(jié)亮度 V
createTrackbar(“vmin”, dstName, &vmin, vmin_Max, callBack);
createTrackbar(“vmax”, dstName, &vmax, vmax_Max, callBack);
callBack(0, 0);
waitKey(0);
return 0;
}文章來源:http://www.zghlxwxcb.cn/news/detail-506993.html
[外鏈圖片轉存中...(img-ccEX47mI-1665380014085)]
[外鏈圖片轉存中...(img-EOomyhXf-1665380014085)]
```txt
void cv::inRange(
InputArray src,
InputArray lowerb,
InputArray upperb,
OutputArray dst
)
OpenCV中的函數(shù)inRange()用于將指定值范圍的像素選出來。如果像素的值滿足指定的范圍,則這個像素點的值被置為255,否則值被置為0。
src:輸入圖像,CV2常用Mat類型;
lowerb:lower boundary下限,scalar類型的像素值,單通道scalar取一個值就行,彩圖3通道scalar三個值;
upperb:上限,類型與lowerb同理;
dst:輸出圖像,尺寸與src一致,類型是CV_8U,但沒有指定通道數(shù)。
部分參考來自OpenCV中Grabcut算法的具體使用_C 語言_腳本之家 (jb51.net)文章來源地址http://www.zghlxwxcb.cn/news/detail-506993.html
到了這里,關于OpenCV數(shù)字圖像處理基于C++:圖像分割的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!