jashliao 用 VC++ 實現 fanfuhan OpenCV 教學032 ~ opencv-032-圖像梯度之robert算子與prewitt算子與 Sobel比較 (邊緣檢測) [彩色/灰階 圖像 robert & prewitt運算]
jashliao 用 VC++ 實現 fanfuhan OpenCV 教學032 ~ opencv-032-圖像梯度之robert算子與prewitt算子與 Sobel比較 (邊緣檢測) [彩色/灰階 圖像 robert & prewitt運算]
資料來源: https://fanfuhan.github.io/
https://fanfuhan.github.io/2019/04/09/opencv-032/
GITHUB:https://github.com/jash-git/fanfuhan_ML_OpenCV
https://github.com/jash-git/jashliao-implements-FANFUHAN-OPENCV-with-VC
★前言:
★主題:
Roberts 算子,又稱羅伯茨算子,是一種最簡單的算子,是一種利用局部差分算子尋找邊緣的算子。他採用對角線方向相鄰兩象素之差近似梯度幅值檢測邊緣。檢測垂直邊緣的效果好於斜向邊緣,定位精度高,對噪聲敏感,無法抑制噪聲的影響。
1963年, Roberts 提出了這種尋找邊緣的算子。 Roberts 邊緣算子是一個 2×2 的模版,採用的是對角方向相鄰的兩個像素之差。
Roberts 算子的模板分為水平方向和垂直方向,如下所示,從其模板可以看出, Roberts 算子能較好的增強正負 45 度的圖像邊緣。
\[dx = \left[ \begin{matrix} -1 & 0\\ 0 & 1 \\ \end{matrix} \right] \]
\[dy = \left[ \begin{matrix} 0 & -1\\ 1 & 0 \\ \end{matrix} \right] \]
Roberts 算子在水平方向和垂直方向的計算公式如下:
\[d_x(i, j) = f(i + 1, j + 1) – f(i, j) \]
\[d_y(i, j) = f(i, j + 1) – f(i + 1, j) \]
Roberts 算子像素的最終計算公式如下:
\[S = \sqrt{d_x(i, j)^2 + d_y(i, j)^2} \]
Prewitt 算子是一種一階微分算子的邊緣檢測,利用像素點上下、左右鄰點的灰度差,在邊緣處達到極值檢測邊緣,去掉部分偽邊緣,對噪聲具有平滑作用。
由於 Prewitt 算子採用 3 * 3 模板對區域內的像素值進行計算,而 Robert 算子的模板為 2 * 2 ,故 Prewitt 算子的邊緣檢測結果在水平方向和垂直方向均比 Robert 算子更加明顯。Prewitt算子適合用來識別噪聲較多、灰度漸變的圖像。
Prewitt 算子的模版如下:
\[dx = \left[ \begin{matrix} 1 & 0 & -1\\ 1 & 0 & -1\\ 1 & 0 & -1\\ \end{matrix} \right] \]
\[dy = \left[ \begin{matrix} -1 & -1 & -1\\ 0 & 0 & 0\\ 1 & 1 & 1\\ \end{matrix} \right] \]
★C++
// VC_FANFUHAN_OPENCV032.cpp : 定義主控台應用程式的進入點。 // /* // Debug | x32 通用屬性 | C/C++ | | 一般 | | 其他 Include 目錄 -> C:\opencv\build\include | | 連結器 | |一一般 | | 其他程式庫目錄 -> C:\opencv\build\x64\vc15\lib | | |一輸入 | | 其他相依性 -> opencv_world411d.lib;%(AdditionalDependencies) // Releas | x64 組態屬性 | C/C++ | | 一般 | | 其他 Include 目錄 -> C:\opencv\build\include | | 連結器 | |一般 | | 其他程式庫目錄 -> C:\opencv\build\x64\vc15\lib | | |一輸入 | | 其他相依性 -> 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 pause() { printf("Press Enter key to continue..."); fgetc(stdin); } int main() { Mat src = imread("../../images/test.png"); if (src.empty()) { cout << "could not load image.." << endl; pause(); return -1; } else { Mat src_gray; cvtColor(src, src_gray, COLOR_BGR2GRAY); GaussianBlur(src, src, Size(7, 7), 0); GaussianBlur(src_gray, src_gray, Size(7, 7), 0); imshow("input_src", src); showHistogram(src, "Histogram_input_src"); imshow("input_src_gray", src_gray); showHistogram(src_gray, "Histogram_input_src_gray"); Mat grad_x00, grad_y00, grad00; // 求取x方向和y方向梯度 Sobel(src, grad_x00, CV_32F, 1, 0); Sobel(src, grad_y00, CV_32F, 0, 1); convertScaleAbs(grad_x00, grad_x00); convertScaleAbs(grad_y00, grad_y00); // 求取总梯度 add(grad_x00, grad_y00, grad00, Mat(), CV_16S); convertScaleAbs(grad00, grad00); imshow("grad_BGR", grad00); Mat grad_x01, grad_y01, grad01; // 求取x方向和y方向梯度 Sobel(src_gray, grad_x01, CV_16S, 1, 0); Sobel(src_gray, grad_y01, CV_16S, 0, 1); convertScaleAbs(grad_x01, grad_x01); convertScaleAbs(grad_y01, grad_y01); add(grad_x01, grad_y01, grad01, Mat(), CV_16S); convertScaleAbs(grad01, grad01); imshow("grad", grad01); // Robert算子 Mat robert_x = (Mat_<int>(2, 2) << 1, 0, 0, -1); Mat robert_y = (Mat_<int>(2, 2) << 0, -1, 1, 0); Mat robert_grad_x, robert_grad_y, robert_grad; filter2D(src, robert_grad_x, CV_16S, robert_x); filter2D(src, robert_grad_y, CV_16S, robert_y); convertScaleAbs(robert_grad_x, robert_grad_x); convertScaleAbs(robert_grad_y, robert_grad_y); add(robert_grad_x, robert_grad_y, robert_grad); convertScaleAbs(robert_grad, robert_grad); imshow("robert_grad", robert_grad); // 定义Prewitt算子 Mat prewitt_x = (Mat_<char>(3, 3) << -1, 0, 1, -1, 0, 1, -1, 0, 1); Mat prewitt_y = (Mat_<char>(3, 3) << -1, -1, -1, 0, 0, 0, 1, 1, 1); Mat prewitt_grad_x, prewitt_grad_y, prewitt_grad; filter2D(src, prewitt_grad_x, CV_32F, prewitt_x); filter2D(src, prewitt_grad_y, CV_32F, prewitt_y); convertScaleAbs(prewitt_grad_x, prewitt_grad_x); convertScaleAbs(prewitt_grad_y, prewitt_grad_y); add(prewitt_grad_x, prewitt_grad_y, prewitt_grad); convertScaleAbs(prewitt_grad, prewitt_grad); imshow("prewitt_grad", prewitt_grad); waitKey(0); return 0; } return 0; } 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 cv2 as cv import numpy as np src = cv.imread("D:/images/test.png") cv.namedWindow("input", cv.WINDOW_AUTOSIZE) cv.imshow("input", src) robert_x = np.array([[1, 0],[0, -1]], dtype=np.float32) robert_y = np.array([[0, -1],[1, 0]], dtype=np.float32) prewitt_x = np.array([[-1, 0, 1], [-1, 0, 1], [-1, 0, 1]], dtype=np.float32) prewitt_y = np.array([[-1, -1, -1], [0, 0, 0], [1, 1, 1]], dtype=np.float32) robert_grad_x = cv.filter2D(src, cv.CV_16S, robert_x) robert_grad_y = cv.filter2D(src, cv.CV_16S, robert_y) robert_grad_x = cv.convertScaleAbs(robert_grad_x) robert_grad_y = cv.convertScaleAbs(robert_grad_y) prewitt_grad_x = cv.filter2D(src, cv.CV_32F, prewitt_x) prewitt_grad_y = cv.filter2D(src, cv.CV_32F, prewitt_y) prewitt_grad_x = cv.convertScaleAbs(prewitt_grad_x) prewitt_grad_y = cv.convertScaleAbs(prewitt_grad_y) # cv.imshow("robert x", robert_grad_x); # cv.imshow("robert y", robert_grad_y); # cv.imshow("prewitt x", prewitt_grad_x); # cv.imshow("prewitt y", prewitt_grad_y); h, w = src.shape[:2] robert_result = np.zeros([h, w*2, 3], dtype=src.dtype) robert_result[0:h,0:w,:] = robert_grad_x robert_result[0:h,w:2*w,:] = robert_grad_y cv.imshow("robert_result", robert_result) prewitt_result = np.zeros([h, w*2, 3], dtype=src.dtype) prewitt_result[0:h,0:w,:] = prewitt_grad_x prewitt_result[0:h,w:2*w,:] = prewitt_grad_y cv.imshow("prewitt_result", prewitt_result) cv.imwrite("D:/prewitt.png", prewitt_result) cv.imwrite("D:/robert.png", robert_result) cv.waitKey(0) cv.destroyAllWindows()
★結果圖:
★延伸說明/重點回顧: