##########################ImgProcess.h
##########################
#include
#include
using namespace cv;
using namespace std;
class MyImg{ //自定义类
private:
Mat OriImg; //原始图像
string filename; //文件名
public: MyImg(); //构造函数
~MyImg(); //析构函数
void ImgShow(string namewindow, Mat img); //显示图像
void ImgRead(); //读取图片
Mat GetOriImg(); //获取原始图片
string Getfilename(); //获取图片路径
void Setfilename(string s); //设置图片路径
Mat RGBToGray(Mat img); //灰度化
Mat GaussFilter(Mat img, double KernalSigma, int KernalSize); //高斯滤波
void GetGaussianKernel(double **KernalModel,int size, double sigma); //高斯模板
void SobelGrade(Mat img, Mat Gx, Mat Gy, Mat Arg, Mat Grad); //高斯滤波
int GetDir(double num); //获取梯度方向
Mat GetNMS(Mat Arg, Mat Grad,int diff); //非极大值抑制
Mat GetDouThre(Mat img, Mat LThImg, Mat HThImg, double lowThreshold, double highThreshold,int size); //双阈值法
};
##########################ImgProcess.c
##########################
#include"ImgProcess.h"
#include "math.h"
#include
MyImg::MyImg() { // 构造函数
printf("Creat Class MyImg......\n");
}
MyImg::~MyImg() { // 析构函数
printf("Delete Class MyImg......\n");
}
void MyImg::ImgRead() { // 读取图片
printf("Img Reading......\n");
OriImg = imread(filename);
}
Mat MyImg::GetOriImg() // 获取原始图片
{
return(OriImg);
}
string MyImg::Getfilename() // 获取路径名字
{
return(filename);
}
void MyImg::Setfilename(string s) // 设置路径名
{
/******************************
// 参数 s: 图片路径名字
********************************/
filename = s;
}
void MyImg::ImgShow(string namewindow,Mat img) { // 显示图像
/******************************
// 参数 namewindow: 窗口名字
// img: 显示的图像
********************************/
printf("Img Showing......\n");
namedWindow(namewindow);
imshow(namewindow, img);
}
Mat MyImg::RGBToGray(Mat img) // 灰度化
{
/******************************
// 参数img: 输入彩色图
********************************/
Mat image_gray = Mat::zeros(img.size(), CV_8UC1); // 初始化图像 单通道
for (int i = 0; i < img.rows; i++) // 循环每个像素点 灰度化
{
for (int j = 0; j < img.cols; j++)
{
image_gray.at<uchar>(i, j) = 0.114 * img.at<Vec3b>(i, j)[0] + 0.587 * img.at<Vec3b>(i, j)[1] + 0.299 * img.at<Vec3b>(i, j)[2];
}
}
//cout << img.at(5, 5)[0] << endl;
return(image_gray);
}
Mat MyImg::GaussFilter(Mat img,double KernalSigma, int KernalSize) // 高斯滤波 单通道 灰度图
{
/******************************
// 参数img: 输入灰度
//KernalSigma:高斯核方差
//KernalSize:高斯核大小(奇数)
********************************/
Mat image_gray = img.clone(); // 初始化图像 单通道
int height = img.rows; // 图像高度
int width = img.cols; // 图像的宽度
// 高斯核
//int KernalSize = 5; //定义卷积核大小
double **KernalModel = new double *[KernalSize]; //卷积核数组
for (int i = 0; i<KernalSize; i++)
{
KernalModel[i] = new double[KernalSize]; //动态生成二维数组
}
//double KernalSigma = 1; // 高斯模板的方差
GetGaussianKernel(KernalModel, KernalSize, KernalSigma); //获取高斯核
int diff = KernalSize / 2; // 边界
double sum;
for (int i = diff; i < height-diff; i++) //高斯滤波 边界直接用原来的值
{
for (int j = diff; j < width-diff; j++)
{
sum = 0;
for (int m = 0; m < KernalSize; m++) {
for (int n = 0; n < KernalSize; n++) {
sum = sum + KernalModel[m][n] * img.at<uchar>(i-diff+m, j-diff+n);
}
}
image_gray.at<uchar>(i, j) = sum;
}
}
return image_gray;
}
void MyImg::GetGaussianKernel(double **KernalModel, int size, double sigma) // 获取高斯核
{
/******************************
// 参数KernalModel: 初始化高斯核
//sigma:高斯核方差
//size:高斯核大小(奇数)
********************************/
const double PI = 3.141592653; //Pi
int center = size / 2; //中心
double sum = 0; //高斯模板总和
for (int i = 0; i < size; i++) //高斯模板计算
{
for (int j = 0; j < size; j++)
{
KernalModel[i][j] = (1 / (2 * PI*sigma*sigma))*exp(-((i - center)*(i - center) + (j - center)*(j - center)) / (2 * sigma*sigma));
sum += KernalModel[i][j];
}
}
cout <<endl<< "归一化高斯模板:" << endl << endl;
for (int m = 0; m < size; m++) // 归一化
{
for (int n = 0; n < size; n++)
{
KernalModel[m][n] /= sum;
cout << KernalModel[m][n] << ",";
}
cout <<endl<<endl;
}
}
void MyImg::SobelGrade(Mat img, Mat Gx, Mat Gy, Mat Arg, Mat Grad)
{
/******************************
// 参数img: 输入图像
//Gx:x方向梯度图
//Gy:y方向梯度图
//Arg:梯度角度图
//Grad:梯度模值图
********************************/
double KernalModel1[3][3] = { {-1,0,1},{-2,0,2},{-1,0,1} };
double KernalModel2[3][3] = { { 1,2,1 },{ 0,0,0 },{ -1,-2,-1 } };
int height = img.rows; // 图像高度
int width = img.cols; // 图像的宽度
//cout << height << endl << width;
int KernalSize = 3; //定义卷积核大小
int diff = KernalSize / 2; // 边界
double sum1;
double sum2;
for (int i = diff; i < height - diff; i++) //高斯滤波 边界直接用原来的值
{
for (int j = diff; j < width - diff; j++)
{
sum1 = 0;
sum2 = 0;
for (int m = 0; m < KernalSize; m++) { // 模板卷积操作
for (int n = 0; n < KernalSize; n++) {
sum1 = sum1 + KernalModel1[m][n] * img.at<uchar>(i - diff + m, j - diff + n);
sum2 = sum2 + KernalModel2[m][n] * img.at<uchar>(i - diff + m, j - diff + n);
}
}
//cout<
//cout << sum2 << endl << endl;
Gx.at<uchar>(i, j) = ((abs(sum1))!=0)? abs(sum1):0.0000001; //x 方向 防止分母为0
Gy.at<uchar>(i, j) = abs(sum2); //y方向
Grad.at<uchar>(i, j) = sqrt(sum1*sum1+sum2*sum2); //梯度模值
Arg.at<uchar>(i, j) = atan(sum1 / sum2)*57.3; // 梯度角度
//cout << atan(sum1 / sum2)*57.3 <<" , ";
}
}
}
int MyImg::GetDir(double num)
{
/******************************
// 获取方向 num表示梯度角度角度
//1:-90度 90度
//2:-45度
//3:0度
//4:45度
********************************/
if (num < -67.5)
return(1); //-90度
else if(num>= -67.5&& num < -22.5)
{
return(2); //-45度
}
else if (num >= -22.5 && num < 22.5)
{
return(3); //0度
}
else if (num >= 22.5 && num < 67.5)
{
return(4); //45度
}
else {
return(1); //90度
}
}
Mat MyImg::GetNMS(Mat Arg, Mat Grad,int diff)
{
//非极大值抑制
// 沿着梯度方向 判断该点的梯度是否为最大
/******************************
// 获取方向 num表示梯度角度角度
//Arg:梯度角度图
//Grad:梯度模值
//diff:沿着梯度方向判断的个数
********************************/
Mat NMS_Img = Grad.clone(); // 初始化图像 单通道
int height = Grad.rows; // 图像高度
int width = Grad.cols; // 图像的宽度
int dir;
for (int i = diff; i < height - diff; i++) //高斯滤波 边界直接用原来的值
{
for (int j = diff; j < width - diff; j++)
{
dir = GetDir(Arg.at<uchar>(i, j));
if (dir == 1) { //-90 90
for (int index = 0; index <diff; index++) { //判断是否为极大值
if ((Grad.at<uchar>(i, j) < Grad.at<uchar>(i - diff + index, j)) || (Grad.at<uchar>(i, j) < Grad.at<uchar>(i + diff - index, j))) {
NMS_Img.at<uchar>(i, j) = 0;
break;
}
}
}
else if (dir == 2) { //-45
for (int index = 0; index <diff; index++) { //判断是否为极大值
if ((Grad.at<uchar>(i, j) < Grad.at<uchar>(i - diff + index, j - diff + index)) || (Grad.at<uchar>(i, j) < Grad.at<uchar>(i + diff - index, j + diff - index))) {
NMS_Img.at<uchar>(i, j) = 0;
break;
}
}
}
else if (dir == 3) { //0
for (int index = 0; index <diff; index++) { //判断是否为极大值
if ((Grad.at<uchar>(i, j) < Grad.at<uchar>(i, j - diff + index)) || (Grad.at<uchar>(i, j) < Grad.at<uchar>(i, j + diff - index))) {
NMS_Img.at<uchar>(i, j) = 0;
break;
}
}
}
else{ //45
for (int index = 0; index <diff; index++) { //判断是否为极大值
if ((Grad.at<uchar>(i, j) < Grad.at<uchar>(i - diff + index, j + diff - index)) || (Grad.at<uchar>(i, j) < Grad.at<uchar>(i + diff - index, j - diff + index))) {
NMS_Img.at<uchar>(i, j) = 0;
break;
}
}
}
}
}
return NMS_Img;
}
Mat MyImg::GetDouThre(Mat img, Mat LThImg, Mat HThImg, double lowThreshold, double highThreshold,int size)
{
// 双阈值法
/******************************
// img: 输入图像
//LThImg:低阈值图
//HThImg:高阈值图
//lowThreshold:低阈值
//highThreshold:高阈值
//size;尺寸
********************************/
int height = img.rows; // 图像高度
int width = img.cols; // 图像的宽度
for (int i = 0; i < height; i++) // 高低阈值处理
{
for (int j = 0; j < width; j++)
{
if(img.at<uchar>(i, j)>lowThreshold){ //低阈值处理
LThImg.at<uchar>(i, j) = 255;
}
else
{
LThImg.at<uchar>(i, j) = 0;
}
if (img.at<uchar>(i, j)>highThreshold) { //高阈值处理
HThImg.at<uchar>(i, j) = 255;
}
else
{
HThImg.at<uchar>(i, j) = 0;
}
}
}
Mat DouImg = HThImg.clone(); //初始化最终Canny检测图像
for (int i = 0; i < height; i++) //双阈值检测边缘
{
for (int j = 0; j < width; j++)
{
if ((HThImg.at<uchar>(i, j) == 0) && (LThImg.at<uchar>(i, j) == 255)) {
/*if (DouImg.at(i - 1, j - 1) == 255 || DouImg.at(i, j - 1) == 255 || DouImg.at(i + 1, j - 1) == 255 || DouImg.at(i - 1, j) == 255 ||
DouImg.at(i+1, j) == 255 || DouImg.at(i - 1, j + 1) == 255 || DouImg.at(i, j + 1) == 255 || DouImg.at(i + 1, j + 1) == 255)
{
DouImg.at(i, j) = 255;
}*/
for (int m = i-size; m < i+size; m++) { // 判断size * size 是否存在高阈值点 如果当前点也是边缘
for (int n = j - size; n < j + size; n++) {
if (DouImg.at<uchar>(m, n) == 255) {
DouImg.at<uchar>(i, j) = 255;
break;
}
}
}
}
}
}
return(DouImg);
}
##########################main.c
##########################
#include
#include
#include
#include
#include"ImgProcess.h"
using namespace cv;
using namespace std;
int main()
{
MyImg myImg; //定义自定义对象
myImg.Setfilename("lena.tiff"); //设置路径
myImg.ImgRead(); //读取图片
Mat grayimg = myImg.RGBToGray(myImg.GetOriImg()); //灰度化
//Mat grayimg2;
//GaussianBlur(grayimg, grayimg2, Size(3, 3), 1, 1); //高斯滤波
//myImg.ImgShow("高斯滤波图像2", grayimg2); //显示灰度图
myImg.ImgShow("原始图像", myImg.GetOriImg()); //显示原图
myImg.ImgShow("灰度图像", grayimg); //显示灰度图
Mat GaussFiltergrayimg = myImg.GaussFilter(grayimg, 1, 3); //自定义高斯滤波
myImg.ImgShow("高斯滤波图像", GaussFiltergrayimg); //显示灰度图
Mat Gx = GaussFiltergrayimg.clone(); //初始化x梯度图像
Mat Gy = GaussFiltergrayimg.clone(); //初始化y梯度图像
Mat Arg = GaussFiltergrayimg.clone(); //初始化梯度角度图像
Mat Grad = GaussFiltergrayimg.clone(); //初始化梯度模值图像
myImg.SobelGrade(GaussFiltergrayimg,Gx,Gy,Arg, Grad); //高斯滤波
myImg.ImgShow("Gx", Gx); //显示x梯度图像
myImg.ImgShow("Gy", Gy); //显示y梯度图像
//myImg.ImgShow("梯度角度图", Arg); //显示梯度角度图像
myImg.ImgShow("梯度模值图", Grad); //显示初始化梯度模值图像
Mat NMSImg = myImg.GetNMS(Arg, Grad, 3); //极大值抑制
myImg.ImgShow("极大值抑制", NMSImg); //显示初始化梯度模值图像
Mat LThImg = NMSImg.clone(); //初始化低阈值图像
Mat HThImg = NMSImg.clone(); //初始化高阈值图像
double lowThreshold = 50; //低阈值
double highThreshold = 180; //高阈值
int Dousize = 7; //领域尺寸
Mat DouImg = myImg.GetDouThre(NMSImg, LThImg, HThImg,lowThreshold, highThreshold, Dousize); //双阈值处理
myImg.ImgShow("低阈值图像", LThImg); //显示低阈值图像
myImg.ImgShow("高阈值图像", HThImg); //显示高阈值图像
myImg.ImgShow("Canny边缘检测", DouImg); //显示Canny图像
waitKey(0); //暂停
system("pause");
cin.get();
return(0);
}
结果说明:根据 Canny 检测的步骤,依次得到以上的结果,图像高斯滤波 后更为平滑,模糊(图 3.3),接着得到 x 和 y 方向的梯度图,以及模值图像。 经过极大值抑制,图像的粗略边缘已经得到,如图 3.7,但仍然有一些非边缘点。 在极大值抑制的结果上,经过高阈值(图 3.9)和低阈值(图 3.8)处理,得到两 幅边缘图像;高阈值图像的边缘点少于低阈值图像的边缘点,通过双阈值法,得 到最后的 Canny 检测轮廓的结果,如图 3.10,边缘检测效果很好。
主页可以下载,或者点这里这里!!!