参考 squares demo
/*
在程序里找寻矩形
*/
#ifdef _CH_
#pragma package <opencv>
#endif
#ifndef _EiC
#include "cv.h"
#include "highgui.h"
#include <stdio.h>
#include <math.h>
#include <string.h>
#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 = 0c <3c++ )
{
// extract the c-th color plane
cvSetImageCOI( timg, c+1 )
cvCopy( timg, tgray, 0 )
// try several threshold levels
for( l = 0l <Nl++ )
{
// 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 = 0i <5i++ )
{
// 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 = 0i <4i++ )
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 = 0i <squares->totali += 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 = 0names[i] != 0i++ )
{
// 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
Line(const CvPoint&head, const CvPoint&tail) // 输入的是引用 会直接改变这个点{
aa = tail.y - head.y// 这个是两点之间y的距离, (y1-y2) 这个数可正可负
bb = head.x - tail.x// 这个是两点之间x的距离 (x1-x2)
cc = head.y * tail.x - head.x * tail.y // 这个是计算直线方程的时候 y=kx+b的需要变量
if (aa <0)
{
aa = -aa // 如果a是负数变为正数 (但是对结果没有任何影响,这块绝对是浪费计算时间)
bb = -bb// 这些都要变化
cc = -cc
}
k = -aa/bb// 在这里你可以看到了 结果没有变化 如果没有上一步 这里依然是和原来一样的值
b = -cc/bb
}
这个程序坏就坏在:
1 输入的时候用到了引用,而这里不仅不能使用引用,而且要用const保护起来,因为你是求直线方程不要把点的数值改变!
2 任何变量,除了循环变量i j 等约定俗成的变量,都不要用aa bb cc这种看不出来意义的变量来写。这里面的aa bb cc 完全可以 用 x_differ y_differ 等能看懂的变量表示
3 其实这个函数很错 你知道吗?连个返回值都没有啊 拜托
霍夫变换(Hough Transform)是图像处理领域中,从图像中识别几何形状的基本方法之一。主要识别具有某些相同特征的几何形状,例如直线,圆形,本篇博客的目标就是从黑白图像中识别出直线。
翻阅霍夫直线变换的原理时候,橡皮擦觉得原理部分需要先略过,否则很容易在这个地方陷进去,但是问题来了,这个原理略过了,直接应用函数,里面有些参数竟然看不懂。例如极坐标,角度扫描范围,这种函数就属于绕不过去的知识点了,所以本文转移方向,死磕原理,下面的博文将语无伦次的为你展示如何学习原理知识。
因为数学知识的贫乏,所以在学习阶段会涉及到很多基础概念的学习,一起来吧。
首先找到相对官方的资料,打开该 地址
下面是一个数学小白对原理的学习经验。
教材说:众所周知,一条直线在图像二维空间可由两个变量表示。
抱歉,小白还真不知道……即使学习过,这些年也早已经还给老师了。
一开始难道要学习笛卡尔坐标系,不,你低估小白的能力了,我第一个查询的是 θ 读作 西塔 ,是一个希腊字母。
什么是笛卡尔坐标系?
这个比较简单,直角坐标系。
斜率和截距
斜率,亦称“角系数”,表示一条直线相对于横坐标轴的倾斜程度。
一条直线与某平面直角坐标系横坐标轴正半轴方向的夹角的正切值即该直线相对于该坐标系的斜率。
如果直线与 x 轴互相垂直,直角的正切直无穷大,故此直线不存在斜率。
对于一次函数 y=kx+b , k 就是该函数图像的斜率。
在学习的时候,也学到如下内容:
截距:对 x 的截距就是 y=0 时, x 的值,对 y 的截距就是 x=0 时, y 的值,
截距就是直线与坐标轴的交点的横(纵)坐标。 x 截距为 a , y 截距 b ,截距式就是: x/a+y/b=1(a≠0且b≠0) 。
斜率:对于任意函数上任意一点,其斜率等于其切线与 x 轴正方向所成的角,即 k=tanα 。 ax+by+c=0中,k=-a/b 。
什么是极坐标系?
关于极坐标系,打开 百度百科 学习一下即可。
重点学到下面这个结论就行:
找资料的时候,发现一个解释的比较清楚的 博客 ,后续可以继续学习使用。
继续阅读资料,看到如下所示的图,这个图也出现在了很多解释原理的博客里面,但是图下面写了一句话
在这里直接蒙掉了,怎么就表示成极坐标系了?上面这个公式依旧是笛卡尔坐标系表示直线的方式呀,只是把 k 和 b 的值给替换掉了。
为何是这样的,具体原因可以参照下图。
<center>chou 图</center>
继续寻找关于霍夫变换的资料,找到一个新的概念 霍夫空间 。
在笛卡尔坐标系中,一条直线可以用公式表示,其中 k 和 b 是参数,表示的是斜率和截距。
接下来将方程改写为 ,这时就建立了一个基于 k - b 的笛卡尔坐标系。
此时这个新的方程在 k - b 坐标系也有一个新的直线。
你可以在纸上画出这两个方程对应的线和点,如下图所示即可。
<center>chou 图</center>
新的 k - b 坐标系就叫做霍夫空间,这时得到一个结论,图像空间 x - y 中的点对应了 霍夫空间 k - b 中的一条直线 ,即图像空间的点与霍夫空间的直线发生了对应关系。
如果在图像空间 x - y 中在增加一个点 ,那相应的该点在霍夫空间也会产生相同的点与线的对应关系,并且 A 点与 B 点产生的直线会在霍夫空间相交于一个点。而这个点的坐标值就是直线 AB 的参数。
如果到这里你掌握了,这个性质就为我们解决直线检测提供了方法,只需要把图像空间的直线对应到霍夫空间的点,然后统计交点就可以达到目的,例如图像空间中有 3 条直线,那对应到霍夫空间就会有 3 个峰值点。
遍历图像空间中的所有点,将点转换到霍夫空间,形成大量直线,然后统计出直线交会的点,每个点的坐标都是图像空间直线方程参数,这时就能得到图像空间的直线了。
上述的内容没有问题,但是存在一种情况是,当直线趋近于垂直时,斜率 k 会趋近于无穷大,这时就没有办法转换了,解决办法是使用法线来表示直线。
上文提及的斜截式如下:
通过第二个公式,可以得到下述公式:
此时,我们可以带入一些数值进行转换。
图像空间有如下的几个点:
转换后的函数,都可以在霍夫空间 θ - ρ (横坐标是 θ ,纵坐标是 ρ )进行表示。
原理这时就比较清晰了:
除了一些数学知识以外,经典的博客我们也有必要记录一下,方便后面学习的时候,进行复盘。
本部分用于记录本文中提及的相关数学原理,后续还要逐步埋坑。
今天涉及了一点点数学知识,能力限制,大家一起学习,有错误的地方,可以在评论区指出,不胜感激。
希望今天的 1 个小时(今天内容有点多,不一定可以看完),你有所收获,我们下篇博客见~
相关阅读
技术专栏
逗趣程序员
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)