图像处理检测裂纹

2018-03-26 11:22:14 LemonXQ 阅读数 73673

写在前面

呼~最近开始入坑图像+机器学习了,学习的过程中遇到了不少不懂的东西,好在自学能力还可以(自恋中= =),所以断断续续也算学会了一些东西因为这段时间一直在做边缘检测和提取的工作,所以本篇就总结一下一些常见的边缘检测方法,篇幅较长,可按点查看

名词解释

图像处理中经常用到一些名词,以下列举一些:

1. 滤波

所谓滤波就是对每个像素点及其邻域点的灰度值按照一定的参数规则进行加权平均,这样可以有效滤去理想图像中叠加的高频噪声。常用的滤波有线性滤波、中值滤波、均值滤波、双边滤波、高斯滤波等。滤波有抑制噪声的作用,但这会使得图像边缘模糊。

2. 直方图

在图像处理中,经常用到直方图,如颜色直方图、灰度直方图等。直方图可以直观展现数据分布情况,如灰度直方图中,横坐标为各个灰度范围,纵坐标为处在相应范围的像素数。图像直方图不关心像素所处的空间位置,因此不受图像旋转和平移变化的影响,可以作为图像的特征。

3. 上采样

上采样即放大图像(或称图像插值(interpolating)),从而使图像可以显示在更高分辨率的显示设备上。注意对图像的缩放操作通常会影响图像的质量。上采样几乎都是采用内插值方法,即在原有图像像素的基础上在像素点之间采用合适的插值算法插入新的元素。

4. 下采样

下采样即缩小图像(或称为降采样(downsampled)),其主要目的有两个:1、使得图像符合显示区域的大小;2、生成对应图像的缩略图。

下采样原理:对于一幅图像I尺寸为M*N,对其进行s倍下采样,即得到(M/s)*(N/s)尺寸的分辨率图像,当然s应该是M和N的公约数才行,如果考虑的是矩阵形式的图像,就是把原始图像s*s窗口内的图像变成一个像素,这个像素点的值就是窗口内所有像素的均值

Canny算子

1.原理

canny算子与LoG算子类似,属于先平滑后求导数的方法,原理可分成下面4个部分:

1.1 高斯滤波

在所有滤波方法中,需要考虑的最重要的一点是如何平衡去噪与边缘检测精确之间的矛盾。实际工程经验表明,高斯函数确定的核可以提供较好的折衷方案。

高斯滤波实现方法有两种:离散化窗口滑动卷积、傅里叶变换。因前者比较常用,故下面只介绍前者。

离散化窗口滑动卷积主要利用高斯核实现,即一个奇数大小的高斯模板。常用的高斯核模板有3*3 和 5*5两种
  

其中的参数通过高斯函数计算,x2+y2表示像素点和中心像素点的距离,sigma表示标准差。

  • 注:
    • sigma如果选的过大,会加深滤波程度,从而导致图像边缘模糊,不利于下一步的边缘检测,如果过小,则滤波效果不佳
    • 计算高斯模板参数时,需要归一化处理,对于归一化的原因,有一种解释是:归一化之后,通过卷积计算出来的模板中心像素被限制到了0-255的灰度区间中。假若某一邻域内所有像素的灰度值为255,利用该模板进行卷积之后,求得的模板中心像素灰度值仍然为255;假若计算出来的高斯模板参数之和小于1,那么通过该模板进行卷积之后,模板中心像素的灰度值将小于255,偏离了实际的灰度值,产生了误差。

1.2 求梯度幅值和梯度方向

canny算子使用的卷积算子如下:

梯度幅值及梯度方向的计算如下,其中P表示x方向一阶偏导数矩阵,Q表示y方向一阶偏导数矩阵,M表示梯度幅值,θ表示梯度方向

1.3 非极大值抑制

在上面求出的梯度幅值矩阵中,值越大的元素代表其梯度越大,但它不一定是边缘像素,因此需要进行非极大值抑制,也就是寻找像素点局部最大值,将非极大值点所对应的灰度值置为0,这样可以剔除掉一大部分非边缘的点。
  

如上图,判定C点是否为8邻域内最大梯度值点,只需要判断C是否比C的梯度方向上dTmp1和dTmp2点大,是则保留,不是则将C点灰度值置0。而dTmp1和dTmp2的梯度值可以通过插值得到。

1.4 双阈值法闭合边缘

上面得到的边缘有些为假边缘,且有边缘断裂的问题,如果根据高阈值得到一个边缘图像,这样一个图像含有很少的假边缘,但是由于阈值较高,产生的图像边缘可能不闭合,因此还要采用一个低阈值,当到达轮廓的端点时,在断点的8邻域点中寻找满足低阈值的点,再根据此点收集新的边缘,直到整个图像边缘闭合。

2.实现

2.1 原始图像灰度化

img = imread('lena.jpg');
img = rgb2gray(img);

2.2 调用matlab内置函数

img_edge = edge(img,'canny');
figure;imshow(img_edge);title('canny');

Roberts、Sobel、Prewitt算子

图像的灰度值梯度可以用一阶偏导的有限差分近似计算,常用梯度算子有:

Roberts算子

梯度幅值为:

matlab中可直接调用edge(img,'roberts')

Sobel算子

梯度幅值为:

matlab中可直接调用edge(img,'sobel')

Prewitt算子

matlab中可直接调用edge(img,'prewitt')

双边滤波(Bilateral Filters)

1. 原理

传统滤波方法多多少少会有模糊边缘的缺点,而双边滤波作为一种非线性滤波器,具有在降噪平滑的同时,保持边缘的效果。该特性主要是通过在卷积的过程中组合空域(space)函数和值域(range)核函数来实现的,空域指的是像素的欧氏距离,值域指的是像素范围域中的辐射差异(如卷积核中像素与中心像素之间相似程度、颜色强度,深度距离等)。典型的核函数为高斯分布函数,如下所示:
[外链图片转存失败(img-nOZjYtOC-1562302602305)(http://my.csdn.net/uploads/201205/30/1338365238_1668.jpg)]

其中,权重系数w(i,j,k,l)取决于空域核和值域核的乘积:
[外链图片转存失败(img-gjxNA9ai-1562302602306)(http://my.csdn.net/uploads/201205/30/1338365512_2777.jpg)]

在图像的平坦区域,像素值变化很小,对应的像素值域权重接近于1,此时空域权重起主要作用,相当于进行高斯模糊;在图像的边缘区域,像素值变化很大,像素值域权重变大,从而保持了边缘的信息。

2. 实现

上述方法的时间复杂度是O(σd^2),非常耗时。论文《Fast O(1) bilateral filtering using trigonometric range kernels》,提出了用Raised cosines函数来逼近高斯值域函数,并利用一些特性把值域函数分解为一些列函数的叠加,从而实现函数的加速,而论文" A fast approximation of the bilateral filter using a signal processing approach"则提出了一种使用信号处理的方法,主要是在原有域上添加了信号强度这一维,构成了高维空间,在高维空间中进行下采样,下面的代码是作者团队编写的:

% 双边滤波函数
function output = fBilateralFilter_ReviseVer( data, edge, edgeMin, edgeMax, sigmaSpatial, sigmaRange,samplingSpatial, samplingRange )
if ~exist( 'edge', 'var' )
	edge = data;
elseif isempty( edge )
	edge = data;
end

inputHeight = size( data, 1 );
inputWidth = size( data, 2 );

if ~exist( 'edgeMin', 'var' )
	edgeMin = min( edge( : ) );
% 	warning( 'edgeMin not set!  Defaulting to: %f\n', edgeMin );
end

if ~exist( 'edgeMax', 'var' )
	edgeMax = max( edge( : ) );
% 	warning( 'edgeMax not set!  Defaulting to: %f\n', edgeMax );
end

edgeDelta = edgeMax - edgeMin;% hl- span of range

% hl- assign scale parameters in both spatial and range domain
if ~exist( 'sigmaSpatial', 'var' )
	sigmaSpatial = min( inputWidth, inputHeight ) / 16;
	fprintf( 'Using default sigmaSpatial of: %f\n', sigmaSpatial );
end
if ~exist( 'sigmaRange', 'var' )
	sigmaRange = 0.1 * edgeDelta;
	fprintf( 'Using default sigmaRange of: %f\n', sigmaRange );
end

if ~exist( 'samplingSpatial', 'var' )
	samplingSpatial = sigmaSpatial;
end

if ~exist( 'samplingRange', 'var' )
	samplingRange = sigmaRange;
end

if size( data ) ~= size( edge )
	error( 'data and edge must be of the same size' );
end

% parameters
derivedSigmaSpatial = sigmaSpatial / samplingSpatial; 
derivedSigmaRange = sigmaRange / samplingRange;
paddingXY = floor( 2 * derivedSigmaSpatial ) + 1;
paddingZ = floor( 2 * derivedSigmaRange ) + 1;

% allocate 3D grid
downsampledWidth = floor( ( inputWidth - 1 ) / samplingSpatial ) + 1 + 2 * paddingXY; % paddingXY - 控制延拓范围
downsampledHeight = floor( ( inputHeight - 1 ) / samplingSpatial ) + 1 + 2 * paddingXY;
downsampledDepth = floor( edgeDelta / samplingRange ) + 1 + 2 * paddingZ;

gridData = zeros( downsampledHeight, downsampledWidth, downsampledDepth );
gridWeights = zeros( downsampledHeight, downsampledWidth, downsampledDepth );

% compute downsampled indices
[ jj, ii ] = meshgrid( 0 : inputWidth - 1, 0 : inputHeight - 1 ); % hl- create the coordinats of xy-plane; jj - y coordinates of all pixels, ii - x coordinates of all pixels

%Compute the downsampled coordinates
di = round( ii / samplingSpatial ) + paddingXY + 1; % round: Round to nearest integer四舍五入
dj = round( jj / samplingSpatial ) + paddingXY + 1;
dz = round( ( edge - edgeMin ) / samplingRange ) + paddingZ + 1;

% hl - average sampling (box sampling)
for k = 1 : numel( dz ) % numel: Number of elements in an array
	dataZ = data( k ); % traverses the image column wise, same as di( k )
	if ~isnan(dataZ),
        dik = di( k ); %取出坐标
        djk = dj( k );
        dzk = dz( k );
        gridData( dik, djk, dzk ) = gridData( dik, djk, dzk ) + dataZ;
        gridWeights( dik, djk, dzk ) = gridWeights( dik, djk, dzk ) + 1;
	end
end

% make gaussian kernel
kernelWidth = 2 * derivedSigmaSpatial + 1;
kernelHeight = kernelWidth;
kernelDepth = 2 * derivedSigmaRange + 1;

halfKernelWidth = floor( kernelWidth / 2 );
halfKernelHeight = floor( kernelHeight / 2 );
halfKernelDepth = floor( kernelDepth / 2 );

[gridX, gridY, gridZ] = meshgrid( 0 : kernelWidth - 1, 0 : kernelHeight - 1, 0 : kernelDepth - 1 );
gridX = gridX - halfKernelWidth;
gridY = gridY - halfKernelHeight;
gridZ = gridZ - halfKernelDepth;
gridRSquared = ( gridX .* gridX + gridY .* gridY ) / ( derivedSigmaSpatial * derivedSigmaSpatial ) + ( gridZ .* gridZ ) / ( derivedSigmaRange * derivedSigmaRange );
kernel = exp( -0.5 * gridRSquared );

% convolve
blurredGridData = convn( gridData, kernel, 'same' );
blurredGridWeights = convn( gridWeights, kernel, 'same' );

% divide
blurredGridWeights( blurredGridWeights == 0 ) = -2; % avoid divide by 0, won't read there anyway  
normalizedBlurredGrid = blurredGridData ./ blurredGridWeights;
normalizedBlurredGrid( blurredGridWeights < -1 ) = 0; % put 0s where it's undefined

% upsample
[ jj, ii ] = meshgrid( 0 : inputWidth - 1, 0 : inputHeight - 1 ); % meshgrid does x, then y, so output arguments need to be reversed
% no rounding
di = ( ii / samplingSpatial ) + paddingXY + 1;
dj = ( jj / samplingSpatial ) + paddingXY + 1;
dz = ( edge - edgeMin ) / samplingRange + paddingZ + 1;

output = interpn( normalizedBlurredGrid, di, dj, dz ); % N-D data interpolation
end

Hessian特征

1. 原理

Hessian矩阵本质是是一个多元函数的二阶偏导数矩阵,描述了函数的局部曲率。关于Hessian矩阵的由来及详细推导证明见参考资料3,这里直接介绍如何得到Hessian矩阵:

  • 高斯函数

  • 求二阶偏导

  • 对原图进行卷积

  • 构成Hessian矩阵

2. 实现

% 提取Hessian特征值
function [hessianValue,Ixx,Ixy,Iyy] = edge_hessian(img)
    [m n]=size(img);
    w=4;
    sigma=1.2;
    [x y]=meshgrid(-w:w,-w:w);
    % 高斯函数对应的二阶偏导  
    Dxx = 1/(-2*pi*sigma^4)*(1-x.^2/sigma^2)*exp(-(x.^2+y.^2)/(2*sigma^2)); 
    Dyy = 1/(-2*pi*sigma^4)*(1-y.^2/sigma^2)*exp(-(x.^2+y.^2)/(2*sigma^2));
    Dxy = 1/(2*pi*sigma^6)*(x.*y)*exp(-(x.^2+y.^2)/(2*sigma^2));

    Ixx=imfilter(img,Dxx,'replicate');
    Iyy=imfilter(img,Dyy,'replicate');
    Ixy=imfilter(img,Dxy,'replicate');

    hessianValue=[];
    for i=1:m
       for j=1:n 
        hessianValue(i,j) = Ixx(i,j)*Iyy(i,j) - Ixy(i,j)*Ixy(i,j);
       end
    end
end

Haar特征

1. 原理

Haar特征是一种反映图像的灰度变化的,像素分模块求差值的一种特征。常用于人脸识别中五官划分,例如:脸部的一些特征能由矩形模块差值特征简单的描述,如:眼睛要比脸颊颜色要深,鼻梁两侧比鼻梁颜色要深,嘴巴比周围颜色要深等。但矩形特征只对一些简单的图形结构,如边缘、线段较敏感,所以只能描述在特定方向(水平、垂直、对角)上有明显像素模块梯度变化的图像结构。

它分为三类:边缘特征、线性特征、中心特征和对角线特征

模板特征值计算: 黑色矩形像素和 - 白色矩形像素和

  • :一般通过积分图计算Haar特征,这样只需求一次积分图,就可以求出多种Haar特征,节省计算时间
  • 积分图:主要思想是将图像从起点开始到各个点所形成的矩形区域像素之和作为一个数组的元素保存在内存中,当要计算某个区域的像素和时可以直接索引数组的元素,不用重新计算这个区域的像素和。示例如下:
    [外链图片转存失败(img-9o0K5mMS-1562302602307)(http://my.csdn.net/uploads/201206/04/1338798979_5003.JPG)]

上图中,D块的像素和=II(4)+II(1)-II(2)-II(3) II表示积分图

2. 实现

这里我只实现了Haar特征的一种,其他的同理

% 提取Haar特征(中心为黑四周为白),思路如下:
% white+black = II(i+1,j+1)+II(i-2,j-1)-II(i-2,j+1)-II(i+1,j-2)
% black = img(i,j)
% harr = white-black = white+black-2*black
function [haar,hg,hgx,hgy] = edge_haar_center(img)
    close all;
    II = integralImage(img);% 求积分图
    II = II(2:end,2:end);
    height = size(II,1);
    width = size(II,2);

    total = II(4:height-1,4:width-1)+II(1:height-4,1:width-4)-II(1:height-4,4:width-1)-II(4:height-1,1:width-4);
    black = img(3:height-2,3:width-2);
    haar = (total - 2*black);
    [hgx,hgy] = gradient(haar);
    hg = sqrt(hgx.^2+hgy.^2);
end

参考资料

  1. Canny边缘检测算法原理及其VC实现详解
  2. Bilateral Filters(双边滤波算法)原理及实现
  3. Hessian矩阵以及在图像中的应用
  4. 机器学习之Haar特征
2018-04-16 17:31:14 qq_16481211 阅读数 5896

裂纹检测与跟踪实现两个功能:裂纹检测和裂纹跟踪,对裂纹图像进行图像增强,对其细节特征进行突出,对裂纹的边缘提取,对于边缘提取的精度进行比较。对裂纹特征进行坐标化和骨骼化,转化为数据数组便于存储于图像文件,有利于图形的传输。
机器人实现裂纹跟踪,将裂纹的轨迹指令发送给机器人从而实现机器人的跟踪,将检测到裂纹特征数据,通过转化为机器人需要的坐标信息,发给机器人。

图像处理

在焊接过程中,会发出强光,采集得到的图像会导致图像白亮化,影响后续的图像处理。针对此现象,首先需要对获取的图像进行预处理,增强图像特征。
针对白亮的图像,先结合原色先验理论和去雾技术,增强裂纹特征,并对增强后的裂纹特征进行边缘提取,分析比较亚像素边缘检测。

2017-07-26 13:35:29 walilk 阅读数 12545

前言

  [图像处理] 实验笔记系列是以图像处理算法为主的文章专栏,以我在算法研究中的实验笔记资料为基础加以整理推出的。该系列内容涉及常见的图像处理算法理论以及常见的算法应用,每篇博客都会介绍相关的算法原理,代码实现和算法在实际应用中的技巧。
  本文主要整理自笔者在一项图像处理任务中的直线检测(line detection)部分的笔记资料,采用了基于霍夫变换(Hough Transform)的直线检测算法。文中给出了直线检测常用的算法介绍,论文资料等,以及笔者的实验笔记和实验结果。
  
  文章小节安排如下:
  1)直线检测相关算法
  2)霍夫直线检测的基本原理
  3)霍夫直线检测的OpenCV实现
  4)直线检测的应用
  
  

一、直线检测相关算法  

1.1 霍夫变换(Hough Transform) 

  霍夫变换(Hough Transform)换于1962年由Paul Hough 首次提出,后于1972年由Richard Duda和Peter Hart推广使用,是图像处理中从图像中检测几何形状的基本方法之一。经典霍夫变换用来检测图像中的直线,后来霍夫变换经过扩展可以进行任意形状物体的识别,例如圆和椭圆。
  
  霍夫变换运用两个坐标空间之间的变换将在一个空间中具有相同形状的曲线或直线映射到另一个坐标空间的一个点上形成峰值,从而把检测任意形状的问题转化为统计峰值问题。
  
  参考论文:
  [1] P.V.C. Hough,Machine Analysis of Bubble Chamber Pictures, Proc. Int. Conf. High Energy Accelerators and Instrumentation, 1959.
  [2] Duda, R. O. and P. E. Hart, “Use of the Hough Transformation to Detect Lines and Curves in Pictures,”Comm. ACM, Vol. 15, pp. 11–15 (January, 1972).
  
  

1.2 霍夫直线检测(Hough Line Detection) 

  Hough直线检测的基本原理在于利用点与线的对偶性,在我们的直线检测任务中,即图像空间中的直线与参数空间中的点是一一对应的,参数空间中的直线与图像空间中的点也是一一对应的。这意味着我们可以得出两个非常有用的结论:
  1)图像空间中的每条直线在参数空间中都对应着单独一个点来表示;
  2)图像空间中的直线上任何一部分线段在参数空间对应的是同一个点。
  
  因此Hough直线检测算法就是把在图像空间中的直线检测问题转换到参数空间中对点的检测问题,通过在参数空间里寻找峰值来完成直线检测任务。
  

1.3 LSD 

  -待续-
  

二、霍夫直线检测的基本原理

2.1 关于对偶性

  首先,我们通过实例来解释一下对偶性的意义。
  1)图像空间中的点与参数空间中的直线一一对应
  在图像空间x-y中一条直线在直角坐标系下可以表示为:
  

直线方程

  其中k和b是参数,对应表示斜率和截距。
  
图像空间的直线

  过某一点A(x0, y0)的所有直线的参数均满足方程y0=k*x0+b,即点A(x0, y0)确定了一族直线。
  如果我们将方程改写为:
  
直接方程形式改写

  那么该方程在参数空间k-b中就对应了一条直线:
  
参数空间的直线

  也就是说,图像空间x-y中的点(x0,y0)对应了参数空间k-b中的直线b=-k*x0+y0。因此可以得到结论,图像空间中的点与参数空间中的直线一一对应。
  
  2)图像空间中的直线与参数空间中的点一一对应
  我们在直线y=k*x+b上再增加一个点B(x1, y1),如下图所示:
  
图像空间的直线

  那么点B(x1, y1)在参数空间同样对应了一条直线:
  
参数空间的直线
  
  可以看到,图像空间x-y中的点A和点B在参数空间k-b中对应的直线相交于一点,这也就是说AB所确定的直线,在参数空间中对应着唯一一个点,这个点的坐标值(k0, b0)也就是直线AB的参数。
  
  以上就是在直线检测任务中关于对偶性的直观解释。这个性质也为我们解决直线检测任务提供了方法,也就是把图像空间中的直线对应到参数空间中的点,最后通过统计特性来解决问题。假如图像空间中有两条直线,那么最终在参数空间中就会对应到两个峰值点,依此类推。
  

2.2 参数空间的选择

  上述为了方便讲解对偶性和霍夫变换的基本原理,我们的参数空间也选择了笛卡尔直角坐标系。但在实际应用中,参数空间是不能选择直角坐标系的,因为原始图像直角坐标空间中的特殊直线x=c(垂直x轴,直线的斜率为无穷大)是没办法在基于直角坐标系的参数空间中表示的。
  
  所以在实际应用中,参数空间采用极坐标系ρ-θ,图示如下:
  

参数空间选择极坐标系时的直线示意

  直线的表达式为:
  
直线表达式

  化简便可得到:
  

化简的直线表达式

  对于直线上的点(x0, y0),可以将通过该点的直线族定义为:
  
经过指定点的直线族表达式

  
  
  这就回到我们刚才的结论,参数空间的每个点(ρ,θ)都对应了图像空间的一条直线,或者说图像空间的一个点在参数空间中就对应为一条曲线。参数空间采用极坐标系,这样就可以在参数空间表示原始空间中的所有直线了。
  注意,此时图像空间(直角坐标系x-y)上的一个点对应到参数空间(极坐标系ρ-θ)上是一条曲线,确切的说是一条正弦曲线。
  
参数空间的曲线

  

2.3 利用霍夫变换检测直线

  如前所述,霍夫直线检测就是把图像空间中的直线变换到参数空间中的点,通过统计特性来解决检测问题。具体来说,如果一幅图像中的像素构成一条直线,那么这些像素坐标值(x, y)在参数空间对应的曲线一定相交于一个点,所以我们只需要将图像中的所有像素点(坐标值)变换成参数空间的曲线,并在参数空间检测曲线交点就可以确定直线了。
  
  在理论上,一个点对应无数条直线或者说任意方向的直线,但在实际应用中,我们必须限定直线的数量(即有限数量的方向)才能够进行计算。
  
  因此,我们将直线的方向θ离散化为有限个等间距的离散值,参数ρ也就对应离散化为有限个值,于是参数空间不再是连续的,而是被离散量化为一个个等大小网格单元。将图像空间(直角坐标系)中每个像素点坐标值变换到参数空间(极坐标系)后,所得值会落在某个网格内,使该网格单元的累加计数器加1。当图像空间中所有的像素都经过霍夫变换后,对网格单元进行检查,累加计数值最大的网格,其坐标值(ρ0, θ0)就对应图像空间中所求的直线。
  

参数空间的量化

  
  以上就是霍夫直线检测算法要做的,它检测图像中每个像素点在参数空间对应曲线之间的交点,如果交于一点的曲线的数量超过了阈值,那就可以认为这个交点(ρ,θ)在图像空间中对应一条直线。
    
    

2.4 霍夫直线检测的优缺点

  优点:
  Hough直线检测的优点是抗干扰能力强,对图像中直线的殘缺部分、噪声以及其它共存的非直线结构不敏感。
  缺点:
  Hough变换算法的特点导致其时间复杂度和空间复杂度都很高,并且在检测过程中只能确定直线方向,丢失了线段的长度信息。
  
  

三、霍夫直线检测的OpenCV实现  

  OpenCV支持三种霍夫直线检测算法:
  1)Standard Hough Transform(SHT,标准霍夫变换)
  2)Multiscale Hough Transform(MSHT,多尺度霍夫变换)
  3)Progressive Probability Houth Transform(PPHT,渐进概率式霍夫变换)
  

3.1 霍夫直线检测函数定义

  在OpenCV2.1之前的版本,霍夫直线检测函数如下:
  
  函数原型:

CVAPI(CvSeq*) cvHoughLines2( CvArr* image, void* line_storage, int method,
  double rho, double theta, int threshold,
  double param1 CV_DEFAULT(0), double param2 CV_DEFAULT(0),
  double min_theta CV_DEFAULT(0), double max_theta CV_DEFAULT(CV_PI));

  函数说明:
  cvHoughLines2老版OpenCV的霍夫直线检测函数,通过method参数可以支持三种霍夫直线检测算法,分别是CV_HOUGH_STANDARD、CV_HOUGH_PROBABILISTIC =1、CV_HOUGH_MULTI_SCALE。
  
  
  在OpenCV新版本下,霍夫直线检测算法定义了两个函数:HoughLines、HoughLinesP
  1)HoughLines:标准霍夫变换、多尺度霍夫变换
  函数原型:

CV_EXPORTS_W void HoughLines( InputArray image, OutputArray lines,
  double rho, double theta, int threshold,
  double srn = 0, double stn = 0,
  double min_theta = 0, double max_theta = CV_PI );
  

  参数说明:
  InputArray image:输入图像,必须是8位单通道图像。
  OutputArray lines:检测到的线条参数集合。
  double rho:以像素为单位的距离步长。
  double theta:以弧度为单位的角度步长。
  int threshold:累加计数值的阈值参数,当参数空间某个交点的累加计数的值超过该阈值,则认为该交点对应了图像空间的一条直线。
  double srn:默认值为0,用于在多尺度霍夫变换中作为参数rho的除数,rho=rho/srn。
  double stn:默认值为0,用于在多尺度霍夫变换中作为参数theta的除数,theta=theta/stn。
  
  函数说明:
  HoughLines函数输出检测到直线的矢量表示集合,每一条直线由具有两个元素的矢量(ρ, θ)表示,其中ρ表示直线距离原点(0, 0)的长度,θ表示直线的角度(以弧度为单位)。
HoughLines函数无法输出图像空间中线段的长度,这也是霍夫变换本身的弱点。

  
  备注说明:
  如果srn和stn同时为0,就表示HoughLines函数执行标准霍夫变换,否则就是执行多尺度霍夫变换。
  
  
  2)HoughLinesP:渐进概率式霍夫变换
  函数原型:

CV_EXPORTS_W void HoughLinesP( InputArray image, OutputArray lines,
  double rho, double theta, int threshold,
  double minLineLength = 0, double maxLineGap = 0 );
  

  参数说明:
  InputArray image:输入图像,必须是8位单通道图像。
  OutputArray lines:检测到的线条参数集合。
  double rho:直线搜索时的距离步长,以像素为单位。
  double theta:直线搜索时的角度步长,以弧度为单位。
  int threshold:累加计数值的阈值参数,当参数空间某个交点的累加计数的值超过该阈值,则认为该交点对应了图像空间的一条直线。
  double minLineLength:默认值为0,表示最小线段长度阈值(像素)。
  double maxLineGap:默认值为0,表示直线断裂的最大间隔距离阈值。即如果有两条线段是在一条直线上,但它们之间有间隙,那么如果这个间隔距离大于该值,则被认为是一条线段,否则认为是两条线段。
  
  函数说明:
  HoughLinesP函数输出检测到直线的矢量表示集合,每一条直线由具有四个元素的矢量(x1, y1, x2, y2)表示,其中(x1, y1)表示线段的起点,(x2, y2)表示线段的终点。
  HoughLinesP函数可以检测出图像空间中线段的长度。

  
  

3.2 霍夫直线检测函数使用

  霍夫直线变换是一种用来在图像空间寻找直线的方法,输入图像要求是二值图像,同时为了提高检测直线的效率和准确率,在使用霍夫线变换之前,最好对图像进行边缘检测生成边缘二值图像,这样的检测效果是最好的。
  
  1)HoughLines函数
  代码:

std::string img_path;
cv::Mat mat_color;
cv::Mat mat_gray;
cv::Mat mat_binary;
cv::Mat mat_canny;
cv::Mat mat_board;

img_path = "line.png";
mat_color = cv::imread(img_path, 1);
mat_gray = cv::imread(img_path, 0);
mat_board = cv::Mat(mat_color.size(), mat_color.type(), Scalar::all(255));

// binary
cv::threshold(mat_gray, mat_binary, 0.0, 255.0, cv::THRESH_OTSU);
// invert color
cv::bitwise_not(mat_binary, mat_binary);

// detect edge
Canny(mat_binary, mat_canny, 50, 200, 3);

// detect line
vector<Vec2f> lines;
HoughLines(mat_canny, lines, 1, CV_PI / 180, 150, 0, 0);

// draw line
cout << "line number: " << lines.size() << endl;
for (size_t i = 0; i < lines.size(); i++)
{
    Vec2f linex = lines[i];
    cout << "radius: " << linex[0] << ", radian: "<< linex[1] << ", angle: " << 180 / CV_PI * linex[1] << endl;
    float rho = lines[i][0], theta = lines[i][1];
    Point 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));
    line(mat_board, pt1, pt2, Scalar(255, 0, 0), 1);
}
cv::imshow("gray", mat_gray);
cv::imshow("binary", mat_binary);
cv::imshow("canny", mat_canny);
cv::imshow("color", mat_board);
cv::waitKey();

  原图:
  

原图

  二值图:
  
二值图

  边缘图:
  
边缘图

  检测效果:
  
HoughLines的检测效果

  
  2)HoughLinesP函数
  代码:

std::string img_path;
cv::Mat mat_color;
cv::Mat mat_gray;
cv::Mat mat_binary;
cv::Mat mat_canny;
cv::Mat mat_board;

img_path = "line.png";
mat_color = cv::imread(img_path, 1);
mat_gray  = cv::imread(img_path, 0);
mat_board = cv::Mat(mat_color.size(), mat_color.type(), Scalar::all(255));

// binary
cv::threshold(mat_gray, mat_binary, 0.0, 255.0, cv::THRESH_OTSU);
// invert color
cv::bitwise_not(mat_binary, mat_binary);

// detect edge
Canny(mat_binary, mat_canny, 50, 200, 3);

// detect line
vector<Vec4i> lines;
HoughLinesP(mat_canny, lines, 1, CV_PI / 180, 150, 50, 50);

// draw line
cout << "line number: " << lines.size() << endl;
for (size_t i = 0; i < lines.size(); i++)
{
    Vec4i linex = lines[i];
    line(mat_board, Point(linex[0], linex[1]), Point(linex[2], linex[3]), Scalar(255, 0, 0), 1);
}

cv::imshow("gray",   mat_gray);
cv::imshow("binary", mat_binary);
cv::imshow("canny", mat_canny);
cv::imshow("color", mat_board);
cv::waitKey();

  检测效果:
  

HoughLinesP的检测效果

  
  

四、直线检测的应用

4.1 直线检测的实际应用

  直线检测是机器视觉和模式识别中最重要的任务之一,对图像理解/分析等有重要的意义。在实际应用中,直线检测可用于机器人定位中的网格识别,板材的裂纹检测,表单票据的格式识别,零件纹路的检测,自动驾驶中的车道检测等等,可以看出,在工业领域中,直线检测以及各种图像处理技术应用是非常丰富的。
  

4.2 直线检测在图像矫正方面的应用

  笔者最近在一个OCR项目也使用了Hough Line Detection算法。在该项目中,待识别文本图像的内容是倾斜的(即文字是倾斜的),我们采用的测略就是通过直线检测确定图像内容的倾斜程度并进行旋转纠正,这样得到的无倾斜图像更有利于OCR任务。
  原图:
  

原图

  矫正效果:
  
矫正效果

  

五、参考资料

参考论文与书目:
[1] Duda, R. O. and P. E. Hart, “Use of the Hough Transformation to Detect Lines and Curves in Pictures,”Comm. ACM, Vol. 15, pp. 11–15 (January, 1972).
[2] 顾思妍. 机器视觉的直线检测技术及应用研究[D].广东工业大学,2011.
[3] 数字图像处理[M]. 电子工业出版社 , (美)RafaelC.Gonzalez,(美)RichardE.Woods,(美)StevenL.Eddins著, 2005

参考博客:
Hough变换-理解篇
http://blog.csdn.net/abcjennifer/article/details/7448513
Hough transform(霍夫变换)
http://www.cnblogs.com/AndyJee/p/3805594.html
霍夫变换概述和标准霍夫变换
http://www.jianshu.com/p/55eabb42c6c2

2018-09-01 11:11:58 qq_14822699 阅读数 9030

本博客仅仅是因为自己经常忘记,记笔记而已

常用操作:

1、二值化

clc
A=imread('a.jpg');   %读取到一张图片   
thresh = graythresh(A);     %自动确定二值化阈值
I2 = im2bw(A,thresh);       %对图像二值化
 
figure(1)
imshow(A)     %显示二值化之前的图片
figure(2)
imshow(I2)    %显示二值化之后的图片

thresh是二值化的阈值,可以自己确定大小,在0到1之间.

2、边缘检测

边缘检测常用的五种

1、Robert算子边缘检测

BW1=edge(I,'Roberts',0.16);
imshow(BW1);

2、Sobel算子边缘检测

BW2=edge(I,'Sobel',0.16);
imshow(BW2);

3、Prewitt算子边缘检测

BW3=edge(I,'Prewitt',0.06);
imshow(BW3);

4、LOG算子边缘检测

BW4=edge(I,'LOG',0.012);
imshow(BW4);

5、Canny算子边缘检测

BW5=edge(I,'Canny',0.35);
imshow(BW5);

简单常用圆检测:imfindcircles

例;[c,r]=imfindcircles(image,[circles1,circles2]),image为检测图像矩阵,circles1,circles2分别是圆检测的半径,即确定可检测圆半径范围。注意:当圆检测半径范围较大时会拖慢程序速度。

检测到圆后可在图像中直接画出对应的圆及圆心:viscircles(c, r,'EdgeColor','b');其中c是圆心坐标,r是圆半径,‘EdgeColor’,‘b’分别是画圆的参数。

[c,r]=imfindcircles(image,[circles1,circles2]);
figure
imshow(image)
viscircles(c, r,'EdgeColor','b');

 

连通域检测常用函数

bwlabel  ;regionprops

[B,L]=bwlabel(B0);
img_reg = regionprops(B, 'all'); 

 

img_regb便是闭合区域的结构体,里面最常用的是Centroid:闭合区域质心;BoundingBox:矩形边界;PixelList连通域坐标

连通域边界检测

[B,L] = bwboundaries(image)

1.B是一个 P x 1 的数组,其中P代表连通体的个数.B内每一行是一个 Q x 2 的矩阵,Q内每一行表示连通体的边界像素的位置坐标(第一列是纵坐标Y,第二列是横坐标X),Q为边界像素的个数.

2.L是一个标记矩阵.

2016-10-10 10:01:54 freedom098 阅读数 15667

师弟最近要使用四旋翼进行桥梁探伤,主要是用运动相机搭载在四轴上检测裂缝,就顺便搞了一下有关于裂缝检测的图像处理。

算法比较简单,没有考虑太多复杂情况,在简单墙面背景下基本可以找到裂缝并框定。

基本思路为,先转换彩色图为灰度图,然后进行自适应局部阈值化,目的是为了减少光照与阴影对阈值的影响,然后进行膨胀操作,尽量放大凸显裂缝,在用中值滤波平滑一下,去除一些高频噪声。然后调用寻找轮廓函数寻找所有轮廓,寻找连通域最大的那个轮廓,然后在原图画框即可。

先上算法代码,之后还有Qt界面加图像传输完整版。

// cracker_find.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <opencv2\opencv.hpp>
#include <vector>
#include<time.h> 

using namespace cv;
using namespace std;

void location(Mat &srcImg,Mat &binImg)
{

    vector< vector<Point> > contours ;
    if(binImg.data)
    {
        findContours(binImg,contours,CV_RETR_EXTERNAL,CV_CHAIN_APPROX_NONE);
	}

    if(contours.size() > 1)
    {
        double maxArea = 0;
        vector <Point> maxContour;
        for(size_t i = 0; i < contours.size(); i++)
        {
            double area = contourArea(contours[i]);
            if (area > maxArea)
            {
                maxArea = area;
                maxContour = contours[i];
            }
        }
        Rect maxRect;
        Mat ROI;
        if (maxContour.size()!=0)
        {
             maxRect = boundingRect(maxContour);          
        }

        rectangle(srcImg, maxRect, cv::Scalar(0,0,255));
    }
}


void preProcessing(Mat &srcImg,Mat &binImg,int elementSize = 7)
{
	Mat grayImg;
	cvtColor(srcImg,grayImg,CV_RGB2GRAY);
	int blockSize = 25;  
    int constValue = 35;    
    adaptiveThreshold(grayImg, binImg, 255, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY_INV, blockSize, constValue); 
	//threshold(grayImg,binImg,150,255,THRESH_BINARY_INV);
	Mat element = getStructuringElement(MORPH_RECT, Size(elementSize, elementSize));
	dilate(binImg,binImg,element);
	medianBlur(binImg,binImg,9);
}

int _tmain(int argc, _TCHAR* argv[])
{

	clock_t start,finish;  
    double totaltime;  
	start=clock();

	Mat srcImg = imread("D:\\crack.jpg");
		
	Mat binImg;
	preProcessing(srcImg,binImg);
	imshow("binImg",binImg);
	location(srcImg,binImg);
	imshow("ansImg",srcImg);

	finish=clock();  
    totaltime=(double)(finish-start)/CLOCKS_PER_SEC;  
    cout<<"\n此程序的运行时间为"<<totaltime<<"秒!"<<endl;
	//system("pause");

	waitKey();

	return 0;
}