目录
基元的概念
基元泛指图像中有特点的单元。常说的基元有:边缘、角点、斑点、直线段、圆、等
基元检测是图像分析的基础
边缘(Edge)检测
边缘是图像中像素灰度值发生剧烈变化而不连续的结果
边缘是赋予单个像素的一种性质,与图像函数在该像素的一个邻域内的梯度特性相关
边缘幅值:梯度的幅值
边缘方向:梯度方向旋转-90度
边缘检测既是常见基元检测的基础,也是基于边界的图像分割的第一步。
边缘检测算法
OpenCV边缘检测:Sobel、拉普拉斯算子
OpenCV边缘检测:坎尼算子算子
斑点(Blob)检测
斑点:与周围灰度有一定差别的区域
面部的雀斑
卫星照片中的一棵数
钢材X光照片中的杂质或气泡
医学图像中的细微肿块
斑点检测算法
OpenCV LoG算子:SIFT算法
OpenCV Blob特征检测算子
角点(Conner)检测
角点:物体的拐角、交叉点、 曲线上曲率最大的点等
角点的邻域是图像中信息比较丰富的区域
角点检测方法
基于边缘的方法:在小邻域内有两个不同的主边缘方向,实际图像中,孤立点、线段端点也会有类似特
性。缺点是:1)需要先提取边缘并编码,计算量大;2)局部变化对稳定性影响大。
基于灰度的方法:计算点的曲率和梯度,目前的主流
角点检测算法:
OpenCV 角点检测:Harris算子
哈夫变换-几何形状检测
基本哈夫变换:直线检测
点–线对偶性:直线所在的图像空间(记为XY)和参数空间PQ(p斜率,q截距)之间的一一映射
XY空间中的直线检测就等同于PQ空间的点检测
基本哈夫变换:曲线检测
对于任意能够用f(x,c)=0(其中x是图像点坐标矢量,c是参数矢量)表示曲线或目标轮廓,均可用类似
的方法检测,只是计算复杂度随着c维数的增加而增加,需要考虑降维
广义哈夫变换:目标检测
问题:待检目标不是参数化曲线(如正方形),而只是一组轮廓点,希望自动检测目标的存在及其中心
参考点(p,q)
广义哈夫变换能够检测到特定目标的位置(即参考点(p,q) ),或者说任意位置的待检目标都是可以发
现的,满足平移不变性
多尺度检测
万物都有其合适的尺度
原子和基本粒子:普朗克常数
集成电路:微米、纳米
人、车、树、建筑:米-厘米-毫米
地理:千米
太空:光年
多分辨率 与 尺度空间
多分辨率( 图像金字塔):(低通滤波)再下采样,多级进行形成金字塔;可能出现假结构.
尺度空间(Wikin’83):用一列单参数、宽度递增的高斯滤波器将原始信号滤波而得到的一组低频信号
;高斯核是实现尺度变换的唯一变换核,具有多种优良性质,不会引入假信号
OpenCV 尺度空间与图像金字塔
http://blog.csdn.net/xiaowei_cqu
========
这次是利用opencv来识别图片中的矩形。
其中遇到的问题主要是识别轮廓时矩形内部的形状导致轮廓不闭合。
过程如下:
1. 对输入灰度图片进行高斯滤波
2. 做灰度直方图,提取阈值,做二值化处理
3. 提取图片轮廓
4. 识别图片中的矩形
5. 提取图片中的矩形
1.对输入灰度图片进行高斯滤波
cv::Mat src = cv::imread("F:\\t13.bmp",CV_BGR2GRAY);
cv::Mat hsv;
GaussianBlur(src,hsv,cv::Size(5,5),0,0);
2.做灰度直方图,提取阈值,做二值化处理
由于给定图片,背景是黑色,矩形背景色为灰色,矩形中有些其他形状为白色,可以参考为:
提取轮廓时,矩形外部轮廓并未闭合。因此,我们需要对整幅图做灰度直方图,找到阈值,进行二值化
处理。即令像素值(黑色)小于阈值的,设置为0(纯黑色);令像素值(灰色和白色)大于阈值的,设
置为255(白色)
// Quantize the gray scale to 30 levels
int gbins = 16;
int histSize[] = {gbins};
// gray scale varies from 0 to 256
float granges[] = {0,256};
const float* ranges[] = { granges };
cv::MatND hist;
// we compute the histogram from the 0-th and 1-st channels
int channels[] = {0};
//calculate hist
calcHist( &hsv, 1, channels, cv::Mat(), // do not use mask
hist, 1, histSize, ranges,
true, // the histogram is uniform
false );
//find the max value of hist
double maxVal=0;
minMaxLoc(hist, 0, &maxVal, 0, 0);
int scale = 20;
cv::Mat histImg;
histImg.create(500,gbins*scale,CV_8UC3);
//show gray scale of hist image
for(int g=0;g(g,0);
int intensity = cvRound(binVal*255);
rectangle( histImg, cv::Point(g*scale,0),
cv::Point((g+1)*scale - 1,binVal/maxVal*400),
CV_RGB(0,0,0),
CV_FILLED );
}
cv::imshow("histImg",histImg);
//threshold processing
cv::Mat hsvRe;
threshold( hsv, hsvRe, 64, 255,cv::THRESH_BINARY);
3.提取图片轮廓
为了识别图片中的矩形,在识别之前还需要提取图片的轮廓。在经过滤波、二值化处理后,轮廓提取后
的效果比未提取前的效果要好很多。
4.识别矩形
识别矩形的条件为:图片中识别的轮廓是一个凸边形、有四个顶角、所有顶角的角度都为90度。
具体可以参考:
http://opencv-code.com/tutorials/detecting-simple-shapes-in-an-image/
vector approx;
for (size_t i = 0; i < contours.size(); i++)
{
approxPolyDP(Mat(contours[i]), approx,
arcLength(Mat(contours[i]), true)*0.02, true);
if (approx.size() == 4 &&
fabs(contourArea(Mat(approx))) > 1000 &&
isContourConvex(Mat(approx)))
{
double maxCosine = 0;
for( int j = 2; j < 5; j++ )
{
double cosine = fabs(angle(approx[j%4], approx[j-2], approx[j-1]));
maxCosine = MAX(maxCosine, cosine);
}
if( maxCosine < 0.3 )
squares.push_back(approx);
}
}
5.提取图片中的矩形
由于图片中矩形倾斜角度不太大,所以没有做倾斜校正这步操作
这是主函数提取部分代码
//get rect from image
std::vector compression_params;
compression_params.push_back(CV_IMWRITE_PNG_COMPRESSION);
compression_params.push_back(9);
for(int i=0;i
这是计算每个矩形的点x,y坐标和长宽值
int* findRectInfo(std::vector rect)
{
int rectInfo[4] = {0};
int x[4]= {0},y[4]= {0};
int maxX = 0,maxY = 0,minX = 2000,minY = 2000;
//get the rect points
for(int i=0;i<4;i++){
x[i] = rect[i].x;
y[i] = rect[i].y;
if(maxXx[i])
minX = x[i];
if(minY>y[i])
minY = y[i];
}
rectInfo[0] = minY;
rectInfo[1] = minX;
rectInfo[2] = maxY - minY;
rectInfo[3] = maxX - minX;
return rectInfo;
}
识别并提取结果为:
利用opencv识别并提取图片中的矩形
Reference:
1.http://opencv-code.com/tutorials/detecting-simple-shapes-in-an-image/
2.http://stackoverflow.com/questions/8667818/opencv-c-obj-c-detecting-a-sheet-of-paper-
square-detection
3.http://blog.csdn.net/timidsmile/article/details/8519751
4.http://blog.163.com/lee_020/blog/static/1247556020136473917915/
========
在图像处理中,Hough变换(霍夫变换)主要用来识别已知的几何形状,最常见的比如直线、线段
、圆形、椭圆、矩形等。如果要检测比较复杂的曲线图形,就需要利用广义霍夫变换。
霍夫变换的原理是根据参数空间的统计规律进行参数估计。
具体说来就是,将直角坐标系中的图形(x,y)变换到参数空间(k1,...,kn),对直角坐标系中的每
一个像素点,计算它在参数空间里的所有可能的参数向量。处理完所有像素点后,把出现次数(频率)
最多的(一个或几个)参数向量的坐标作为参数代入直角坐标方程,即检测到的图形方程。
以直线检测为例,详细讲一下步骤:(圆和直线的原理相同,只是直线的公式比较好打~)
1.图像二值化,待检测的线变为黑色,背景置为白色。既然是形状检测,这步是必不可少的。
2.假设直线的参数方程为p=x*cosa+y*sina,对于直线上的某个点(x,y)来说,变换到参数空间的
坐标就是(p,a),而且这条直线上的所有点都对应于(p,a)。对于一个固定点(x,y)来说,经过它的直线系
可以表示为p=(x^2+y^2)^1/2*sin(a+b),其中tanb=x/y,对应参数空间里的一条正弦曲线。也就是说,
图像中的一条直线对应参数空间的一点,图像中的一点对应参数空间的一条正弦曲线。
关于参数变换,我再白话几句。如果直线方程写成y=k*x-b,则对应于参数空间里的点(k,-b),这就有点
像图论中的对偶变换了。在写图的程序时有时会遇到若干半平面求交的问题(整张平面被一条直线分割
后得到两张半平面)。半平面求交关键在于找到求交后的边界(如果交集非空),既可以使用递增式算
法(在已经找到的一部分边界基础上引入下一张半平面的直线求下一步的边界),也可以使用上面提到
的参数变换方法。
比如我想求几张方向都朝上(y轴正方向)的半平面的交,我想得到的应该是一个下侧以向下凸的
折线为边界的上侧无穷的区域。我的问题关键在于找到这条下凸的折线。直线y=k*x-b做参数变换,得到
点(k,-b),所有半平面的边界直线经变换得到若干个点。这些点形成的点集存在一个凸包(包含点集的
最小凸多边形,而且该多边形每个顶点都来自点集),其中构成折线的直线所对应的点恰好是凸包的上
半部分,也就是“下包络”变换成上凸包。而求点集的上凸包可是很简单的(也是增量式算法)。
3.把参数空间分割为n*m个格子,得到参数矩阵,矩阵元(pi,aj)的初始值均为0,用来对参数计数
。计数值代表这个参数是最终结果的可能性,计数值越大,说明落在这条直线上的像素点越多,也就说
明它越有可能是我们想找到的参数。p的范围可以是[0,图像对角线长度],a的范围可以是[0,PI/2](如
果取左上角为原点的话),但要包含整个图像。
4.按照栅格顺序扫描图像,遇到黑色像素就做如下操作:
pi的i从0取到n-1,对每一个pi,把它和像素点的坐标(x,y)代入参数方程,计算得到相应的ai
,如果ai在定义域范围内(或者在图像内),将矩阵元(pi,ai)加一。
处理完所有像素后,如果想识别d条直线,就在参数矩阵中找到前d个数值最大的矩阵元,他们
的坐标作为方程参数,在直角坐标系绘制出直线就可以了。
OpenCV中提供了计算霍夫变换的库函数HoughLines和HoughLinesP,想知道怎样使用,请戳传送门
。
圆形检测的过程很类似,只是参数方程有变化,而且参数空间增加了一个维度(圆心坐标x,y和半
径r)。
霍夫变换的一个好处就是不需要图像中出现完整的圆,只要落在一个圆上的像素数量足够多,就
能正确识别。
关于误差的问题:如果待检测像素没有严格落在同一个圆上,比如构成圆的圆弧彼此有些错位,
如果依据参数点最多准则,只会识别出弧长最长的圆弧而忽略其他本来也属于同一个圆的圆弧。如果目
标是检测不止一个圆,这种误差可能会使得程序依据同一个圆上的几个圆弧识别到几个不同的圆。解决
这个问题一种方法是仍然采用参数点最多准则,但减小参数空间分割的份数,让错位圆弧的圆心落在同
一个参数矩阵元上,但这样做会使检测到的圆心位置有比较大的误差。另一种方法是仍然把参数空间细
密分割,用聚类算法寻找可能的圆心,因为错位圆弧的圆心彼此靠得很近而且计数值都很大,只要找到
这些点的外接圆圆心就可以了。
下面为了计算简便,我给出只检测一个半径为100的圆形的代码(要想采用聚类算法,只需修改第
71-81行的代码块):
#include "stdafx.h"
#include "highgui.h"
#include "cv.h"
#include
#define X_MAX 400
#define Y_MAX 400
#define TO_BE_BLACK 40
//radius of circles is known
int houghTrans_r(IplImage *src, IplImage *dst, IplImage *tmp, float r, int xstep, int
ystep)
{
int width = src->width;
int height = src->height;
int channel = src->nChannels;
int xmax = width%xstep ? width/xstep+1 : width/xstep;
int ymax = height%ystep ? height/ystep+1 : height/ystep;
int i,j,x,y;
int para[X_MAX][Y_MAX] = {0};
//i,j are in the pixel space
//x,y are in the parameter space
for(j=0; jimageData + j*src->widthStep);
for(i=0; i=0 && y=0 && y paramax)
{
paramax=para[x][y];
findx=x;
findy=y;
}
}
}
//draw the parameter space image
int ii,jj;
for(y=0; yimageData + y*tmp->widthStep);
for(x=0; x=0 && findy>=0)
{
for(j=0;jimageData+j*src->widthStep);
uchar* pout=(uchar*)(dst->imageData+j*dst->widthStep);
for(i=0;i
以下是检测示例:(左中右分别为原图像、参数空间图像和检测结果,检测结果用蓝色线绘制)
由于固定半径r,所以参数就是圆心位置(x,y),绘制的点代表圆心的可能位置,颜色越浅,可
能性越大。
检测单独的圆
在很乱的线中检测不完整的圆
检测彼此错位的圆弧(参数划分扩大为5*5)
========
http://blog.csdn.net/byxdaz/archive/2009/12/01/4912136.aspx
检测直线:cvHoughLines,cvHoughLines2
检测圆:cvHoughCircles
检测矩形:opencv中没有对应的函数,下面有段代码可以检测矩形,是通过先找直线,然后找到直线平
行与垂直的四根线。
检测直线代码:
/* This is a standalone program. Pass an image name as a first parameter of the program.
Switch between standard and probabilistic Hough transform by changing "#if 1" to "#if 0"
and back */
#include
#include
#include
int main(int argc, char** argv)
{
const char* filename = argc >= 2 ? argv[1] : "pic1.png";
IplImage* src = cvLoadImage( filename, 0 );
IplImage* dst;
IplImage* color_dst;
CvMemStorage* storage = cvCreateMemStorage(0);
CvSeq* lines = 0;
int i;
if( !src )
return -1;
dst = cvCreateImage( cvGetSize(src), 8, 1 );
color_dst = cvCreateImage( cvGetSize(src), 8, 3 );
cvCanny( src, dst, 50, 200, 3 );
cvCvtColor( dst, color_dst, CV_GRAY2BGR );
#if 0
lines = cvHoughLines2( dst, storage, CV_HOUGH_STANDARD, 1, CV_PI/180, 100, 0, 0 );
for( i = 0; i < MIN(lines->total,100); i++ )
{
float* line = (float*)cvGetSeqElem(lines,i);
float rho = line[0];
float theta = line[1];
CvPoint pt1, pt2;
double a = cos(theta), b = sin(theta);
double x0 = a*rho, y0 = b*rho;
pt1.x = cvRound(x0 + 1000*(-b));
pt1.y = cvRound(y0 + 1000*(a));
pt2.x = cvRound(x0 - 1000*(-b));
pt2.y = cvRound(y0 - 1000*(a));
cvLine( color_dst, pt1, pt2, CV_RGB(255,0,0), 3, CV_AA, 0 );
}
#else
lines = cvHoughLines2( dst, storage, CV_HOUGH_PROBABILISTIC, 1, CV_PI/180, 50, 50, 10
);
for( i = 0; i < lines->total; i++ )
{
CvPoint* line = (CvPoint*)cvGetSeqElem(lines,i);
cvLine( color_dst, line[0], line[1], CV_RGB(255,0,0), 3, CV_AA, 0 );
}
#endif
cvNamedWindow( "Source", 1 );
cvShowImage( "Source", src );
cvNamedWindow( "Hough", 1 );
cvShowImage( "Hough", color_dst );
cvWaitKey(0);
return 0;
}
检测圆代码:
#include
#include
#include
int main(int argc, char** argv)
{
IplImage* img;
if( argc == 2 && (img=cvLoadImage(argv[1], 1))!= 0)
{
IplImage* gray = cvCreateImage( cvGetSize(img), 8, 1 );
CvMemStorage* storage = cvCreateMemStorage(0);
cvCvtColor( img, gray, CV_BGR2GRAY );
cvSmooth( gray, gray, CV_GAUSSIAN, 9, 9 ); // smooth it, otherwise a lot of false
circles may be detected
CvSeq* circles = cvHoughCircles( gray, storage, CV_HOUGH_GRADIENT, 2, gray-
>height/4, 200, 100 );
int i;
for( i = 0; i < circles->total; i++ )
{
float* p = (float*)cvGetSeqElem( circles, i );
cvCircle( img, cvPoint(cvRound(p[0]),cvRound(p[1])), 3, CV_RGB(0,255,0), -1,
8, 0 );
cvCircle( img, cvPoint(cvRound(p[0]),cvRound(p[1])), cvRound(p[2]), CV_RGB
(255,0,0), 3, 8, 0 );
}
cvNamedWindow( "circles", 1 );
cvShowImage( "circles", img );
}
return 0;
}
检测矩形代码:
/*在程序里找寻矩形*/
#ifdef _CH_
#pragma package
#endif
#ifndef _EiC
#include "cv.h"
#include "highgui.h"
#include
#include
#include
#endif
int thresh = 50;
IplImage* img = 0;
IplImage* img0 = 0;
CvMemStorage* storage = 0;
CvPoint pt[4];
const char* wndname = "Square Detection Demo";
// helper function:
// finds a cosine of angle between vectors
// from pt0->pt1 and from pt0->pt2
double angle( CvPoint* pt1, CvPoint* pt2, CvPoint* pt0 )
{
double dx1 = pt1->x - pt0->x;
double dy1 = pt1->y - pt0->y;
double dx2 = pt2->x - pt0->x;
double dy2 = pt2->y - pt0->y;
return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10);
}
// returns sequence of squares detected on the image.
// the sequence is stored in the specified memory storage
CvSeq* findSquares4( IplImage* img, CvMemStorage* storage )
{
CvSeq* contours;
int i, c, l, N = 11;
CvSize sz = cvSize( img->width & -2, img->height & -2 );
IplImage* timg = cvCloneImage( img ); // make a copy of input image
IplImage* gray = cvCreateImage( sz, 8, 1 );
IplImage* pyr = cvCreateImage( cvSize(sz.width/2, sz.height/2), 8, 3 );
IplImage* tgray;
CvSeq* result;
double s, t;
// create empty sequence that will contain points -
// 4 points per square (the square's vertices)
CvSeq* squares = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvPoint), storage );
// select the maximum ROI in the image
// with the width and height divisible by 2
cvSetImageROI( timg, cvRect( 0, 0, sz.width, sz.height ));
// down-scale and upscale the image to filter out the noise
cvPyrDown( timg, pyr, 7 );
cvPyrUp( pyr, timg, 7 );
tgray = cvCreateImage( sz, 8, 1 );
// find squares in every color plane of the image
for( c = 0; c < 3; c++ )
{
// extract the c-th color plane
cvSetImageCOI( timg, c+1 );
cvCopy( timg, tgray, 0 );
// try several threshold levels
for( l = 0; l < N; l++ )
{
// hack: use Canny instead of zero threshold level.
// Canny helps to catch squares with gradient shading
if( l == 0 )
{
// apply Canny. Take the upper threshold from slider
// and set the lower to 0 (which forces edges merging)
cvCanny( tgray, gray, 0, thresh, 5 );
// dilate canny output to remove potential
// holes between edge segments
cvDilate( gray, gray, 0, 1 );
}
else
{
// apply threshold if l!=0:
// tgray(x,y) = gray(x,y) < (l+1)*255/N ? 255 : 0
cvThreshold( tgray, gray, (l+1)*255/N, 255, CV_THRESH_BINARY );
}
// find contours and store them all as a list
cvFindContours( gray, storage, &contours, sizeof(CvContour),
CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0) );
// test each contour
while( contours )
{
// approximate contour with accuracy proportional
// to the contour perimeter
result = cvApproxPoly( contours, sizeof(CvContour), storage,
CV_POLY_APPROX_DP, cvContourPerimeter(contours)*0.02, 0 );
// square contours should have 4 vertices after approximation
// relatively large area (to filter out noisy contours)
// and be convex.
// Note: absolute value of an area is used because
// area may be positive or negative - in accordance with the
// contour orientation
if( result->total == 4 &&
fabs(cvContourArea(result,CV_WHOLE_SEQ)) > 1000 &&
cvCheckContourConvexity(result) )
{
s = 0;
for( i = 0; i < 5; i++ )
{
// find minimum angle between joint
// edges (maximum of cosine)
if( i >= 2 )
{
t = fabs(angle(
(CvPoint*)cvGetSeqElem( result, i ),
(CvPoint*)cvGetSeqElem( result, i-2 ),
(CvPoint*)cvGetSeqElem( result, i-1 )));
s = s > t ? s : t;
}
}
// if cosines of all angles are small
// (all angles are ~90 degree) then write quandrange
// vertices to resultant sequence
if( s < 0.3 )
for( i = 0; i < 4; i++ )
cvSeqPush( squares,
(CvPoint*)cvGetSeqElem( result, i ));
}
// take the next contour
contours = contours->h_next;
}
}
}
// release all the temporary images
cvReleaseImage( &gray );
cvReleaseImage( &pyr );
cvReleaseImage( &tgray );
cvReleaseImage( &timg );
return squares;
}
// the function draws all the squares in the image
void drawSquares( IplImage* img, CvSeq* squares )
{
CvSeqReader reader;
IplImage* cpy = cvCloneImage( img );
int i;
// initialize reader of the sequence
cvStartReadSeq( squares, &reader, 0 );
// read 4 sequence elements at a time (all vertices of a square)
for( i = 0; i < squares->total; i += 4 )
{
CvPoint* rect = pt;
int count = 4;
// read 4 vertices
memcpy( pt, reader.ptr, squares->elem_size );
CV_NEXT_SEQ_ELEM( squares->elem_size, reader );
memcpy( pt + 1, reader.ptr, squares->elem_size );
CV_NEXT_SEQ_ELEM( squares->elem_size, reader );
memcpy( pt + 2, reader.ptr, squares->elem_size );
CV_NEXT_SEQ_ELEM( squares->elem_size, reader );
memcpy( pt + 3, reader.ptr, squares->elem_size );
CV_NEXT_SEQ_ELEM( squares->elem_size, reader );
// draw the square as a closed polyline
cvPolyLine( cpy, &rect, &count, 1, 1, CV_RGB(0,255,0), 3, CV_AA, 0 );
}
// show the resultant image
cvShowImage( wndname, cpy );
cvReleaseImage( &cpy );
}
void on_trackbar( int a )
{
if( img )
drawSquares( img, findSquares4( img, storage ) );
}
char* names[] = { "pic1.png", "pic2.png", "pic3.png",
"pic4.png", "pic5.png", "pic6.png", 0 };
int main(int argc, char** argv)
{
int i, c;
// create memory storage that will contain all the dynamic data
storage = cvCreateMemStorage(0);
for( i = 0; names[i] != 0; i++ )
{
// load i-th image
img0 = cvLoadImage( names[i], 1 );
if( !img0 )
{
printf("Couldn't load %s/n", names[i] );
continue;
}
img = cvCloneImage( img0 );
// create window and a trackbar (slider) with parent "image" and set callback
// (the slider regulates upper threshold, passed to Canny edge detector)
cvNamedWindow( wndname, 1 );
cvCreateTrackbar( "canny thresh", wndname, &thresh, 1000, on_trackbar );
// force the image processing
on_trackbar(0);
// wait for key.
// Also the function cvWaitKey takes care of event processing
c = cvWaitKey(0);
// release both images
cvReleaseImage( &img );
cvReleaseImage( &img0 );
// clear memory storage - reset free space position
cvClearMemStorage( storage );
if( c == 27 )
break;
}
cvDestroyWindow( wndname );
return 0;
}
#ifdef _EiC
main(1,"squares.c");
#endif
其它参考博客:
1、http://blog.csdn.net/superdont/article/details/6664254
2、http://hi.baidu.com/%CE%C4%BF%A1%B5%C4%CF%A3%CD
%FB/blog/item/3a5cb2079158b304738b65f2.html
#include
#include
#include
int main()
{
IplImage* src;
if( (src=cvLoadImage("5.bmp", 1)) != 0)
{
IplImage* dst = cvCreateImage( cvGetSize(src), 8, 1 );
IplImage* color_dst = cvCreateImage( cvGetSize(src), 8, 3 );
CvMemStorage* storage = cvCreateMemStorage(0);//存储检测到线段,当然可以是N*1的矩阵
数列,如果
实际的直线数量多余N,那么最大可能数目的线段被返回
CvSeq* lines = 0;
int i;
IplImage* src1=cvCreateImage(cvSize(src->width,src->height),IPL_DEPTH_8U,1);
cvCvtColor(src, src1, CV_BGR2GRAY); //把src转换成灰度图像保存在src1中,注意进行边缘检测一定
要
换成灰度图
cvCanny( src1, dst, 50, 200, 3 );//参数50,200的灰度变换
cvCvtColor( dst, color_dst, CV_GRAY2BGR );
#if 1
lines = cvHoughLines2( dst, storage, CV_HOUGH_STANDARD, 1, CV_PI/180, 150, 0, 0
);//标准霍夫变
换后两个参数为0,由于line_storage是内存空间,所以返回一个CvSeq序列结构的指针
for( i = 0; i < lines->total; i++ )
{
float* line = (float*)cvGetSeqElem(lines,i);//用GetSeqElem得到直线
float rho = line[0];
float theta = line[1];//对于SHT和MSHT(标准变换)这里line[0],line[1]是rho(与像素
相关单位的距
离精度)和theta(弧度测量的角度精度)
CvPoint pt1, pt2;
double a = cos(theta), b = sin(theta);
if( fabs(a) < 0.001 )
{
pt1.x = pt2.x = cvRound(rho);
pt1.y = 0;
pt2.y = color_dst->height;
}
else if( fabs(b) < 0.001 )
{
pt1.y = pt2.y = cvRound(rho);
pt1.x = 0;
pt2.x = color_dst->width;
}
else
{
pt1.x = 0;
pt1.y = cvRound(rho/b);
pt2.x = cvRound(rho/a);
pt2.y = 0;
}
cvLine( color_dst, pt1, pt2, CV_RGB(255,0,0), 3, 8 );
}
#else
lines = cvHoughLines2( dst, storage, CV_HOUGH_PROBABILISTIC, 1, CV_PI/180, 80, 30,
10 );
for( i = 0; i < lines->total; i++ )
{
CvPoint* line = (CvPoint*)cvGetSeqElem(lines,i);
cvLine( color_dst, line[0], line[1], CV_RGB(255,0,0), 3, 8 );
}
#endif
cvNamedWindow( "Source", 1 );
cvShowImage( "Source", src );
cvNamedWindow( "Hough", 1 );
cvShowImage( "Hough", color_dst );
cvWaitKey(0);
}
}
line_storage
检测到的线段存储仓. 可以是内存存储仓 (此种情况下,一个线段序列在存储仓中被创建,并且由函数
返回),或者是包含线段参数的特殊类型(见下面)的具有单行/单列的矩阵(CvMat*)。矩阵头为函数所
修改,使得它的 cols/rows 将包含一组检测到的线段。如果 line_storage 是矩阵,而实际线段的数目
超过矩阵尺寸,那么最大可能数目的线段被返回(线段没有按照长度、可信度或其它指标排序).
method
Hough 变换变量,是下面变量的其中之一:
CV_HOUGH_STANDARD - 传统或标准 Hough 变换. 每一个线段由两个浮点数 (ρ, θ) 表示,其中 ρ 是
直线与原点 (0,0) 之间的距离,θ 线段与 x-轴之间的夹角。因此,矩阵类型必须是 CV_32FC2 type.
CV_HOUGH_PROBABILISTIC - 概率 Hough 变换(如果图像包含一些长的线性分割,则效率更高). 它返回
线段分割而不是整个线段。每个分割用起点和终点来表示,所以矩阵(或创建的序列)类型是
CV_32SC4.
CV_HOUGH_MULTI_SCALE - 传统 Hough 变换的多尺度变种。线段的编码方式与 CV_HOUGH_STANDARD 的一
致。
rho
与象素相关单位的距离精度
theta
弧度测量的角度精度
threshold
阈值参数。如果相应的累计值大于 threshold, 则函数返回的这个线段.
param1
第一个方法相关的参数:
对传统 Hough 变换,不使用(0).
对概率 Hough 变换,它是最小线段长度.
对多尺度 Hough 变换,它是距离精度 rho 的分母 (大致的距离精度是 rho 而精确的应该是 rho /
param1 ).
param2
第二个方法相关参数:
对传统 Hough 变换,不使用 (0).
对概率 Hough 变换,这个参数表示在同一条直线上进行碎线段连接的最大间隔值(gap), 即当同一条直
线上的两条碎线段之间的间隔小于param2时,将其合二为一。
对多尺度 Hough 变换,它是角度精度 theta 的分母 (大致的角度精度是 theta 而精确的角度应该是
theta / param2).
函数 cvHoughLines2 实现了用于线段检测的不同 Hough 变换方法. Example. 用 Hough transform 检
测线段
3、http://www.opencv.org.cn/index.php/Hough%E7%BA%BF%E6%AE%B5%E6%A3%80%E6%B5%8B
========
OpenCV支持大量的轮廓、边缘、边界的相关函数,相应的函数有moments、HuMoments、findContours、
drawContours、approxPolyDP、arcLength、boundingRect、contourArea、convexHull、fitEllipse、
fitLine、isContourConvex、minAreaRect、minEnclosingCircle、mathcShapes、pointPolygonTest。
还有一些c版本的针对老版本的数据结构的函数比如cvApproxChains、cvConvexityDefects。这里先介绍
一些我用过的函数,以后用到再陆续补充。
OpenCV里支持很多边缘提取的办法,可是如何在一幅图像里得到轮廓区域的参数呢,这就需要用到
findContours函数,这个函数的原型为:
//C++:
void findContours(InputOutputArray image, OutputArrayOfArrays contours, OutputArray
hierarchy, int mode, int method, Point offset=Point())
void findContours(InputOutputArray image, OutputArrayOfArrays contours, int mode, int
method, Point offset=Point())
[cpp] view plain copy
//C++:
void findContours(InputOutputArray image, OutputArrayOfArrays contours, OutputArray
hierarchy, int mode, int method, Point offset=Point())
void findContours(InputOutputArray image, OutputArrayOfArrays contours, int mode, int
method, Point offset=Point())
这里介绍下该函数的各个参数:
输入图像image必须为一个2值单通道图像
contours参数为检测的轮廓数组,每一个轮廓用一个point类型的vector表示
hiararchy参数和轮廓个数相同,每个轮廓contours[ i ]对应4个hierarchy元素hierarchy[ i ][ 0 ]
~hierarchy[ i ][ 3 ],分别表示后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号,如果没有
对应项,该值设置为负数。
mode表示轮廓的检索模式
CV_RETR_EXTERNAL表示只检测外轮廓
CV_RETR_LIST检测的轮廓不建立等级关系
CV_RETR_CCOMP建立两个等级的轮廓,上面的一层为外边界,里面的一层为内孔的边界信息。如果内孔内
还有一个连通物体,这个物体的边界也在顶层。
CV_RETR_TREE建立一个等级树结构的轮廓。具体参考contours.c这个demo
method为轮廓的近似办法
CV_CHAIN_APPROX_NONE存储所有的轮廓点,相邻的两个点的像素位置差不超过1,即max(abs(x1-x2)
,abs(y2-y1))==1
CV_CHAIN_APPROX_SIMPLE压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标,例
如一个矩形轮廓只需4个点来保存轮廓信息
CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似算法
offset表示代表轮廓点的偏移量,可以设置为任意值。对ROI图像中找出的轮廓,并要在整个图像中进行
分析时,这个参数还是很有用的。
具体应用参考sample文件夹下面的squares.cpp这个demo
findContours后会对输入的2值图像改变,所以如果不想改变该2值图像,需创建新mat来存放,
findContours后的轮廓信息contours可能过于复杂不平滑,可以用approxPolyDP函数对该多边形曲线做
适当近似
contourArea函数可以得到当前轮廓包含区域的大小,方便轮廓的筛选
findContours经常与drawContours配合使用,用来将轮廓绘制出来。其中第一个参数image表示目标图像
,第二个参数contours表示输入的轮廓组,每一组轮廓由点vector构成,第三个参数contourIdx指明画
第几个轮廓,如果该参数为负值,则画全部轮廓,第四个参数color为轮廓的颜色,第五个参数
thickness为轮廓的线宽,如果为负值或CV_FILLED表示填充轮廓内部,第六个参数lineType为线型,第
七个参数为轮廓结构信息,第八个参数为maxLevel
得到了复杂轮廓往往不适合特征的检测,这里再介绍一个点集凸包络的提取函数convexHull,输入参数
就可以是contours组中的一个轮廓,返回外凸包络的点集
还可以得到轮廓的外包络矩形,使用函数boundingRect,如果想得到旋转的外包络矩形,使用函数
minAreaRect,返回值为RotatedRect;也可以得到轮廓的外包络圆,对应的函数为minEnclosingCircle
;想得到轮廓的外包络椭圆,对应的函数为fitEllipse,返回值也是RotatedRect,可以用ellipse函数
画出对应的椭圆
如果想根据多边形的轮廓信息得到多边形的多阶矩,可以使用类moments,这个类可以得到多边形和光栅
形状的3阶以内的所有矩,类内有变量m00,m10,m01,m20,m11,m02,m30,m21,m12,m03,比如多边
形的质心为 x = m10 / m00,y = m01 / m00。
如果想获得一点与多边形封闭轮廓的信息,可以调用pointPolygonTest函数,这个函数返回值为该点距
离轮廓最近边界的距离,为正值为在轮廓内部,负值为在轮廓外部,0表示在边界上。
========
形状是当我们看到物体时最开始的印象之一,这一章我们将赋予计算机这种能力。识别图像里的形状是
通常是做决策时一个重要步骤。形状是由图像的轮廓形成的,所以理论上形状识别是通常在边缘或轮廓
检测后的步骤。
所以,我们将首先讨论从图像里提取轮廓,然后再开始讨论形状。将会包含:
?霍夫变换,可以使我们检测图像里的常规形状如线条和圆形。
?随机样本一致性(RANSAC),一个广泛使用的可以确定数据点来匹配特定模型的框架。我们将编写算法
代码来检测图像里的椭圆。
?对象周围的绑定盒,绑定椭圆和凸形外壳的计算。
?形状匹配。
轮廓
轮廓和边缘有一个显著区别。边缘是图像亮度梯度的局部极大值集合。我们也看到,这些梯度极大值不
全是在物体的轮廓上而且他们是非常有噪声的。Canny边缘有一点不同,更像轮廓一些,因为在梯度极大
值提取后经过一些后处理步骤。轮廓,相对而言,是一系列相连的点,更可能落在物体的外框上。
OpenCV的轮廓提取基于二值图像(像Canny边缘检测的输出或对Scharr边缘做阈值处理或者一张黑白图)
然后提取边缘点连接的层次结构。组织层次使得位于数结构更高的轮廓更有可能是物体的轮廓,然而低
位的轮廓更有可能是噪声边缘和“洞口”的轮廓以及噪声块。
实现这些特性的函数叫findContours()然后它使用了由S.Suzuki和K.Abe在“数字二值图像的基于边界跟
随的拓扑结构分析”一文中描述的算法来提取轮廓并排列层次结构。文中描述了决定层次结构的详细规
则,简而言之呢,当一个轮廓围绕着另一个轮廓的时候被认为是那个轮廓的“父亲”。
为了更实际地显示我们所说的层次结构呢,我们将编写一个程序,见例6-1,使用了我们最喜欢的工具,
滑块,来选择要显示层次结构的级别值。注意该函数仅接受一个二值图像为输入。从普通图像得到二值
图的方式有:
?通过threshold()或adaptiveThreshold()来阈值处理
?使用inRange()检查像素值边界
?Canny边缘
?Scharr边缘做阈值处理
例 6-1 程序展现层次轮廓提取
// Program to illustrate hierarchical contour extraction
// Author: Samarth Manoj Brahmbhatt, University of Pennsylvania
#include
#include
#include
using namespace std;
using namespace cv;
Mat img;
vector
vector
int levels = 0;
void on_trackbar(int, void *) {
if(contours.empty()) return;
Mat img_show = img.clone();
// Draw contours of the level indicated by slider
drawContours(img_show, contours, -1, Scalar(0, 0, 255), 3, 8, heirarchy, levels);
imshow("Contours", img_show);
}
int main() {
img = imread("circles.jpg");
Mat img_b;
cvtColor(img, img_b, CV_RGB2GRAY);
Mat edges;
Canny(img_b, edges, 50, 100);
// Extract contours and heirarchy
findContours(edges, contours, heirarchy, CV_RETR_TREE, CV_CHAIN_APPROX_NONE);
namedWindow("Contours");
createTrackbar("levels", "Contours", &levels, 15, on_trackbar);
// Initialize by drawing the top level contours (as 'levels' is initialized to 0)
on_trackbar(0, 0);
while(char(waitKey(1)) != 'q') {}
return 0;
}
注意每个轮廓是一个STL向量里的点。所以,储存轮廓的数据结构是一个含点向量的向量。层次结构是一
个含四整数向量的向量(注:即向量的元素也是向量)。对每个轮廓来说,它的层次结构位置表示为四
个整数值:他们是轮廓向量基于0的索引分别指示 下一位置(同等级),上一个(同等级),父级,以
及第一个子轮廓。假使其中任意一个不存在(比如,如果一个轮廓没有父轮廓),对应的整数值则为负
值。同时注意drawContours()函数根据层次结构和绘制允许最大层次等级来通过绘制轮廓修改输入图片
。
图6-1显示了一张简图上的不同等级的轮廓。
\\
\
图6-1 不同等级的轮廓层次
经常和findContours()一起使用的一个函数是approxPolyDP()。approxPolyDP()用另一条顶点较少的曲
线来逼近一条曲线或者一个多边形,这样两条曲线之间的距离小于或等于指定的精度。同时也有使闭合
逼近曲线的选项(那就是说,起始点和终止点相同)
点-多边形测试
我们先暂且来介绍一个有趣的特性:点-多边形测试。你也许猜到了,pointPolygonTest()函数判定一个
点是否在一个多边形内。如果你开启 measureDist标签的话它也会返回该点到轮廓最近点的有符号欧式
距离。如果点在曲线内,距离则为正,外则负,点在轮廓上则为零。如果标签关闭的话,相应的距离则
被替换为+1,-1和0。
让我们来做一个程序来演示点-多边形和闭合曲线逼近的新知识——一个寻找图像上用户点击点相近的最
小闭合轮廓的程序。同时它也演示了轮廓层次的导引。代码见例6-2
例6-2 寻找点击点围绕的最小轮廓
<"http://www.2cto.com/kf/ware/vc/" target="_blank"
class="keylink">vcD4KPHByZSBjbGFzcz0="brush:java;">// Program to find the smallest contour
that surrounds the clicked point // Author: Samarth Manoj Brahmbhatt, University of
Pennsylvania #include #include #include using namespace std; using namespace cv; Mat
img_all_contours; vector > closed_contours; vector heirarchy; // Function to approximate
contours by closed contours vector > make_contours_closed(vector > contours) { vector >
closed_contours; closed_contours.resize(contours.size()); for(int i = 0; i < contours.size
(); i++) approxPolyDP(contours[i], closed_contours[i], 0.1, true); return closed_contours;
} // Function to return the index of smallest contour in 'closed_contours' surrounding the
clicked point int smallest_contour(Point p, vector > contours, vector heirarchy) { int idx
= 0, prev_idx = -1; while(idx >= 0) { vector c = contours[idx]; // Point-polgon test double
d = pointPolygonTest(c, p, false); // If point is inside the contour, check its children
for an even smaller contour... if(d > 0) { prev_idx = idx; idx = heirarchy[idx][2]; } //
...else, check the next contour on the same level else idx = heirarchy[idx][0]; } return
prev_idx; } void on_mouse(int event, int x, int y, int, void *) { if(event !=
EVENT_LBUTTONDOWN) return; // Clicked point Point p(x, y); // Find index of smallest
enclosing contour int contour_show_idx = smallest_contour(p, closed_contours, heirarchy);
// If no such contour, user clicked outside all contours, hence clear image if
(contour_show_idx < 0) { imshow("Contours", img_all_contours); return; } // Draw the
smallest contour using a thick red line vector > contour_show; contour_show.push_back
(closed_contours[contour_show_idx]); if(!contour_show.empty()) { Mat img_show =
img_all_contours.clone(); drawContours(img_show, contour_show, -1, Scalar(0, 0, 255), 3);
imshow("Contours", img_show); } } int main() { Mat img = imread("circles.jpg");
img_all_contours = img.clone(); Mat img_b; cvtColor(img, img_b, CV_RGB2GRAY); Mat edges;
Canny(img_b, edges, 50, 100); // Extract contours and heirarchy vector > contours;
findContours(edges, contours, heirarchy, CV_RETR_TREE, CV_CHAIN_APPROX_NONE); // Make
contours closed so point-polygon test is valid closed_contours = make_contours_closed
(contours); // Draw all contours usign a thin green line drawContours(img_all_contours,
closed_contours, -1, Scalar(0, 255, 0)); imshow("Contours", img_all_contours); // Mouse
callback setMouseCallback("Contours", on_mouse); while(char(waitKey(1)) != 'q') {} return
0; } 假设 idx为轮廓在点向量的向量中的索引而hierarchy代表层次的话:
? hierarchy[idx][0] 返回同等级层次结构的下一个轮廓索引
? hierarchy[idx][1] 返回同等级层次结构的上一个轮廓索引
? hierarchy[idx][2] 返回第一个子轮廓的索引
? hierarchy[idx][3] 返回父轮廓的索引
如果其中一个轮廓不存在,返回索引为负值。
程序运行的截图如图6-2所示
图 6-2 最小封闭轮廓的应用
OpenCV也提供了一些函数检查其中的一些属性来帮助你过滤噪声图像的轮廓。如表6-1
表6-1 OpenCV轮廓后处理函数
函数 描述
ArcLength() 查找轮廓长度
ContourArea() 查找轮廓区域和方向
BoundingRect() 计算轮廓的垂直边界矩形
ConvexHull() 计算轮廓围绕的凸形壳
IsContourConvex() 测试轮廓的凸性
MinAreaRect() 计算围绕轮廓的最小旋转矩形
MinEnclosingCircle() 查找围绕轮廓的最小区域圆形
FitLine() 基于轮廓匹配一条线(最小二乘)
========
//正方形检测源码
//载入数张包含各种形状的图片,检测出其中的正方形
#include "cv.h"
#include "highgui.h"
#include
#include
#include
#include
int thresh = 50;
IplImage* img =NULL;
IplImage* img0 = NULL;
CvMemStorage* storage =NULL;
const char * wndname = "正方形检测 demo";
//angle函数用来返回(两个向量之间找到角度的余弦值)
double angle( CvPoint* pt1, CvPoint* pt2, CvPoint* pt0 )
{
double dx1 = pt1->x - pt0->x;
double dy1 = pt1->y - pt0->y;
double dx2 = pt2->x - pt0->x;
double dy2 = pt2->y - pt0->y;
return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10);
}
// 返回图像中找到的所有轮廓序列,并且序列存储在内存存储器中
CvSeq* findSquares4( IplImage* img, CvMemStorage* storage )
{
CvSeq* contours;
int i, c, l, N = 11;
CvSize sz = cvSize( img->width & -2, img->height & -2 );
IplImage* timg = cvCloneImage( img );
IplImage* gray = cvCreateImage( sz, 8, 1 );
IplImage* pyr = cvCreateImage( cvSize(sz.width/2, sz.height/2), 8, 3 );
IplImage* tgray;
CvSeq* result;
double s, t;
// 创建一个空序列用于存储轮廓角点
CvSeq* squares = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvPoint), storage );
cvSetImageROI( timg, cvRect( 0, 0, sz.width, sz.height ));
// 过滤噪音
cvPyrDown( timg, pyr, 7 );
cvPyrUp( pyr, timg, 7 );
tgray = cvCreateImage( sz, 8, 1 );
// 红绿蓝3色分别尝试提取
for( c = 0; c < 3; c++ )
{
// 提取 the c-th color plane
cvSetImageCOI( timg, c+1 );
cvCopy( timg, tgray, 0 );
// 尝试各种阈值提取得到的(N=11)
for( l = 0; l < N; l++ )
{
// apply Canny. Take the upper threshold from slider
// Canny helps to catch squares with gradient shading
if( l == 0 )
{
cvCanny( tgray, gray, 0, thresh, 5 );
//使用任意结构元素膨胀图像
cvDilate( gray, gray, 0, 1 );
}
else
{
// apply threshold if l!=0:
cvThreshold( tgray, gray, (l+1)*255/N, 255, CV_THRESH_BINARY );
}
// 找到所有轮廓并且存储在序列中
cvFindContours( gray, storage, &contours, sizeof(CvContour),
CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0) );
// 遍历找到的每个轮廓contours
while( contours )
{
//用指定精度逼近多边形曲线
result = cvApproxPoly( contours, sizeof(CvContour), storage,
CV_POLY_APPROX_DP, cvContourPerimeter(contours)*0.02, 0 );
if( result->total == 4 &&
fabs(cvContourArea(result,CV_WHOLE_SEQ)) > 500 &&
fabs(cvContourArea(result,CV_WHOLE_SEQ)) < 100000 &&
cvCheckContourConvexity(result) )
{
s = 0;
for( i = 0; i < 5; i++ )
{
// find minimum angle between joint edges (maximum of cosine)
if( i >= 2 )
{
t = fabs(angle(
(CvPoint*)cvGetSeqElem( result, i ),
(CvPoint*)cvGetSeqElem( result, i-2 ),
(CvPoint*)cvGetSeqElem( result, i-1 )));
s = s > t ? s : t;
}
}
// if 余弦值 足够小,可以认定角度为90度直角
//cos0.1=83度,能较好的趋近直角
if( s < 0.1 )
for( i = 0; i < 4; i++ )
cvSeqPush( squares,
(CvPoint*)cvGetSeqElem( result, i ));
}
// 继续查找下一个轮廓
contours = contours->h_next;
}
}
}
cvReleaseImage( &gray );
cvReleaseImage( &pyr );
cvReleaseImage( &tgray );
cvReleaseImage( &timg );
return squares;
}
//drawSquares函数用来画出在图像中找到的所有正方形轮廓
void drawSquares( IplImage* img, CvSeq* squares )
{
CvSeqReader reader;
IplImage* cpy = cvCloneImage( img );
int i;
cvStartReadSeq( squares, &reader, 0 );
// read 4 sequence elements at a time (all vertices of a square)
for( i = 0; i < squares->total; i += 4 )
{
CvPoint pt[4], *rect = pt;
int count = 4;
// read 4 vertices
CV_READ_SEQ_ELEM( pt[0], reader );
CV_READ_SEQ_ELEM( pt[1], reader );
CV_READ_SEQ_ELEM( pt[2], reader );
CV_READ_SEQ_ELEM( pt[3], reader );
// draw the square as a closed polyline
cvPolyLine( cpy, &rect, &count, 1, 1, CV_RGB(0,255,0), 2, CV_AA, 0 );
}
cvShowImage( wndname, cpy );
cvReleaseImage( &cpy );
}
char* names[] = { "pic1.png", "pic2.png", "pic3.png",
"pic4.png", "pic5.png", "pic6.png","pic7.png","pic8.png",
"pic9.png","pic10.png","pic11.png","pic12.png", 0 };
int main(int argc, char** argv)
{
int i, c;
storage = cvCreateMemStorage(0);
for( i = 0; names[i] != 0; i++ )
{
img0 = cvLoadImage( names[i], 1 );
if( !img0 )
{
cout<<"不能载入"<
}
img = cvCloneImage( img0 );
cvNamedWindow( wndname, 1 );
// find and draw the squares
drawSquares( img, findSquares4( img, storage ) );
c = cvWaitKey(0);
cvReleaseImage( &img );
cvReleaseImage( &img0 );
cvClearMemStorage( storage );
if( (char)c == 27 )
break;
}
cvDestroyWindow( wndname );
return 0;
}
========
线段检测主要运用Hough变换,Hough变换是图像处理中从图像中识别几何形状的基本方法之一,应用很
广泛,也有很多改进算法。主要用来从图像中分离出具有某种相同特征的几何形状(如,直线,圆等)
。最基本的霍夫变换是从黑白图像中检测直线(线段)。
在OpenCV编程中,实现线段检测主要使用cvHoughLines2函数。
函数原型:
CvSeq* cvHoughLines2(
CvArr* image,
void* line_storage,
int method,
double rho,
double theta,
int threshold,
double param1=0, double param2=0
);
参数说明:
第一个参数表示输入图像,必须为二值图像(黑白图)。
第二个参数表示存储容器,可以传入CvMemStorage类型的指针。
第三个参数表示变换变量,可以取下面的值:
CV_HOUGH_STANDARD - 传统或标准 Hough 变换. 每一个线段由两个浮点数 (ρ, θ) 表示,其中 ρ
是线段与原点 (0,0) 之间的距离,θ 线段与 x-轴之间的夹角。
CV_HOUGH_PROBABILISTIC - 概率 Hough 变换(如果图像包含一些长的线性分割,则效率更高)。它返
回线段分割而不是整个线段。每个分割用起点和终点来表示。
CV_HOUGH_MULTI_SCALE - 传统 Hough 变换的多尺度变种。线段的编码方式与 CV_HOUGH_STANDARD 的
一致。
第四个参数表示与象素相关单位的距离精度。
第五个参数表示弧度测量的角度精度。
第六个参数表示检测线段的最大条数,如果已经检测这么多条线段,函数返回。
第七个参数与第三个参数有关,其意义如下:
对传统 Hough 变换,不使用(0).
对概率 Hough 变换,它是最小线段长度.
对多尺度 Hough 变换,它是距离精度 rho 的分母 (大致的距离精度是 rho 而精确的应该是 rho /
param1 ).
第八个参数与第三个参数有关,其意义如下:
对传统 Hough 变换,不使用 (0).
对概率 Hough 变换,这个参数表示在同一条线段上进行碎线段连接的最大间隔值(gap), 即当同一条
线段上的两条碎线段之间的间隔小于param2时,将其合二为一。
对多尺度 Hough 变换,它是角度精度 theta 的分母 (大致的角度精度是 theta 而精确的角度应该是
theta / param2)。
示例程序:
hough.cpp
#include
#include
#include
#include
#include
using namespace std;
int main (int argc, char **argv)
{
const char *pstrWindowsSrcTitle = "initial";
const char *pstrWindowsLineName = "hough";
IplImage *pSrcImage = cvLoadImage("hough.jpg", CV_LOAD_IMAGE_UNCHANGED);
IplImage *pGrayImage = cvCreateImage(cvGetSize(pSrcImage), IPL_DEPTH_8U, 1);
cvCvtColor(pSrcImage, pGrayImage, CV_BGR2GRAY);
IplImage *pCannyImage = cvCreateImage(cvGetSize(pSrcImage), IPL_DEPTH_8U, 1);
cvCanny(pGrayImage, pCannyImage, 30, 90);
CvMemStorage *pcvMStorage = cvCreateMemStorage();
double fRho = 1;
double fTheta = CV_PI / 180;
int nMaxLineNumber = 50; //最多检测条直线
double fMinLineLen = 50; //最小线段长度
double fMinLineGap = 10; //最小线段间隔
CvSeq *pcvSeqLines = cvHoughLines2(pCannyImage, pcvMStorage, CV_HOUGH_PROBABILISTIC, fRho,
fTheta, nMaxLineNumber, fMinLineLen, fMinLineGap);
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);
}
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;
}
makefile:
INCLUDE = $(shell pkg-config --cflags opencv)
LIBS = $(shell pkg-config --libs opencv)
SOURCES = hough.cpp
# 目标文件
OBJECTS = $(SOURCES:.cpp=.o)
# 可执行文件
TARGET = hough
$(TARGET):$(OBJECTS)
g++ -o $(TARGET) $(OBJECTS) -I $(INCLUDE) $(LIBS)
$(OBJECTS):$(SOURCES)
g++ -c $(SOURCES)
clean:
rm $(OBJECTS) $(TARGET)
# 编译规则 $@代表目标文件 $< 代表第一个依赖文件
%.o:%.cpp
g++ -I $(INCLUDE) -o $@ -c $<
所在文件夹上已有hough.jpg图片,make后执行./hough hough.jpg
========