学数字图像处理有挺长时间了,正好最近这段时间有空,学习了一下Hough变换,作了几个比较简单的东西,分享出来,希望能和大家一起学习,也希望各位能提出宝贵的意见,共同进步,文采不好,欢迎拍砖,下面正式进入正题。
Hough变换于1962年被Paul Hough提出以来,在数字图像中基本几何形状的检测与提取方面,受到了各界学者的广泛关注。Hough变换因其具有很好地鲁棒性、对图像数据的不完全性、以及检测的准确性等优点被广泛应用生产生活中。举几个最常见例子,在工业生产流水线上,纸张或铝板在出厂的过程中,破损与划痕在所难免,而这些破损与划痕普遍的形状无非就是直线和原点。我们要在减小出厂产品破损率,检测成本做到最低,最好的方法无非就是利用数字图像处理的方法了。又扯远了。。。言归正传,那么什么是Hough变换呢?一听这个Hough变换这个名字,相信很多初学者就被吓蒙了,没事,听我细细道来。
Hough变换就是在两个两个坐标系下进行的变换。
step1:假设直线方程是:y=kx+b,其中s1和s2是直线上的两个点,如图1所示。
step2:就是在进行坐标系变化把y=kx+b变成b=-xk+y。直线上的点s1(x1,y1)在转换坐标系后变为一条直线,s2(x2,y2)这个点在转换坐标系后变成另一条直线,依次类推。在变换坐标系后,Hough发现了一个规律,在变换后所有直线都相交于一个点!于是,Hough提出检测直线的问题可以转化为检测点的问题,交点的位置涵盖了原图中直线的位置信息和方向。
图1 图2
但是这还不够,我们想一下,当原始图像中有一条垂直于x轴的直线怎么办?斜率k为无穷大啊,没法坐标转化。但是问题还得解决。Hart、Dual提出了将直线换一下表达形式,即将笛卡尔直角坐标系下y=kx+b先变换一下,变换为极坐标系下的p=x*cos(theta)+y*sin(theta);(原谅我这么写,没法打公式啊)
。同上,在转换后再进行一步转换坐标系,原图像直线上的每个点都对应转换坐标的一条正弦曲线,许多点一起变换坐标系又能产生一个交点。如下所示:
图3 图4
这便是标准Hough变换检测直线的基本原理。
圆的检测是Hough变换直线检测的延伸,这里就不给原理了,直接给代码吧。
下面给出用opencv实现的代码:(vs2015+opencv3.1.0)
vs2015下载
http://www.ithome.com/html/win10/164028.htm
opencv下载
https://sourceforge.net/projects/opencvlibrary/files/opencv-win/3.1.0/opencv-3.1.0.exe/download
vs2015+opencv3.1.0的配置如下没有配置的或者配置不对的可以去看看:
http://blog.csdn.net/liu798675179/article/details/51233742
#include "stdafx.h"
#include <opencv2/opencv.hpp>
using namespace std;
#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
int main()
{
const char *pstrWindowsSrcTitle = "原图";
const char *pstrWindowsLineName = "hough变换后图像";
// 从文件中加载原图
IplImage *pSrcImage = cvLoadImage("c://pic//line.jpg", CV_LOAD_IMAGE_UNCHANGED);
// 灰度图
IplImage *pGrayImage = cvCreateImage(cvGetSize(pSrcImage), IPL_DEPTH_8U, 1);
cvCvtColor(pSrcImage, pGrayImage, CV_BGR2GRAY);
//边缘图
cvSmooth(pGrayImage, pGrayImage, CV_GAUSSIAN,5,5);
//平滑滤波
IplImage *pCannyImage = cvCreateImage(cvGetSize(pSrcImage), IPL_DEPTH_8U, 1);
cvCanny(pGrayImage, pCannyImage, 80, 90);
//线段检测(二值图像)
CvMemStorage *pcvMStorage = cvCreateMemStorage();
double fRho = 1;
double fTheta = CV_PI / 180;
int nMaxLineNumber = 5; //最多检测条直线
double fMinLineLen = 50; //最小线段长度
double fMinLineGap = 10; //最小线段间隔
CvSeq *pcvSeqLines = cvHoughLines2(pCannyImage, pcvMStorage, CV_HOUGH_PROBABILISTIC, fRho, fTheta, nMaxLineNumber, fMinLineLen, fMinLineGap);
//圆检测(灰度图)
double fMinCircleGap = pGrayImage->height / 10;
CvSeq *pcvSeqCircles = cvHoughCircles(pGrayImage, pcvMStorage, CV_HOUGH_GRADIENT, 1, fMinCircleGap,100);//每个圆由三个浮点数表示:圆心坐标(x,y)和半径
//绘制线段(用红色线条表示)
IplImage *pColorImage = cvCreateImage(cvGetSize(pSrcImage), IPL_DEPTH_8U, 3);
cvCvtColor(pCannyImage, pColorImage, CV_GRAY2BGR);
int i;
for (i = 0; i < pcvSeqLines->total; i++)
{
CvPoint* line = (CvPoint*)cvGetSeqElem(pcvSeqLines, i);
cvLine(pColorImage, line[0], line[1], CV_RGB(255, 0, 0), 2);
}
//绘制圆形(红色线段表示)
int j;
for (j = 0; j < pcvSeqCircles->total; j++)
{
float* p = (float*)cvGetSeqElem(pcvSeqCircles, j);
cvCircle(pColorImage, cvPoint(cvRound(p[0]), cvRound(p[1])), cvRound(p[2]), CV_RGB(255, 0, 0), 2);
}
cvNamedWindow(pstrWindowsSrcTitle, CV_WINDOW_AUTOSIZE);
cvShowImage(pstrWindowsSrcTitle, pSrcImage);
cvNamedWindow(pstrWindowsLineName, CV_WINDOW_AUTOSIZE);
cvShowImage(pstrWindowsLineName, pColorImage);
cvWaitKey(0);
cvReleaseMemStorage(&pcvMStorage);
cvDestroyWindow(pstrWindowsSrcTitle);
cvDestroyWindow(pstrWindowsLineName);
cvReleaseImage(&pSrcImage);
cvReleaseImage(&pGrayImage);
cvReleaseImage(&pCannyImage);
cvReleaseImage(&pColorImage);
return 0;
}
实验结果如下: