实验要求
- 边缘检测
- 霍夫线变换
- 霍夫圆变换
算法实现
边缘检测
本代码采用LoG边缘检测算子
- 算子与图像卷积
- 寻找零交叉点,即边缘点
霍夫线变换
-
将彩色图像转化为灰度图,并对灰度图做边缘检测得到二值边缘图
-
参数空间离散化:对直线方程的参数 ( r , θ ) (r,\theta) (r,θ)离散化,并给出 ( r m i n , r m a x ) (r_{min},r_{max}) (rmin,rmax)和 ( θ m i n , θ m a x ) (\theta_{min},\theta_{max}) (θmin,θmax),划分为有限个等间距的离散值,使参数空间量化为一个个等大小网格。
-
设置累加器A:为每个网格单元设置累加器。A表示 ( r m i n : r m a x , θ m i n : θ m a x ) (r_{min}:r_{max},\theta_{min}:\theta_{max}) (rmin:rmax,θmin:θmax),初始为0。
-
对图像空间中每个像素点坐标值(x,y),计算参数空间对应的曲线方程,将该曲线穿过的格子的计数值加一。
-
最后,遍历A(i,j)中的寻找累加计数大于某阈值M格子,其坐标 ( r m , θ m ) (r_m, \theta_m) (rm,θm)即为检测到的直线参数。利用cvtColor()将二值边缘图转换为RGB图,并将检测到的所有直线在图中画出来。
霍夫圆变换
-
将彩色图像转化为灰度图,并对灰度图做边缘检测得到二值边缘图
-
设定检测半径和角度范围 ( r m i n : r m a x , θ m i n : θ m a x ) (r_{min}:r_{max},\theta_{min}:\theta_{max}) (rmin:rmax,θmin:θmax),ax),设置累加器A(x,y,r)
-
对图像空间中每个像素点坐标值(x,y),在指定半径和圆心角范围内,计算参数空间对应的圆心,累加器A(x_center, y_center,r)加一。
-
最后,遍历累加器中的寻找累加计数大于某阈值M格子,其坐标 ( x c e n t e r , y c e n t e r , r ) (x_center, y_center,r) (xcenter,ycenter,r)即为检测到的圆参数。利用cvtColor()将二值边缘图转换为RGB图,并将检测到的所有直线在图中画出来。
显然这样的方法效率很低,时间和空间复杂度都很高,所以我们用梯度法优化。
-
用sobel算子计算出整张图的梯度
-
对于图像中每个点,在其梯度方向上的点的累加器A(x,y)加一
-
遍历A(i,j)中的寻找累加计数大于某阈值M格子,其坐标 ( x , y ) (x,y) (x,y)即为可能是圆心的点。
-
对于每个圆心计算每个像素点与他的距离,并塞入数组R,然后寻找最多的半径R的数量,如果大于阈值那么就以该圆心半径画圆。若俩个圆心距离很近,那么就选其一。
代码实现
#include <stdlib.h>
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
#include <vector>
#define LINEAR_X 0
#define SIZE 5
#define PI 3.1415926
#define oo 1e9+7
#define theta_cnt 500
using namespace cv;
using namespace std;
//边缘检测//
//边缘检测函数 LoG边缘检测算子
void EdgeDetector(Mat input, Mat &output){
threshold(input, input, 150, 255, THRESH_BINARY);
//imshow("threshold",input);
double Gaussian_Temp[SIZE][SIZE] = {{0,0,-1,0,0},
{0,-1,-2,-1,0},
{-1,-2,16,-2,-1},
{0,-1,-2,-1,0},
{0,0,-1,0,0}};//模板
//Mat Temp=Mat::zeros(input.size(),CV_8UC1);
int rows = input.rows;
int cols = input.cols;
double *Temp = new double[rows*cols];
for(int i=0;i<rows;i++)
{
for(int j=0;j<cols;j++)
{
double sum = 0;
for(int k=0;k<SIZE;k++)
for(int t=0;t<SIZE;t++)
{
int x=i+k-SIZE/2;
int y=j+t-SIZE/2;
if(x<0||y<0||x>=rows||y>=cols)continue;
double m=Gaussian_Temp[k][t];
sum+=m*input.at<uchar>(x,y);
}
Temp[i*cols+j]=sum;
}
}
//寻找零交叉
int dx[2][4]={{-1,0,1,-1},//上,左,左上,左下
{1,0,-1,1}};//下,右,右下,右上
int dy[2][4]={{0,-1,-1,-1},//上,左,左上,左下
{0,1,1,1}};//下,右,右下,右上
for(int i=0;i<rows;i++)
{
for(int j=0;j<cols;j++)
{
int pa,pb,num=0;
for(int k=0;k<4;k++){
int x1=i+dx[0][k],y1=j+dy[0][k];
int x2=i+dx[1][k],y2=j+dy[1][k];
if(x1<0||y1<0||x1>=rows||y1>=cols)pa=0;
else pa=Temp[x1*cols+y1];
if(x2<0||y2<0||x2>=rows||y2>=cols)pb=0;
else pb=Temp[x2*cols+y2];
if(pa*pb<0&&abs(pa-pb)>255*0.5)num++;
//cout<<pa<<" "<<pb<<endl;
}
if(num>=2)output.at<uchar>(i,j)=255;
else output.at<uchar>(i,j)=0;
}
}
}
//霍夫线变换//
Mat Hough_Line(Mat input){
Mat output=Mat::zeros(input.size(),CV_8UC3);
cvtColor(input,output,COLOR_GRAY2BGR);
int rows = input.rows;
int cols = input.cols;
int max_length = sqrt(pow(rows,2)+pow(cols,2));
int r_cnt=max_length*2;
int M = 100;//阈值
int *A = new int[r_cnt*theta_cnt];//累加器
//遍历,并累加
for(int i=0;i<rows;i++)
{
for(int j=0;j<cols;j++)
{
if(input.at<uchar>(i,j)==0)continue;
for(int k=0;k<theta_cnt;k++){
double theta=2.0*PI*k/theta_cnt;
int r=1.0*i*cos(theta)+1.0*j*sin(theta);//直线方程
if(r<-max_length||r>=max_length)continue;
A[(r+max_length)*theta_cnt+k]++;
}
}
}
//画直线
for(int i=0;i<r_cnt;i++){
for(int j=0;j<theta_cnt;j++){
if(A[i*theta_cnt+j]<M)continue;
if((i>0&&A[i*theta_cnt+j]<A[(i-1)*theta_cnt+j])
||(j>0&&A[i*theta_cnt+j]<A[i*theta_cnt+j-1]))continue;
if((i<r_cnt-1&&A[i*theta_cnt+j]<A[(i+1)*theta_cnt+j])
||(j<theta_cnt-1&&A[i*theta_cnt+j]<A[i*theta_cnt+j+1]))continue;
double theta=2.0*PI*j/theta_cnt;
int r = i-max_length;
double a = cos(theta);
double b = sin(theta);
double x1 = int(b*r + 1000*(a));
double y1 = int(a*r + 1000*(-b));
double x2 = int(b*r - 1000*(a));
double y2 = int(a*r - 1000*(-b));
line(output,Point(x1,y1),Point(x2,y2),Scalar(0,0,255),1);
}
}
return output;
}
//霍夫圆变换//
Mat calc(Mat input,double kernel[3][3],int Size){
Mat output=Mat::zeros(input.size(),CV_8UC1);
int rows = input.rows;
int cols = input.cols;
for(int i=0;i<rows;i++)
{
for(int j=0;j<cols;j++)
{
for(int k=0;k<Size;k++)
for(int t=0;t<Size;t++)
{
int x=i+k-Size/2;
int y=j+t-Size/2;
if(x<0||y<0||x>=rows||y>=cols)continue;
double m=kernel[k][t];
output.at<int>(i,j)+=m*input.at<uchar>(x,y);
}
}
}
return output;
}
Mat Hough_Circle(Mat input){
Mat output=Mat::zeros(input.size(),CV_8UC3);
cvtColor(input,output,COLOR_GRAY2BGR);
int rows = input.rows;
int cols = input.cols;
int r_max=max(rows,cols)/2;
double M = 0.1;
int A[rows][cols]={0};
double sobelx[3][3]={{1,2,1},
{0,0,0},
{-1,-2,-1}};
double sobely[3][3]={{1,0,-1},
{2,0,-2},
{1,0,-1}};
Mat dx=calc(input,sobelx,3);
Mat dy=calc(input,sobely,3);
//累加器寻找圆心
for(int i=0;i<rows;i++)
{
for(int j=0;j<cols;j++)
{
if(input.at<uchar>(i,j)==0)continue;
double vx=dx.at<int>(i,j);
double vy=dy.at<int>(i,j);
double mag=sqrt(vx*vx+vy*vy);
if(mag == 0)continue;
double sx=vx/mag;
double sy=vy/mag;
for(int t=-1;t<2;t+=2){
double x=1.0*i+sx*t;
double y=1.0*j+sy*t;
for(int r=0;r<r_max;r++,x+=sx*t,y+=sy*t)
{
int X=int(x),Y=int(y);
if(X<0||Y<0||X>=rows||Y>=cols)break;
A[X][Y]++;
}
}
}
}
//寻找可能圆心
vector<Point>centerP;
vector<Point>circleP;
for(int i=0;i<rows;i++)
{
for(int j=0;j<cols;j++)
{
if(A[i][j]<50)continue;
if((i>0&&A[i][j]<A[i-1][j])||(j>0&&A[i][j]<A[i][j-1]))continue;
if((i<rows-1&&A[i][j]<A[i+1][j])||(j<cols-1&&A[i][j]<A[i][j+1]))continue;
centerP.push_back(Point(i,j));
}
}
//对每个可能圆心,寻找可能半径
int l=centerP.size();
for(int i=0;i<l;i++){
int cx=centerP[i].x;
int cy=centerP[i].y;
int L=circleP.size();
bool flag=0;
for(int j=0;j<L;j++){
int cx1=circleP[j].x;
int cy1=circleP[j].y;
if( pow(( cx1- cx),2) + pow( cy1- cy,2) <= 9){//圆心相近
flag=1;
break;
}
}
if(flag)continue;
vector<int>R;
for(int k=0;k<rows;k++)
{
for(int t=0;t<cols;t++)
{
if(input.at<uchar>(k,t)==0)continue;
int r=sqrt(pow(cx-k,2)+pow(cy-t,2));
if(r+cx<rows&&r+cy<cols&&cx-r>=0&&cy-r>=0)R.push_back(r);
}
}
//半径数最多的就是最有可能的
if(R.size()==0)continue;
sort(R.begin(),R.end());
int startR=R[0],cnt=0,maxcnt=0,ansR=0;
for(int j=1;j<R.size();j++){
int r=R[j];
if(r-startR>2){
int nowR=(R[j-1]+startR)/2;
if(cnt*ansR>=maxcnt*nowR){
ansR=nowR;
maxcnt=cnt;
}
cnt=0;
startR=r;
}
cnt++;
}
if(maxcnt>700){
circle(output, Point(cy,cx),ansR , Scalar(0, 0, 255), 0.5);
circleP.push_back(Point(cx,cy));
}
}/**/
return output;
}
int main(int argc,char **argv){
//读取原始图像
Mat src=imread(argv[1],IMREAD_UNCHANGED);
//检查是否读取图像
if(src.empty()){
std::cout<<"Error! Input image cannot be read...\n";
return -1;
}
imshow("src",src);
// 灰度图转换
Mat garyimg;
cvtColor(src,garyimg,COLOR_BGR2GRAY);
imshow("gray",garyimg);
// 边缘检测函数
Mat frOut1=Mat::zeros(garyimg.size(),CV_8UC1);
EdgeDetector(garyimg, frOut1);
// 线检测
Mat frOut2=Hough_Line(frOut1);
// 圆检测
Mat frOut3=Hough_Circle(frOut1) ;
imshow("Edge",frOut1);
imshow("Line",frOut2);
imshow("circle",frOut3);
std::cout << "Press any key to exit...\n";
waitKey(); // Wait for key press
return 0;
}
运行结果