标签:
关于Canny图像边缘检测的原理,网上有很多介绍的资料,
其中一篇介绍得比较好的文章:http://blog.csdn.net/likezhaobin/article/details/6892176
大家可以参考一下。
----------------------少废话,上代码-------------------------------------------------
/* * CannyEdgeDetection.h * * 过客 && 386520874@qq.com && 2016.03.26 * */ #ifndef __CANNY_EDGE_DETECTION_H__ #define __CANNY_EDGE_DETECTION_H__ #include <windows.h> #include <vector> //----canny边缘检测----------- class CCannyEdgeDetection { public: CCannyEdgeDetection(); ~CCannyEdgeDetection(); public: int m_guid_index; //保存的图片格式,0-ImageFormatBMP,1-ImageFormatJPEG,2-ImageFormatPNG,3-ImageFormatGIF BITMAP m_bitmap; HBITMAP m_hBitmap; public: //--------图片操作--------------- int OpenImage(wchar_t* filename, BITMAP &bitmap, HBITMAP &hBitmap); //打开图片 int SaveImage(wchar_t* filename, HBITMAP &hBitmap); //保存图片,针对位图句柄 int SaveImage(wchar_t* filename, BITMAP &bitmap); //保存图片,针对内存中位图结果 int CreateEmptyImage(BITMAP &bitmap, int width, int height, int bmBitsPixel); //在内存中创建一幅空白位图 int ReleaseHandle(); //主动释放资源 int ReleaseBitmap(BITMAP &bitmap); //主动释放资源 int Canny(BITMAP &bitmap_src, BITMAP &bitmap_dst, double low_thresh, double high_thresh); //canny边缘检测 int Sobel(BITMAP &bitmap_src, BITMAP &bitmap_dst, double low_thresh, double high_thresh); //Sobel图像一阶差分梯度 }; #endif //__CANNY_EDGE_DETECTION_H__
/* * CannyEdgeDetection.cpp * * 过客 && 386520874@qq.com && 2016.03.26 * */ #define WINVER 0x0500 #define _WIN32_WINNT 0x0500 //#include <windows.h> #include <afx.h> #include <atlimage.h> #include <afxwin.h> #include "CannyEdgeDetection.h" CCannyEdgeDetection::CCannyEdgeDetection() { m_guid_index = 2; //默认输出png m_hBitmap = NULL; } CCannyEdgeDetection::~CCannyEdgeDetection() { if(m_hBitmap != NULL) { ::DeleteObject(m_hBitmap); m_hBitmap = NULL; } } //--------图片操作--------------- int CCannyEdgeDetection::ReleaseHandle() { if(m_hBitmap != NULL) { ::DeleteObject(m_hBitmap); m_hBitmap = NULL; } return 1; } int CCannyEdgeDetection::ReleaseBitmap(BITMAP &bitmap) { unsigned char* pBits = static_cast<unsigned char*>(bitmap.bmBits); if(pBits != NULL) { delete [] pBits; //释放用new申请的资源 pBits = NULL; memset(&bitmap, 0, sizeof(BITMAP)); } return 1; } int CCannyEdgeDetection::OpenImage(wchar_t* filename, BITMAP &bitmap, HBITMAP &hBitmap) { CFileFind filefind; BOOL IsFileFind = filefind.FindFile(filename); if(!IsFileFind) { // printf("Error: Can not find file: %s;\n", filename); wchar_t err_str[200]; wsprintf(err_str, _T("您打开的图片文件[%s]不存在!"), filename); MessageBox(NULL, err_str, _T("错误"), MB_OK|MB_ICONERROR); return 0; } //---------------------------------------- CImage img; img.Load(filename); int width = img.GetWidth(); int height = img.GetHeight(); hBitmap = img.Detach(); //如果用Detach(),则CImage析构后,hBitmap仍可使用。 int nBytes = ::GetObject(hBitmap, sizeof(BITMAP), &bitmap); return 1; } int CCannyEdgeDetection::SaveImage(wchar_t* filename, HBITMAP &hBitmap) { GUID guid[4] = { Gdiplus::ImageFormatBMP, Gdiplus::ImageFormatJPEG, Gdiplus::ImageFormatPNG, Gdiplus::ImageFormatGIF }; if(m_guid_index<0 || m_guid_index>3) { printf("Erorr: SaveImage: m_guid_index must in [0,3]\n"); } CImage img; img.Attach(hBitmap); img.Save(filename, guid[m_guid_index]); //可以从guid看出,CImage的Save()实际上是通过GDI+实现的。 hBitmap = img.Detach(); //如果用Detach(),则CImage析构后,hBitmap仍可使用。 return 1; } int CCannyEdgeDetection::SaveImage(wchar_t* filename, BITMAP &bitmap) { int width = bitmap.bmWidth; int height = bitmap.bmHeight; int biBitCount = bitmap.bmBitsPixel; RGBQUAD *pColorTable = NULL; int colorTablesize = 0; //颜色表大小,以字节为单位,灰度图像颜色表为1024字节,彩色图像颜色表大小为0 // if(biBitCount == 8){colorTablesize=1024;} // int lineByte = (width * biBitCount/8+3)/4*4; //待存储图像数据每行字节数为4的倍数 int lineByte = bitmap.bmWidthBytes; //待存储图像数据每行字节数为4的倍数 int size_1 = sizeof(BITMAPFILEHEADER); // size_1 = 14 int size_2 = sizeof(BITMAPINFOHEADER); // size_2 = 40 int size_3 = lineByte * height; //计算位图尺寸 // int bpp = bitmap.bmBitsPixel/8; //bpp代表通道的数目,一般 bpp = 3 //--------------1. 位图文件头结构----------------------------------------------------- BITMAPFILEHEADER fileHead; fileHead.bfType = 0x4D42; //bmp类型 fileHead.bfSize = size_1 + size_2 + colorTablesize + lineByte * height; //bfSize是图像文件4个组成部分之和 fileHead.bfReserved1 = 0; fileHead.bfReserved2 = 0; fileHead.bfOffBits = 54 + colorTablesize; //bfOffBits是图像文件前3个部分所需空间之和 //--------------2. 位图信息头结构----------------------------------------------------- BITMAPINFOHEADER head; head.biBitCount = biBitCount; // 8,24,32 head.biClrImportant = 0; head.biClrUsed = 0; head.biCompression = 0; //BI_RGB = 0L head.biHeight = height; head.biPlanes = 1; head.biSize = 40; head.biSizeImage = lineByte * height; head.biWidth = width; head.biXPelsPerMeter = 0; head.biYPelsPerMeter = 0; //---------------3. 内存中的文件读写操作------------------------------- long file_size = fileHead.bfSize; //计算位图文件尺寸 unsigned char* pBits = static_cast<unsigned char*>(bitmap.bmBits); HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE|GMEM_ZEROINIT, file_size); if(hMem == NULL){printf("Erorr: GlobalAlloc: hMem == NULL\n");return 0;} unsigned char *pbuff = static_cast<unsigned char*>(GlobalLock(hMem)); // get the actual pointer for the HGLOBAL memcpy(pbuff, &fileHead, size_1); //内存复制 pbuff += size_1; memcpy(pbuff, &head, size_2); //内存复制 pbuff += size_2; memcpy(pbuff, pBits, size_3); //内存复制 IStream *pStream = 0; HRESULT hr = CreateStreamOnHGlobal(hMem, TRUE, &pStream); //此函数是内存数据到文件流的关键API函数 if(hr != S_OK) { printf("Erorr: CreateStreamOnHGlobal: hr != S_OK\n"); return 0; } //--------------4. 将文件流数据正式保存到磁盘文件中---------------------------------------- GUID guid[4] = { Gdiplus::ImageFormatBMP, Gdiplus::ImageFormatJPEG, Gdiplus::ImageFormatPNG, Gdiplus::ImageFormatGIF }; if(m_guid_index<0 || m_guid_index>3) { printf("Erorr: SaveImage: m_guid_index must in [0,3]\n"); return 0; } CImage img; img.Load(pStream); img.Save(filename, guid[m_guid_index]); //可以从guid看出,CImage的Save()实际上是通过GDI+实现的。 img.Detach(); img.Destroy(); GlobalFree(hMem); //释放GlobalAlloc(...)申请的内存 return 1; } /*-------------------在内存中创建一幅空白位图------------------------------ * * 参数1: bitmap 返回的结果 * 参数2: width 位图高度 * 参数3: height 位图宽度 * 参数4: bmBitsPixel 一个像素的字节大小,一般是24字节,也可以是32字节 * * 过客 && 386520874@qq.com && 2014.12.26 */ int CCannyEdgeDetection::CreateEmptyImage(BITMAP &bitmap, int width, int height, int bmBitsPixel) { // bmBitsPixel = 32; bitmap.bmWidth = width; bitmap.bmHeight = height; bitmap.bmBitsPixel = bmBitsPixel; bitmap.bmType = 0; bitmap.bmPlanes = 1; bitmap.bmWidthBytes = (width * bmBitsPixel/8+3)/4*4; printf("CreateEmptyImage: [%d x %d] memory = %d bytes;\n", width, height, bitmap.bmHeight * bitmap.bmWidthBytes); unsigned char *pBits = new unsigned char[bitmap.bmHeight * bitmap.bmWidthBytes]; //在堆上申请 if(pBits == NULL){printf("CreateEmptyImage: pBits == NULL\n");return 0;} memset(pBits, 0, sizeof(unsigned char) * bitmap.bmHeight * bitmap.bmWidthBytes); //初始化为黑色背景 bitmap.bmBits = pBits; return 1; } /*-------------------图像的Canny边缘检测------------------------------ * 算法原理: * 图像的Canny边缘检测算法,是一种理论和实际效果比较靠谱的算法,它 * 大致有下面几个步骤: * 1. 将输入的RGB图像转换成单通道的灰度图像 * 2. 计算灰度图像的一阶梯度,该算法选择Sobel算子计算dx和dy两个方向 * 的梯度 * 3. 对图像的梯度幅值进行非极大值抑制,这一步是Canny算法的独到之处 * 经过这一步后,真正的边缘点会被暴露出来,并将其位置保存下来 * 4. 双阈值检测:将保存下来的已确认为边缘的点,以每个点为中心,将 * 相邻的8个像素由1变成2,即将曲线的轮廓进行不断的延伸。 * * Sobel算子模板: * | -1 0 +1 | | +1 +2 +1 | * Sx = | -2 0 +2 | Sy = | 0 0 0 | * | -1 0 +1 | | -1 -2 -1 | * * 函数名称: Canny(...) * 参数1: bitmap_src [in]输入的图像位图数据 * 参数2: bitmap_dst [out]输出的图像位图数据 * 参数3: low_thresh [in]低阈值,所有梯度幅值低于此值的点不认为是边缘点 * 参数4: high_thresh [in]高阈值,经过非极大值抑制后,所有梯度幅值高于 * 此值的点认为是边缘点 * * 过客 && 386520874@qq.com && 2015.03.27 */ int CCannyEdgeDetection::Canny(BITMAP &bitmap_src, BITMAP &bitmap_dst, double low_thresh, double high_thresh) { //-------------1. 输入参数检查----------------------- BITMAP bitmap1 = bitmap_src; unsigned char* pBits1 = static_cast<unsigned char*>(bitmap1.bmBits); int bpp1 = bitmap1.bmBitsPixel/8; //bpp代表通道的数目,一般 bpp = 3 int width2 = bitmap1.bmWidth; int height2 = bitmap1.bmHeight; CreateEmptyImage(bitmap_dst, width2, height2, bitmap1.bmBitsPixel); int bpp2 = bitmap_dst.bmBitsPixel/8; //bpp代表通道的数目,一般 bpp = 3 unsigned char* pBits2 = static_cast<unsigned char*>(bitmap_dst.bmBits); BITMAP bitmap2 = bitmap_dst; //-------------2. RGB转灰度----------------------- int* gray = new int[width2 * height2]; //保存灰度图像数据 for(int y = 0; y < bitmap1.bmHeight; y++) { for(int x = 0; x < bitmap1.bmWidth; x++) { int B = pBits1[y * bitmap1.bmWidthBytes + x * bpp1 + 0]; //Blue int G = pBits1[y * bitmap1.bmWidthBytes + x * bpp1 + 1]; //Green int R = pBits1[y * bitmap1.bmWidthBytes + x * bpp1 + 2]; //Red // int A = R * 0.299 + G * 0.587 + B * 0.114; //一般RGB2Gray公式 int A = R * 0.212671 + G * 0.715160 + B * 0.072169; //opencv的RGB2Gray公式 0.212671*R + 0.715160*G + 0.072169*B gray[y * width2 + x] = A; } } //-------------3. 计算灰度图像梯度幅值和方向----------------------- int* dx = new int[width2 * height2]; //x向偏导数 int* dy = new int[width2 * height2]; //y向偏导数 memset(dx, 0, sizeof(int) * width2 * height2); memset(dy, 0, sizeof(int) * width2 * height2); //利用Sobel算子,计算x,y方向的偏导数 for(int y = 0; y < height2; y++) { for(int x = 0; x < width2; x++) { if(x < 1 || x >= width2 -1 || y < 1 || y >= height2 - 1){continue;} //3x3的算子,图像的4条边需要跳过 dx[y * width2 + x] = -(gray[(y - 1) * width2 + (x - 1)] * 1 + gray[(y + 0) * width2 + (x - 1)] * 2 + gray[(y + 1) * width2 + (x - 1)] * 1) + (gray[(y - 1) * width2 + (x + 1)] * 1 + gray[(y + 0) * width2 + (x + 1)] * 2 + gray[(y + 1) * width2 + (x + 1)] * 1); dy[y * width2 + x] = (gray[(y - 1) * width2 + (x - 1)] * 1 + gray[(y - 1) * width2 + (x + 0)] * 2 + gray[(y - 1) * width2 + (x + 1)] * 1) - (gray[(y + 1) * width2 + (x - 1)] * 1 + gray[(y + 1) * width2 + (x + 0)] * 2 + gray[(y + 1) * width2 + (x + 1)] * 1); } } //计算梯度幅值和梯度的方向 //....... //-------------4. 非极大值抑制----------------------- //下面代码来自于opencv的canny.cpp修改版本 bool L2gradient = false; //采用哪种梯度的计算公式 // bool L2gradient = true; //采用哪种梯度的计算公式 // const int cn = src.channels(); const int cn = 1; int low = low_thresh; int high = high_thresh; typedef unsigned char uchar; ptrdiff_t mapstep = width2 + 2; //注意buffer是一个二维数组,总体来说有[3 + height]行,前面3行用来不断滚动的临时计算 //和存储图像梯度|dx+dy|的幅值,后面[height]行中每个数组元素用来标记,图像中对应点是 //否是边缘点信息,只有[0,1,2]三种值, //0-表示该像素可能是边缘 //1-表示该像素不可能是边缘 //2-表示该像素是边缘 uchar * buffer = new uchar[(width2 + 2) * (height2 + 2) + cn * mapstep * 3 * sizeof(int)]; //存储边缘信息的数组 int* mag_buf[3]; mag_buf[0] = (int*)(uchar*)buffer; mag_buf[1] = mag_buf[0] + mapstep * cn; mag_buf[2] = mag_buf[1] + mapstep * cn; memset(mag_buf[0], 0, mapstep * sizeof(int)); uchar* map = (uchar*)(mag_buf[2] + mapstep * cn); memset(map, 1, mapstep); memset(map + mapstep * (height2 + 1), 1, mapstep); int maxsize = max(1 << 10, width2 * height2 / 10); //栈stack用来存储标记为2的像素点指针,栈的最大尺寸为[width2 * height2] std::vector<uchar*> stack(maxsize); uchar **stack_top = &stack[0]; uchar **stack_bottom = &stack[0]; //---------------------------- #define CANNY_PUSH(d) *(d) = uchar(2), *stack_top++ = (d) #define CANNY_POP(d) (d) = *--stack_top // calculate magnitude and angle of gradient, perform non-maxima suppression. // fill the map with one of the following values: // 0 - the pixel might belong to an edge,0-表示该像素可能是边缘 // 1 - the pixel can not belong to an edge,1-表示该像素不可能是边缘 // 2 - the pixel does belong to an edge,2-表示该像素是边缘 for(int i = 0; i <= height2; i++) //遍历行 { int* _norm = mag_buf[(i > 0) + 1] + 1; if(i < height2) { int* _dx = dx + i * width2; int* _dy = dy + i * width2; if(!L2gradient) { for(int j = 0; j < width2 * cn; j++) { _norm[j] = abs(_dx[j]) + abs(_dy[j]); //梯度的幅值 |G| = |dx| + |dy|,默认使用这个公式 } }else { for(int j = 0; j < width2 * cn; j++) { // _norm[j] = _dx[j] * _dx[j] + _dy[j] * _dy[j]; //梯度的幅值 |G| = |dx|*|dx| + |dy|*|dy| _norm[j] = sqrt(1.0 * (_dx[j] * _dx[j] + _dy[j] * _dy[j])); //梯度的幅值 |G| = sqrt(|dx|*|dx| + |dy|*|dy|) } } if(cn > 1) { for(int j = 0, jn = 0; j < width2; ++j, jn += cn) { int maxIdx = jn; for(int k = 1; k < cn; ++k) { if(_norm[jn + k] > _norm[maxIdx]){maxIdx = jn + k;} } _norm[j] = _norm[maxIdx]; _dx[j] = _dx[maxIdx]; _dy[j] = _dy[maxIdx]; } } _norm[-1] = _norm[width2] = 0; }else { memset(_norm - 1, 0, mapstep * sizeof(int)); } // at the very beginning we do not have a complete ring // buffer of 3 magnitude rows for non-maxima suppression if (i == 0){continue;} uchar* _map = map + mapstep * i + 1; _map[-1] = _map[width2] = 1; int* _mag = mag_buf[1] + 1; // take the central row ptrdiff_t magstep1 = mag_buf[2] - mag_buf[1]; ptrdiff_t magstep2 = mag_buf[0] - mag_buf[1]; const int* _x = dx + (i - 1) * width2; const int* _y = dy + (i - 1) * width2; if((stack_top - stack_bottom) + width2 > maxsize) { int sz = (int)(stack_top - stack_bottom); maxsize = maxsize * 3 / 2; stack.resize(maxsize); //将栈空间扩大为原来的3/2=1.5倍 stack_bottom = &stack[0]; stack_top = stack_bottom + sz; } int prev_flag = 0; for(int j = 0; j < width2; j++) //遍历列 { #define CANNY_SHIFT 15 const int TG22 = (int)(0.4142135623730950488016887242097*(1<<CANNY_SHIFT) + 0.5); //tan(PI/8)=0.41421356... double m = _mag[j]; if(m > low) { int xs = _x[j]; int ys = _y[j]; int x = std::abs(xs); int y = std::abs(ys) << CANNY_SHIFT; double tg22x = x * TG22; if(y < tg22x) // PI/8 = 22.5度,tan(PI/8) { if(m > _mag[j-1] && m >= _mag[j+1]) goto __ocv_canny_push; //非极大值抑制:3x3模板,8邻域像素的水平方向 }else { double tg67x = tg22x + (x << (CANNY_SHIFT+1)); if(y > tg67x) // PI*3/8 = 67.5度,tan(PI*3/8) { if(m > _mag[j+magstep2] && m >= _mag[j+magstep1]) goto __ocv_canny_push; //非极大值抑制:3x3模板,8邻域像素的垂直方向 }else { int s = (xs ^ ys) < 0 ? -1 : 1; if(m > _mag[j+magstep2-s] && m > _mag[j+magstep1+s]) goto __ocv_canny_push; //非极大值抑制:3x3模板,8邻域像素的两条45度对角线方向 } } } prev_flag = 0; _map[j] = uchar(1); //1-表示该像素点不是边缘 continue; __ocv_canny_push: if (!prev_flag && m > high && _map[j-mapstep] != 2) { CANNY_PUSH(_map + j); //2-表示该像素是边缘,则将其弹入栈中 prev_flag = 1; }else { _map[j] = 0; //0-表示该像素可能是边缘 } } // scroll the ring buffer // 滚动交换保存梯度幅值的行 _mag = mag_buf[0]; mag_buf[0] = mag_buf[1]; mag_buf[1] = mag_buf[2]; mag_buf[2] = _mag; } //-------------5. 双阈值检测:将8个相邻的像素由1变成2---------------------------------- // now track the edges (hysteresis thresholding) while(stack_top > stack_bottom) { uchar* m; if ((stack_top - stack_bottom) + 8 > maxsize) { int sz = (int)(stack_top - stack_bottom); maxsize = maxsize * 3 / 2; stack.resize(maxsize); //将栈空间扩大为原来的3/2=1.5倍 stack_bottom = &stack[0]; stack_top = stack_bottom + sz; } CANNY_POP(m); //因为栈中保存的都是边缘点像素,现在循环检测栈中所有边缘点的 //周围8个像素是否被标记为0,如果是0,则认为该点也是边缘点,并 //将其弹入栈中,可以看出这个栈的功能实现了递归函数的功能,直 //到栈空为止,则结束循环。 if(!m[-1]) CANNY_PUSH(m - 1); if(!m[1]) CANNY_PUSH(m + 1); if(!m[-mapstep-1]) CANNY_PUSH(m - mapstep - 1); if(!m[-mapstep]) CANNY_PUSH(m - mapstep); if(!m[-mapstep+1]) CANNY_PUSH(m - mapstep + 1); if(!m[mapstep-1]) CANNY_PUSH(m + mapstep - 1); if(!m[mapstep]) CANNY_PUSH(m + mapstep); if(!m[mapstep+1]) CANNY_PUSH(m + mapstep + 1); } //------------6. 保存算法结果到图片中---------------------- // the final pass, form the final image const uchar* pmap = map + mapstep + 1; for(int y = 0; y < height2; y++) { for(int x = 0; x < width2; x++) { uchar _gray = (uchar)-(pmap[y * mapstep + x] >> 1); //因为pmap[]里面的值只有0,1,2三种,而2才是边缘像素点,所以,-(2 >> 1) = 255,即用白色表示边缘 pBits2[y * bitmap2.bmWidthBytes + x * bpp2 + 0] = _gray; pBits2[y * bitmap2.bmWidthBytes + x * bpp2 + 1] = _gray; pBits2[y * bitmap2.bmWidthBytes + x * bpp2 + 2] = _gray; } } //------------------------------ delete [] gray; gray = NULL; delete [] dx; dx = NULL; delete [] dy; dy = NULL; delete [] buffer; buffer = NULL; return 1; } /*-------------------Sobel图像一阶差分梯度边缘检测------------------------------ * 算法原理: * * Sobel算子模板: * | -1 0 +1 | | +1 +2 +1 | * Sx = | -2 0 +2 | Sy = | 0 0 0 | * | -1 0 +1 | | -1 -2 -1 | * * 函数名称: Canny(...) * 参数1: bitmap_src [in]输入的图像位图数据 * 参数2: bitmap_dst [out]输出的图像位图数据 * 参数3: low_thresh [in]低阈值,(暂时不使用该参数) * 参数4: high_thresh [in]高阈值,所有梯度幅值高于此值的点认为是边缘点 * * 过客 && 386520874@qq.com && 2015.03.28 */ int CCannyEdgeDetection::Sobel(BITMAP &bitmap_src, BITMAP &bitmap_dst, double low_thresh, double high_thresh) { //-------------1. 输入参数检查----------------------- BITMAP bitmap1 = bitmap_src; unsigned char* pBits1 = static_cast<unsigned char*>(bitmap1.bmBits); int bpp1 = bitmap1.bmBitsPixel/8; //bpp代表通道的数目,一般 bpp = 3 int width2 = bitmap1.bmWidth; int height2 = bitmap1.bmHeight; CreateEmptyImage(bitmap_dst, width2, height2, bitmap1.bmBitsPixel); int bpp2 = bitmap_dst.bmBitsPixel/8; //bpp代表通道的数目,一般 bpp = 3 unsigned char* pBits2 = static_cast<unsigned char*>(bitmap_dst.bmBits); BITMAP bitmap2 = bitmap_dst; //-------------2. RGB转灰度----------------------- int* gray = new int[width2 * height2]; //保存灰度图像数据 for(int y = 0; y < bitmap1.bmHeight; y++) { for(int x = 0; x < bitmap1.bmWidth; x++) { int B = pBits1[y * bitmap1.bmWidthBytes + x * bpp1 + 0]; //Blue int G = pBits1[y * bitmap1.bmWidthBytes + x * bpp1 + 1]; //Green int R = pBits1[y * bitmap1.bmWidthBytes + x * bpp1 + 2]; //Red // int A = R * 0.299 + G * 0.587 + B * 0.114; //一般RGB2Gray公式 int A = R * 0.212671 + G * 0.715160 + B * 0.072169; //opencv的RGB2Gray公式 0.212671*R + 0.715160*G + 0.072169*B gray[y * width2 + x] = A; } } //-------------3. 计算灰度图像梯度幅值和方向----------------------- int* dx = new int[width2 * height2]; //x向偏导数 int* dy = new int[width2 * height2]; //y向偏导数 memset(dx, 0, sizeof(int) * width2 * height2); memset(dy, 0, sizeof(int) * width2 * height2); //利用Sobel算子,计算x,y方向的偏导数 for(int y = 0; y < height2; y++) { for(int x = 0; x < width2; x++) { if(x < 1 || x >= width2 -1 || y < 1 || y >= height2 - 1){continue;} //3x3的算子,图像的4条边需要跳过 int dx = -(gray[(y - 1) * width2 + (x - 1)] * 1 + gray[(y + 0) * width2 + (x - 1)] * 2 + gray[(y + 1) * width2 + (x - 1)] * 1) + (gray[(y - 1) * width2 + (x + 1)] * 1 + gray[(y + 0) * width2 + (x + 1)] * 2 + gray[(y + 1) * width2 + (x + 1)] * 1); int dy = (gray[(y - 1) * width2 + (x - 1)] * 1 + gray[(y - 1) * width2 + (x + 0)] * 2 + gray[(y - 1) * width2 + (x + 1)] * 1) - (gray[(y + 1) * width2 + (x - 1)] * 1 + gray[(y + 1) * width2 + (x + 0)] * 2 + gray[(y + 1) * width2 + (x + 1)] * 1); int Grad = abs(dx) + abs(dy); //计算梯度幅值 if(Grad >= high_thresh) { Grad = 255; }else { Grad = 0; } pBits2[y * bitmap2.bmWidthBytes + x * bpp2 + 0] = Grad; pBits2[y * bitmap2.bmWidthBytes + x * bpp2 + 1] = Grad; pBits2[y * bitmap2.bmWidthBytes + x * bpp2 + 2] = Grad; } } //计算梯度幅值和梯度的方向 //....... return 1; }
/* * test.cpp * * 过客 && 386520874@qq.com && 2016.03.26 * */ #include "CannyEdgeDetection.h" int main(int argc, char* argv[]) //专门测试字体的边缘轮廓检测 { wchar_t in[260] = TEXT("./picture/test2.png"); wchar_t out[260]; CCannyEdgeDetection ced; BITMAP bitmap; HBITMAP hBitmap; BITMAP bitmap_dst; ced.OpenImage(in, bitmap, hBitmap); ced.Sobel(bitmap, bitmap_dst, 0, 400); ced.SaveImage(TEXT("./picture/test2.sobel.0_400.png"), bitmap_dst); ced.ReleaseBitmap(bitmap_dst); ced.Canny(bitmap, bitmap_dst, 10, 100); ced.SaveImage(TEXT("./picture/test2.canny.10_100.png"), bitmap_dst); ced.ReleaseBitmap(bitmap_dst); system("pause"); return 1; }
图1.1 原始位图
图1.2canny检测[low,high]=[10,100]
图1.3 sobel检测[low,high]=[0,200]
图1.4 sobel检测[low,high]=[0,400]
------------------------------------------------测试示例2----------------------------------------------------------
图2.1 原始位图
图2.2 canny检测[low,high]=[100,400]
图2.3 sobel检测[low,high]=[0,400]
--------------------------结论-------------------------------------
通过上面两个示例图片的比较,可以发现sobel的处理比较干净利落,
而canny算法的处理结果细节比较多一点,即线条的毛边比较多。
但canny的好处是它的线宽只有1个像素,而sobel则不一定。两者各
有利弊,在图像特征检测方面很难说谁好谁坏,不过听说卷积神经网络
的特征检测用了sobel算子。
标签:
原文地址:http://blog.csdn.net/jfu22/article/details/50985651