jashliao 用 VC++ 實現 fanfuhan OpenCV 教學085 ~ opencv-085-視頻(影片)擷取圖像轉灰階之後使用shi-tomas檢測(goodFeaturesToTrack)進行檢測之後針對移動對象的KLT光流跟踪/5偵測算法並刪除靜止點與定位跟踪軌跡
jashliao 用 VC++ 實現 fanfuhan OpenCV 教學085 ~ opencv-085-視頻(影片)擷取圖像轉灰階之後使用shi-tomas檢測(goodFeaturesToTrack)進行檢測之後針對移動對象的KLT光流跟踪/5偵測算法並刪除靜止點與定位跟踪軌跡
資料來源: https://fanfuhan.github.io/
https://fanfuhan.github.io/2019/05/06/opencv-085/
GITHUB:https://github.com/jash-git/fanfuhan_ML_OpenCV
https://github.com/jash-git/jashliao-implements-FANFUHAN-OPENCV-with-VC
★前言:
★主題:
在84的知識點分享中我們已經可以跟踪到前後兩幀之前的位置移動,但是這個還不足夠,我們需要移位移動對像從初始到最終的完整可以檢測的運動軌跡,同時對一些靜止的角點進行刪除,所以我們需要對狀態為1的角點,計算它們之間的距離,只有dx + dy> 2(dx = abs(p1.x –p2.x),dy = abs(p1.y-p2) .y))的我們才對它進行保留跟踪。
★C++
// VC_FANFUHAN_OPENCV085.cpp : 定義主控台應用程式的進入點。 // /* // Debug | x32 通用屬性 | C/C++ | | 一般 | | 其他 Include 目錄 -> ..\..\opencv411_x64\include | | 連結器 | |一一般 | | 其他程式庫目錄 -> ..\..\opencv411_x64\lib | | |一輸入 | | 其他相依性 -> opencv_world411d.lib;%(AdditionalDependencies) // Releas | x64 組態屬性 | C/C++ | | 一般 | | 其他 Include 目錄 -> ..\..\opencv411_x64\include;%(AdditionalDependencies) | | 連結器 | |一般 | | 其他程式庫目錄 -> ..\..\opencv411_x64\lib;%(AdditionalDependencies) | | |一輸入 | | 其他相依性 -> opencv_world411.lib;%(AdditionalDependencies) */ #include "stdafx.h" #include <iostream> #include <opencv2/opencv.hpp> #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> using namespace std; using namespace cv; void blur_demo(Mat &image, Mat &sum); void edge_demo(Mat &image, Mat &sum); int getblockSum(Mat &sum, int x1, int y1, int x2, int y2, int i); void showHistogram(InputArray src, cv::String StrTitle); void backProjection_demo(Mat &mat, Mat &model); void blur3x3(Mat &src, Mat *det); void add_salt_pepper_noise(Mat &image); void add_gaussian_noise(Mat &image); void USMImage(Mat src, Mat &usm, float fltPar); void pyramid_up(Mat &image, vector<Mat> &pyramid_images, int level); void pyramid_down(vector<Mat> &pyramid_images); void laplaian_demo(vector<Mat> &pyramid_images, Mat &image); void connected_component_demo(Mat &image); void componentwithstats_demo(Mat &image); void contours_info(Mat &image, vector<vector<Point>> &pts); void contours_info(Mat &image, vector<vector<Point>> &pts, int threshold01, int threshold02); void open_demo(bool blnopen); void close_demo(); void sort_box(vector<Rect> &boxes); Mat get_template(Mat &binary, vector<Rect> &rects); void detect_defects(Mat &binary, vector<Rect> &rects, Mat &tpl, vector<Rect> &defects); void process_frame(Mat &image); vector<Point2f> featurePoints; vector<Scalar> color_lut; RNG rng; void draw_lines(Mat &image, vector<Point2f> pt1, vector<Point2f> pt2); void pause() { printf("Press Enter key to continue..."); fgetc(stdin); } int main() { VideoCapture capture("../../images/vtest.avi"); if (!capture.isOpened()) { cout << "could not open video.." << endl; pause(); return -1; } else { int fps = capture.get(CAP_PROP_FPS); int width = capture.get(CAP_PROP_FRAME_WIDTH); int height = capture.get(CAP_PROP_FRAME_HEIGHT); int num_of_frames = capture.get(CAP_PROP_FRAME_COUNT); printf("frame width: %d, frame height: %d, FPS : %d \n", width, height, fps); // 角点检测参数 double qualityLevel = 0.01; int minDistance = 10; int maxCorners = 100; // KLT光流跟踪参数 vector<Point2f> pts[2]; vector<uchar> status; vector<float> err; vector<Point2f> initPoints; // 读取第一帧及其角点 Mat old_frame, old_gray; capture.read(old_frame); cvtColor(old_frame, old_gray, COLOR_BGR2GRAY); goodFeaturesToTrack(old_gray, featurePoints, maxCorners, qualityLevel, minDistance, Mat()); pts[0].insert(pts[0].end(), featurePoints.begin(), featurePoints.end()); initPoints.insert(initPoints.end(), featurePoints.begin(), featurePoints.end()); VideoWriter writer("./085.mp4", VideoWriter::fourcc('D', 'I', 'V', 'X'), 10, Size(width * 2, height), true); Mat result = Mat::zeros(Size(width * 2, height), CV_8UC3); Rect roi(0, 0, width, height); Mat gray, frame; while (true) { bool ret = capture.read(frame); if (!ret) break; imshow("frame", frame); roi.x = 0; frame.copyTo(result(roi)); cvtColor(frame, gray, COLOR_BGR2GRAY); // 计算光流 calcOpticalFlowPyrLK(old_gray, gray, pts[0], pts[1], status, err, Size(31, 31)); size_t i, k; for (int i = k = 0; i < pts[1].size(); ++i) { // 距离与状态测量,删除静止点 double dist = abs(pts[0][i].x - pts[1][i].x) + abs(pts[0][i].y - pts[1][i].y); if (status[i] && dist > 2) { pts[0][k] = pts[0][i]; initPoints[k] = initPoints[i]; pts[1][k++] = pts[1][i]; circle(frame, pts[1][i], 4, Scalar(0, 255, 0), -1); } } // resize 有用特征点 pts[0].resize(k); pts[1].resize(k); initPoints.resize(k); // 绘制跟踪轨迹 draw_lines(frame, initPoints, pts[1]); imshow("result", frame); roi.x = width; frame.copyTo(result(roi)); char c = waitKey(50); if (c == 27) break; // 更新old std::swap(pts[1], pts[0]); cv::swap(old_gray, gray); // 重新初始化角点 if (initPoints.size() < 40) { goodFeaturesToTrack(old_gray, featurePoints, maxCorners, qualityLevel, minDistance, Mat()); pts[0].insert(pts[0].end(), featurePoints.begin(), featurePoints.end()); initPoints.insert(initPoints.end(), featurePoints.begin(), featurePoints.end()); } writer.write(result); } } return 0; } void draw_lines(Mat &image, vector<Point2f> pt1, vector<Point2f> pt2) { if (color_lut.size() < pt1.size()) { for (size_t i = 0; i < pt1.size(); ++i) { int b = rng.uniform(0, 255); int g = rng.uniform(0, 255); int r = rng.uniform(0, 255); Scalar color(b, g, r); color_lut.push_back(color); } } for (size_t j = 0; j < pt1.size(); ++j) { line(image, pt1[j], pt2[j], color_lut[j], 2); } } void process_frame(Mat &image) { // Detector parameters int maxCorners = 100; double quality_level = 0.01; double minDistance = 0.04; // detecting corners Mat gray, dst; cvtColor(image, gray, COLOR_BGR2GRAY); vector<Point2f> corners; /* void cv::goodFeaturesToTrack( InputArray image, OutputArray corners, int maxCorners, double qualityLevel, double minDistance, InputArray mask = noArray(), int blockSize = 3, bool useHarrisDetector = false, double k = 0.04 ) src單通道輸入圖像,八位或者浮點數 corners是輸出的關鍵點坐標集合 maxCorners表示最大返回關鍵點數目 qualityLevel表示拒絕的關鍵點 R < qualityLevel × max response將會被直接丟棄 minDistance 表示兩個關鍵點之間的最短距離 mask 表示mask區域,如果有表明只對mask區域做計算 blockSize 計算梯度與微分的窗口區域 useHarrisDetector 表示是否使用harris角點檢測,默認是false 為shi-tomas k = 0.04默認值,當useHarrisDetector為ture時候起作用 */ goodFeaturesToTrack(gray, corners, maxCorners, quality_level, minDistance, Mat(), 3, false); // detect sub-pixel 亚像素检测 Size winSize = Size(5, 5); Size zeroZone = Size(-1, -1); TermCriteria criteria = TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 40, 0.001); /* void cv::cornerSubPix( InputArray image, InputOutputArray corners, Size winSize, Size zeroZone, TermCriteria criteria ) image单通道输入图像,八位或者浮点数 corners是输入输出的关键点坐标集合 winSize表示插值计算时候窗口大小 zeroZone表示搜索区域中间的dead region边长的一半,有时用于避免自相关矩阵的奇异性。如果值设为(-1,-1)则表示没有这个区域。 criteria角点精准化迭代过程的终止条件 */ cornerSubPix(gray, corners, winSize, zeroZone, criteria); // drawing corner RNG rng; for (int i = 0; i < corners.size(); ++i) { int b = rng.uniform(0, 255); int g = rng.uniform(0, 255); int r = rng.uniform(0, 255); circle(image, corners[i], 5, Scalar(b, g, r), 3); } } void detect_defects(Mat &binary, vector<Rect> &rects, Mat &tpl, vector<Rect> &defects) { int height = tpl.rows; int width = tpl.cols; int index = 1; int size = rects.size(); // 发现缺失 for (int i = 0; i < size; ++i) { Mat roi = binary(rects[i]); resize(roi, roi, tpl.size());//調整待測物的大小與模板一致 Mat mask; subtract(tpl, roi, mask);//圖像相減 Mat se = getStructuringElement(MORPH_RECT, Size(5, 5)); morphologyEx(mask, mask, MORPH_OPEN, se);//開運算-藉此去白色雜訊 threshold(mask, mask, 0, 255, THRESH_BINARY); int count = 0; for (int row = 0; row < height; ++row) { for (int col = 0; col < width; ++col) { int pv = mask.at<uchar>(row, col); if (pv == 255) { ++count; } } } if (count > 0) { defects.push_back(rects[i]); } } } Mat get_template(Mat &binary, vector<Rect> &rects) //以範圍最大為良品基準 { return binary(rects[0]); } void sort_box(vector<Rect> &boxes) { int size = boxes.size(); for (int i = 0; i < size - 1; ++i) { for (int j = i; j < size; ++j) { if (boxes[j].y < boxes[i].y) { Rect tmp = boxes[i]; boxes[i] = boxes[j]; boxes[j] = tmp; } } } } void close_demo() { //讀取圖像 Mat src = imread("../images/morph3.png"); imshow("close_demo_input", src); //二值圖像 Mat gray, binary; cvtColor(src, gray, COLOR_BGR2GRAY); threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU); imshow("close_demo_binary", binary); //閉操作// //Mat se = getStructuringElement(MORPH_ELLIPSE, Size(30, 30), Point(-1, -1)); //Mat se = getStructuringElement(MORPH_RECT, Size(30, 30), Point(-1 , -1)); Mat se = getStructuringElement(MORPH_RECT, Size(35, 35), Point(-1, -1)); morphologyEx(binary, binary, MORPH_CLOSE, se); imshow("close_demo rect=35,35 ", binary); } void open_demo(bool blnopen) { //讀取圖像 Mat src = imread("../images/fill.png"); imshow("open_demo_input", src); //二值圖像 Mat gray, binary; cvtColor(src, gray, COLOR_BGR2GRAY); if (blnopen) { threshold(gray, binary, 0, 255, THRESH_BINARY_INV | THRESH_OTSU); } else { threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU); } imshow("open_demo_binary", binary); //開操作 -去除黑色雜訊 強化/獲取 連通元件(mask) 保留長(x=25)底線(y=1) Mat se = getStructuringElement(MORPH_RECT, Size(25, 1), Point(-1, -1)); if (blnopen) { morphologyEx(binary, binary, MORPH_OPEN, se); } else { morphologyEx(binary, binary, MORPH_CLOSE, se);// threshold(binary, binary, 0, 255, THRESH_BINARY_INV | THRESH_OTSU); } imshow("open_op", binary); //繪製填空位置 vector < vector <Point>> contours; vector <Vec4i> hierarhy; findContours(binary, contours, hierarhy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(-1, -1)); for (size_t t = 0; t < contours.size(); t++) { Rect roi = boundingRect(contours[t]); roi.y = roi.y - 10; roi.height = 12; rectangle(src, roi, Scalar(0, 0, 255)); } //顯示結果 imshow("open_demo", src); } void contours_info(Mat &image, vector<vector<Point>> &pts)//目標物為同類型(顏色) ~ 抓取輪廓(findContours)函數 { // 去噪声与二值化 //彩色轉二值化步驟(SOP) 彩色 -> 高斯模糊(去雜訊) -> 轉灰階 -> 二值化 Mat dst, gray, binary00; GaussianBlur(image, dst, Size(3, 3), 0, 0); cvtColor(dst, gray, COLOR_BGR2GRAY); threshold(gray, binary00, 0, 255, THRESH_BINARY | THRESH_OTSU); imshow("binary00", binary00); vector<Vec4i> hierarchy00; Scalar color = Scalar(255, 0, 0); /* void findContours(InputOutputArray image,OutputArrayOfArrays contours,OutputArray hierarchy,int mode,int method,Point offset = Point() ) 各個參數詳解如下: image表示輸入圖像,必須是二值圖像,二值圖像可以threshold輸出、Canny輸出、inRange輸出、自適應閾值輸出等。 contours獲取的輪廓,每個輪廓是一系列的點集合 hierarchy輪廓的層次信息,每個輪廓有四個相關信息,分別是同層下一個、前一個、第一個子節點、父節點 mode 表示輪廓尋找時候的拓撲結搆返回 -RETR_EXTERNAL表示只返回最外層輪廓 -RETR_TREE表示返回輪廓樹結搆 method表示輪廓點集合取得是基於什么算法,常見的是基於CHAIN_APPROX_SIMPLE鏈式編碼方法 */ findContours(binary00, pts, hierarchy00, RETR_TREE, CHAIN_APPROX_SIMPLE, Point()); } void contours_info(Mat &image, vector<vector<Point>> &pts, int threshold01, int threshold02)//目標物非同類型(顏色) ~ 抓取輪廓(findContours)函數 { Mat dst, gray, binary01; //彩色轉二值化步驟(直接使用Canny) /* void Canny(InputArray image, OutputArray edges, double threshold1, double threshold2, int apertureSize=3, bool L2gradient=false ) image, edges:輸入和輸出的圖片。 threshold1, threshold2:用來區分 strong edge 和 weak edge,範圍都是 0 ~ 255,會在實作過程中進一步討論,通常選擇 threshold2 / threshold1 = 1/2 ~ 1/3,例如 (70, 140), (70, 210) apertureSize:用來計算梯度的 kernel size,也就是 Sobel 的 ksize L2gradient:選擇要用 L1 norm(絕對值平均)還是 L2 norm(平方根)當作梯度的大小。預設是用 L1 norm */ Canny(image, binary01, threshold01, threshold02); // 膨胀 /* OpenCV提供getStructuringElement()讓我們得到要進行侵蝕或膨脹的模板 Mat getStructuringElement(int shape, Size ksize, Point anchor=Point(-1,-1)) shape:模板形狀,有MORPH_RECT、MORPH_ELLIPSE、MORPH_CROSS三種可選。 ksize:模板尺寸。 */ Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1)); /* OpenCV膨脹 dilate(const Mat &src, Mat &dst, Mat kernel, Point anchor=Point(-1,-1), int iterations=1) src:輸入圖,可以多通道,深度可為CV_8U、CV_16U、CV_16S、CV_32F或CV_64F。 dst:輸出圖,和輸入圖尺寸、型態相同。 kernel:結構元素,如果kernel=Mat()則為預設的3×3矩形,越大膨脹效果越明顯。 anchor:原點位置,預設為結構元素的中央。 iterations:執行次數,預設為1次,執行越多次膨脹效果越明顯。 */ dilate(binary01, binary01, k); imshow("binary01", binary01); vector<Vec4i> hierarchy01; Scalar color = Scalar(255, 0, 0); findContours(binary01, pts, hierarchy01, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point()); } void componentwithstats_demo(Mat &image)//八方鍊碼:元件標記/尋找/計算(計數)/參數:中心位置、起始座標、長、寬、面積,取得分類的所需資訊作業 + 繪製各元件的外矩形 { // extract labels //彩色轉二值化步驟(SOP) 彩色 -> 高斯模糊(去雜訊) -> 轉灰階 -> 二值化 Mat gray, binary; GaussianBlur(image, image, Size(3, 3), 0); cvtColor(image, gray, COLOR_BGR2GRAY); threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU); imshow("input_binary", binary); showHistogram(binary, "Histogram_input_binary"); Mat labels = Mat::zeros(image.size(), CV_32S); Mat stats, centroids; int num_labels = connectedComponentsWithStats(binary, labels, stats, centroids, 8, 4); cout << "total labels : " << num_labels - 1 << endl; vector<Vec3b> colors(num_labels); // 背景颜色 colors[0] = Vec3b(0, 0, 0); // 目标颜色 RNG rng; for (int i = 1; i < num_labels; ++i) { colors[i] = Vec3b(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256)); } // 抽取统计信息 Mat dst = image.clone(); for (int i = 1; i < num_labels; ++i) { // 中心位置 int cx = centroids.at<double>(i, 0); int cy = centroids.at<double>(i, 1); // 统计信息 int x = stats.at<int>(i, CC_STAT_LEFT); int y = stats.at<int>(i, CC_STAT_TOP); int w = stats.at<int>(i, CC_STAT_WIDTH); int h = stats.at<int>(i, CC_STAT_HEIGHT); int area = stats.at<int>(i, CC_STAT_AREA); // 中心位置绘制 circle(dst, Point(cx, cy), 2, Scalar(0, 255, 0), 2); // 外接矩形 Rect rect(x, y, w, h); rectangle(dst, rect, colors[i]); putText(dst, format("num:%d", i), Point(x, y), FONT_HERSHEY_SIMPLEX, .5, Scalar(0, 0, 255), 1); printf("num : %d, rice area : %d\n", i, area); } imshow("result", dst); } void connected_component_demo(Mat &image) //八方鍊碼 元件 計數(計算) 數量 / 標色 { // extract labels Mat gray, binary; //彩色轉二值化步驟(SOP) 彩色 -> 高斯模糊(去雜訊) -> 轉灰階 -> 二值化 GaussianBlur(image, image, Size(3, 3), 0); cvtColor(image, gray, COLOR_BGR2GRAY); threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU); imshow("input_binary", binary); showHistogram(binary, "Histogram_input_binary"); //計算(計數) 元件(mask) 數量 和 所需元素顏色數量陣列 /* 参数介绍如下: image:也就是输入图像,必须是二值图,即8位单通道图像。(因此输入图像必须先进行二值化处理才能被这个函数接受) 返回值: num_labels:所有连通域的数目 labels:图像上每一像素的标记,用数字1、2、3…表示(不同的数字表示不同的连通域) */ Mat labels = Mat::zeros(image.size(), CV_32S);//背景也會被算一個區域 int num_labels = connectedComponents(binary, labels, 8, CV_32S);//數量 cout << "total labels : " << num_labels - 1 << endl; vector<Vec3b> colors(num_labels); // 背景颜色 colors[0] = Vec3b(0, 0, 0); // 目标颜色 RNG rng; for (int i = 1; i < num_labels; ++i) { colors[i] = Vec3b(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256)); } // 给结果着色 Mat dst = Mat::zeros(image.size(), image.type()); for (int row = 0; row < image.rows; ++row) { for (int col = 0; col < image.cols; ++col) { int label = labels.at<int>(row, col); if (label == 0) continue; dst.at<Vec3b>(row, col) = colors[label]; } } imshow("result", dst); } void laplaian_demo(vector<Mat> &pyramid_images, Mat &image)//拉普拉斯金字塔 { for (int i = pyramid_images.size() - 1; i > -1; --i) { Mat dst; if (i - 1 < 0) { pyrUp(pyramid_images[i], dst, image.size()); subtract(image, dst, dst);//圖像相減 dst = dst + Scalar(127, 127, 127); //调亮度, 实际中不能这么用 imshow(format("laplaian_layer_%d", i), dst); } else { pyrUp(pyramid_images[i], dst, pyramid_images[i - 1].size()); subtract(pyramid_images[i - 1], dst, dst);//圖像相減 dst = dst + Scalar(127, 127, 127); //調亮度, 实际中不能这么用 imshow(format("laplaian_layer_%d", i), dst); } } } void pyramid_down(vector<Mat> &pyramid_images)//高斯金字塔01 { for (int i = pyramid_images.size() - 1; i > -1; --i) { Mat dst; /* pyrUp(tmp, dst, Size(tmp.cols * 2, tmp.rows * 2)) tmp: 當前影象, 初始化為原影象 src 。 dst : 目的影象(顯示影象,為輸入影象的兩倍) Size(tmp.cols * 2, tmp.rows * 2) : 目的影象大小, 既然我們是向上取樣, pyrUp 期待一個兩倍於輸入影象(tmp)的大小。 */ pyrUp(pyramid_images[i], dst); imshow(format("pyramid_down_%d", i), dst); } } void pyramid_up(Mat &image, vector<Mat> &pyramid_images, int level)//高斯金字塔02 { Mat temp = image.clone(); Mat dst; for (int i = 0; i < level; ++i) { /* pyrDown( tmp, dst, Size( tmp.cols/2, tmp.rows/2 )) tmp: 當前影象, 初始化為原影象 src 。 dst: 目的影象( 顯示影象,為輸入影象的一半) Size( tmp.cols/2, tmp.rows/2 ) :目的影象大小, 既然我們是向下取樣, pyrDown 期待一個一半於輸入影象( tmp)的大小。 注意輸入影象的大小(在兩個方向)必須是2的冥,否則,將會顯示錯誤。 最後,將輸入影象 tmp 更新為當前顯示影象, 這樣後續操作將作用於更新後的影象。 tmp = dst; */ pyrDown(temp, dst); imshow(format("pyramid_up_%d", i), dst); temp = dst.clone(); pyramid_images.push_back(temp); } } void USMImage(Mat src, Mat &usm, float fltPar)//圖像銳化增强演算法(USM) { Mat blur_img; /* USM銳化公式表示如下: (源圖像– w*高斯模糊)/(1-w);其中w表示權重(0.1~0.9),默認為0.6 OpenCV中的代碼實現步驟 – 高斯模糊 – 權重疊加 – 輸出結果 */ GaussianBlur(src, blur_img, Size(0, 0), 25); addWeighted(src, (1 + fltPar), blur_img, (fltPar*-1), 0, usm);//原圖 : 模糊圖片= 1.5 : -0.5 的比例進行混合 imshow("usm", usm); showHistogram(usm, "Histogram_input_usm"); } void blur_demo(Mat &image, Mat &sum) { int w = image.cols; int h = image.rows; Mat result = Mat::zeros(image.size(), image.type()); int x2 = 0, y2 = 0; int x1 = 0, y1 = 0; int ksize = 5; int radius = ksize / 2; int ch = image.channels(); int cx = 0, cy = 0; for (int row = 0; row < h + radius; row++) { y2 = (row + 1)>h ? h : (row + 1); y1 = (row - ksize) < 0 ? 0 : (row - ksize); for (int col = 0; col < w + radius; col++) { x2 = (col + 1)>w ? w : (col + 1); x1 = (col - ksize) < 0 ? 0 : (col - ksize); cx = (col - radius) < 0 ? 0 : col - radius; cy = (row - radius) < 0 ? 0 : row - radius; int num = (x2 - x1)*(y2 - y1); for (int i = 0; i < ch; i++) { // 积分图查找和表,计算卷积 int s = getblockSum(sum, x1, y1, x2, y2, i); result.at<Vec3b>(cy, cx)[i] = saturate_cast<uchar>(s / num); } } } imshow("blur_demo", result); } /** * 3x3 sobel 垂直边缘检测演示 */ void edge_demo(Mat &image, Mat &sum) { int w = image.cols; int h = image.rows; Mat result = Mat::zeros(image.size(), CV_32SC3); int x2 = 0, y2 = 0; int x1 = 0, y1 = 0; int ksize = 3; // 算子大小,可以修改,越大边缘效应越明显 int radius = ksize / 2; int ch = image.channels(); int cx = 0, cy = 0; for (int row = 0; row < h + radius; row++) { y2 = (row + 1)>h ? h : (row + 1); y1 = (row - ksize) < 0 ? 0 : (row - ksize); for (int col = 0; col < w + radius; col++) { x2 = (col + 1)>w ? w : (col + 1); x1 = (col - ksize) < 0 ? 0 : (col - ksize); cx = (col - radius) < 0 ? 0 : col - radius; cy = (row - radius) < 0 ? 0 : row - radius; int num = (x2 - x1)*(y2 - y1); for (int i = 0; i < ch; i++) { // 积分图查找和表,计算卷积 int s1 = getblockSum(sum, x1, y1, cx, y2, i); int s2 = getblockSum(sum, cx, y1, x2, y2, i); result.at<Vec3i>(cy, cx)[i] = saturate_cast<int>(s2 - s1); } } } Mat dst, gray; convertScaleAbs(result, dst); normalize(dst, dst, 0, 255, NORM_MINMAX); cvtColor(dst, gray, COLOR_BGR2GRAY); imshow("edge_demo", gray); } int getblockSum(Mat &sum, int x1, int y1, int x2, int y2, int i) { int tl = sum.at<Vec3i>(y1, x1)[i]; int tr = sum.at<Vec3i>(y2, x1)[i]; int bl = sum.at<Vec3i>(y1, x2)[i]; int br = sum.at<Vec3i>(y2, x2)[i]; int s = (br - bl - tr + tl); return s; } void add_gaussian_noise(Mat &image)//高斯雜訊 { Mat noise = Mat::zeros(image.size(), image.type()); // 产生高斯噪声 randn(noise, (15, 15, 15), (30, 30, 30)); Mat dst; add(image, noise, dst); image = dst.clone();//dst.copyTo(image);//圖像複製 //imshow("gaussian_noise", dst); } void add_salt_pepper_noise(Mat &image)//白雜訊 { // 随机数产生器 RNG rng(12345); for (int i = 0; i < 1000; ++i) { int x = rng.uniform(0, image.rows); int y = rng.uniform(0, image.cols); if (i % 2 == 1) { image.at<Vec3b>(y, x) = Vec3b(255, 255, 255); } else { image.at<Vec3b>(y, x) = Vec3b(0, 0, 0); } } //imshow("saltp_epper", image); } void blur3x3(Mat &src, Mat *det) { // 3x3 均值模糊,自定义版本实现 for (int row = 1; row < src.rows - 1; row++) { for (int col = 1; col < src.cols - 1; col++) { Vec3b p1 = src.at<Vec3b>(row - 1, col - 1); Vec3b p2 = src.at<Vec3b>(row - 1, col); Vec3b p3 = src.at<Vec3b>(row - 1, col + 1); Vec3b p4 = src.at<Vec3b>(row, col - 1); Vec3b p5 = src.at<Vec3b>(row, col); Vec3b p6 = src.at<Vec3b>(row, col + 1); Vec3b p7 = src.at<Vec3b>(row + 1, col - 1); Vec3b p8 = src.at<Vec3b>(row + 1, col); Vec3b p9 = src.at<Vec3b>(row + 1, col + 1); int b = p1[0] + p2[0] + p3[0] + p4[0] + p5[0] + p6[0] + p7[0] + p8[0] + p9[0]; int g = p1[1] + p2[1] + p3[1] + p4[1] + p5[1] + p6[1] + p7[1] + p8[1] + p9[1]; int r = p1[2] + p2[2] + p3[2] + p4[2] + p5[2] + p6[2] + p7[2] + p8[2] + p9[2]; det->at<Vec3b>(row, col)[0] = saturate_cast<uchar>(b / 9); det->at<Vec3b>(row, col)[1] = saturate_cast<uchar>(g / 9); det->at<Vec3b>(row, col)[2] = saturate_cast<uchar>(r / 9); } } } void backProjection_demo(Mat &image, Mat &model)//反向投影 { Mat image_hsv, model_hsv; cvtColor(image, image_hsv, COLOR_BGR2HSV);//彩色轉HSV cvtColor(model, model_hsv, COLOR_BGR2HSV); // 定义直方图参数与属性 int h_bins = 32, s_bins = 32; int histSize[] = { h_bins, s_bins };//要切分的像素強度值範圍,預設為256。每個channel皆可指定一個範圍。例如,[32,32,32] 表示RGB三個channels皆切分為32區段 float h_ranges[] = { 0, 180 }, s_ranges[] = { 0, 256 }; const float* ranges[] = { h_ranges, s_ranges }; int channels[] = { 0, 1 }; Mat roiHist;//計算ROI的直方圖 calcHist(&model_hsv, 1, channels, Mat(), roiHist, 2, histSize, ranges); normalize(roiHist, roiHist, 0, 255, NORM_MINMAX, -1, Mat()); Mat roiproj, backproj; calcBackProject(&image_hsv, 1, channels, roiHist, roiproj, ranges);//使用反向投影 產生ROI(前景)的mask bitwise_not(roiproj, backproj);//產生背景的mask imshow("ROIProj", roiproj); imshow("BackProj", backproj); } void showHistogram(InputArray src, cv::String StrTitle)//直方圖 { bool blnGray = false; if (src.channels() == 1) { blnGray = true; } // 三通道/單通道 直方圖 紀錄陣列 vector<Mat> bgr_plane; vector<Mat> gray_plane; // 定义参数变量 const int channels[1] = { 0 }; const int bins[1] = { 256 }; float hranges[2] = { 0, 255 }; const float *ranges[1] = { hranges }; Mat b_hist, g_hist, r_hist, hist; // 计算三通道直方图 /* 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 ); 1.輸入的圖像數組 2.輸入數組的個數 3.通道數 4.掩碼 5.直方圖 6.直方圖維度 7.直方圖每個維度的尺寸數組 8.每一維數組的範圍 9.直方圖是否是均勻 10.配置階段不清零 */ if (blnGray) { split(src, gray_plane); calcHist(&gray_plane[0], 1, 0, Mat(), hist, 1, bins, ranges); } else { split(src, bgr_plane); calcHist(&bgr_plane[0], 1, 0, Mat(), b_hist, 1, bins, ranges); calcHist(&bgr_plane[1], 1, 0, Mat(), g_hist, 1, bins, ranges); calcHist(&bgr_plane[2], 1, 0, Mat(), r_hist, 1, bins, ranges); } /* * 显示直方图 */ int hist_w = 512; int hist_h = 400; int bin_w = cvRound((double)hist_w / bins[0]); Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3); // 归一化直方图数据 if (blnGray) { normalize(hist, hist, 0, histImage.rows, NORM_MINMAX, -1); } else { normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1); normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1); normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1); } // 绘制直方图曲线 for (int i = 1; i < bins[0]; ++i) { if (blnGray) { line(histImage, Point(bin_w * (i - 1), hist_h - cvRound(hist.at<float>(i - 1))), Point(bin_w * (i), hist_h - cvRound(hist.at<float>(i))), Scalar(255, 255, 255), 2, 8, 0); } else { line(histImage, Point(bin_w * (i - 1), hist_h - cvRound(b_hist.at<float>(i - 1))), Point(bin_w * (i), hist_h - cvRound(b_hist.at<float>(i))), Scalar(255, 0, 0), 2, 8, 0); line(histImage, Point(bin_w * (i - 1), hist_h - cvRound(g_hist.at<float>(i - 1))), Point(bin_w * (i), hist_h - cvRound(g_hist.at<float>(i))), Scalar(0, 255, 0), 2, 8, 0); line(histImage, Point(bin_w * (i - 1), hist_h - cvRound(r_hist.at<float>(i - 1))), Point(bin_w * (i), hist_h - cvRound(r_hist.at<float>(i))), Scalar(0, 0, 255), 2, 8, 0); } } imshow(StrTitle, histImage); }
★Python
import numpy as np import cv2 as cv cap = cv.VideoCapture('D:\\code-workspace\\Clion-workspace\\learnOpencv\\images\\vtest.avi') # 角点检测参数 feature_params = dict(maxCorners=100, qualityLevel=0.01, minDistance=10, blockSize=3) # KLT光流参数 lk_params = dict(winSize=(31, 31), maxLevel=3, criteria=(cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 30, 0.01)) # 随机颜色 color = np.random.randint(0,255,(100,3)) # 读取第一帧 ret, old_frame = cap.read() old_gray = cv.cvtColor(old_frame, cv.COLOR_BGR2GRAY) p0 = cv.goodFeaturesToTrack(old_gray, mask=None, **feature_params) initPoints = p0.copy() # 光流跟踪 while True: ret, frame = cap.read() if ret is False: break cv.imshow('frame',frame) gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY) # 计算光流 p1, st, err = cv.calcOpticalFlowPyrLK(old_gray, gray, p0, None, **lk_params) # 距离与状态测量,删除静止点 k=0 for i, (new, old) in enumerate(zip(p1,p0)): a,b = new.ravel() c,d = old.ravel() dist = abs(a-c) + abs(b-d) if st[i] == 1 and dist > 2: p0[k] = p0[i] initPoints[k] = initPoints[i] p1[k] = p1[i] k = k+1 frame = cv.circle(frame,(a,b),5,color[i].tolist(),-1) # 取有用特征点 p0 = p0[:k] p1 = p1[:k] initPoints = initPoints[:k] # 绘制跟踪线 for i, (old, new) in enumerate(zip(initPoints,p1)): a,b = old.ravel() c,d = new.ravel() frame = cv.line(frame, (a,b),(c,d), (0,255,0), 2) cv.imshow('result',frame) k = cv.waitKey(30) & 0xff if k == 27: break # 更新old old_gray = gray.copy() p0, _ = p1, p0 # 重新初始化角点 if len(initPoints) < 40: p0 = cv.goodFeaturesToTrack(old_gray, mask=None, **feature_params) initPoints = p0.copy() cv.destroyAllWindows() cap.release()
★結果圖:
★延伸說明/重點回顧: