一、提取直线、轮廓和区域

1.1 canny边缘检测

二值边缘分布图有两个主要缺点:第一,检测到的边缘过厚,这加大了识别物体边界的难度;第二,也是更重要的,通常不可能找到既低到足以检测到图像中所有重要边缘,又高到足以避免产生太多无关紧要边缘的阈值。这是一个难以权衡的问题,Canny 算法试图解决这个问题。

简单的来说Canny 算法就是在各方向求导,找到局部最大值。实现步骤:

  1. 用高斯滤波器平滑图像
  2. 用Sobel等梯度算子计算梯度幅值和方向
  3. 对梯度幅值进行非极大值抑制
  4. 用双阈值算法检测和连接边缘

 非极大值抑制(Non-Maximum Suppression,NMS),顾名思义就是抑制不是极大值的元素,可以理解为局部最大搜索。这个局部代表的是一个邻域,邻域有两个参数可变,一是邻域的维数,二是邻域的大小。就是把非极大值过滤掉(抑制)。

canny算法opencv实现

// 应用 Canny 算法
cv::Mat contours;
cv::Canny(image, // 灰度图像
contours, // 输出轮廓
125, // 低阈值
350); // 高阈值

1.2 霍夫变换直线检测

霍夫变换(Hough Transform)是图像处理领域内从图像中检测几何形状的基本方法之一。霍夫变换是一种在图像中寻找直线,圆及其他简单形状的方法。 opencv支持两种不同的霍夫变换:标准霍夫变换(SHT)和累积概率霍夫变换(PPHT)。经典霍夫变换用来检测图像中的直线,后来霍夫变换经过扩展可以进行任意形状物体的识别,例如圆和椭圆。

霍夫变换运用两个坐标空间之间的变换,将在一个空间中具有相同形状的曲线或直线映射到另一个坐标空间的一个点上形成峰值,从而把检测任意形状的问题转化为统计峰值问题。

在霍夫变换中,用这个方程式表示直线:

opencv 获取矩形区域 opencv提取图像某区域_opencv

 参数 ρ是直线与图像原点(左上角)的距离,θ是直线与垂直线间的角度。在这种表示法中,
图像中的直线有一个 0~π(弧度)的角 θ,而半径 ρ 的最大值是图像对角线的长度。

opencv 获取矩形区域 opencv提取图像某区域_opencv_02

像直线 1 这样的垂直线,其角度值 θ等于 0,而水平线(例如直线 5)的 θ等于 π/2。因此直
线 3 的 θ等于 π/4,直线 4 大约是 0.7π。为了表示[0, π]范围内的所有 θ值,半径值可以用负数表
示——例如直线 2,它的 θ等于 0.8π,ρ是负数。

如果对位于同一直线上的n个点进行变换,原图像空间的n个点在参数空间对应得到有n条正弦曲线,并且这些曲线相交于一点。

opencv 获取矩形区域 opencv提取图像某区域_霍夫变换_03

霍夫变换的步骤: 

1.彩色图像->灰度图
2.去噪(高斯核)
3.边缘提取(梯度算子、拉普拉斯算子、canny、sobel)
4.二值化(判断此处是否为边缘点,就看灰度值==255)
5.映射到霍夫空间(准备两个容器,一个用来展示hough-space概况,一个数组hough-space用来储存voting的值,因为投票过程往往有某个极大值超过阈值,多达几千,不能直接用灰度图来记录投票信息)
6.取局部极大值,设定阈值,过滤干扰直线
7.绘制直线、标定角点
 

Opencv霍夫变换实现:

概率霍夫变换,在 OpenCV中通过 cv::HoughLinesP 函数实现。

class LineFinder 
{
private:
// 原始图像
cv::Mat img;
// 包含被检测直线的端点的向量
std::vector<cv::Vec4i> lines;
// 累加器分辨率参数
double deltaRho;
double deltaTheta;
// 确认直线之前必须收到的最小投票数
int minVote;
// 直线的最小长度
double minLength;
// 直线上允许的最大空隙
double maxGap;
public:
// 默认累加器分辨率是 1 像素,1 度
// 没有空隙,没有最小长度
LineFinder() : deltaRho(1), deltaTheta(PI/180),
minVote(10), minLength(0.), maxGap(0.) {}
// 设置累加器的分辨率
void setAccResolution(double dRho, double dTheta) {
    deltaRho= dRho;
    deltaTheta= dTheta;
}
// 设置最小投票数
void setMinVote(int minv) {
    minVote= minv;
}
// 设置直线长度和空隙
void setLineLengthAndGap(double length, double gap) {
    minLength= length;
    maxGap= gap;
}
// 应用概率霍夫变换
std::vector<cv::Vec4i> findLines(cv::Mat& binary) {
    lines.clear();
    cv::HoughLinesP(binary,lines,
    deltaRho, deltaTheta, minVote,
    minLength, maxGap);
    return lines;
}
// 在图像上绘制检测到的直线
void drawDetectedLines(cv::Mat &image,
cv::Scalar color=cv::Scalar(255,255,255)) {
    // 画直线
    std::vector<cv::Vec4i>::const_iterator it2= lines.begin();
    while (it2!=lines.end()) {
        cv::Point pt1((*it2)[0],(*it2)[1]);
        cv::Point pt2((*it2)[2],(*it2)[3]);
        cv::line( image, pt1, pt2, color);
        ++it2;
    }
}


// 创建 LineFinder 类的实例
LineFinder finder;
// 设置概率霍夫变换的参数
finder.setLineLengthAndGap(100,20);
finder.setMinVote(60);
// 检测直线并画线
std::vector<cv::Vec4i> lines= finder.findLines(contours);
finder.drawDetectedLines(image);

1.3 提取连续区域

提取轮廓的算法很简单,它系统地扫描图像,直到找到连续区域。从区域的起点开始,沿着
它的轮廓对边界像素做标记。处理完这个轮廓后,就从上个位置继续扫描,直到发现新的区域。

OpenCV 提供了一个简单的函数,可以提取出图像中连续区域的轮廓,这个函数就是
cv::findContours :

/ 用于存储轮廓的向量
std::vector<std::vector<cv::Point>> contours;
cv::findContours(image,
contours, // 存储轮廓的向量
cv::RETR_EXTERNAL, // 检索外部轮廓
cv::CHAIN_APPROX_NONE); // 每个轮廓的全部像素

函数输入是二值图像。输出的是一个存储轮廓的向量,每个轮廓用一个cv::Point 类型的向量表示。

可通过cv::drawContours函数可在图像(这里用白色图像)上画出那些区域的轮廓 :

// 在白色图像上画黑色轮廓
cv::Mat result(image.size(),CV_8U,cv::Scalar(255));
cv::drawContours(result,contours,
-1, // 画全部轮廓
0, // 用黑色画
2); // 宽度为 2

1.4 计算区域的轮廓描述算子

连续区域通常代表着场景中的某个物体。为了识别该物体,或将它与其他图像元素做比较,
需要对此区域进行测量,以提取出部分特征。

在表示和定位图像中区域的方法中,边界框可能是最简洁的。它的定义是:能完整包含该形
状的最小垂直矩形。比较边界框的高度和宽度,可以获得物体在垂直或水平方向的特征(例如可
以通过计算高度与宽度的比例,分辨出一幅图像是汽车还是行人)。

二、各模块opencv实现

2.1 HSV特定颜色提取

Mat img = imread("test.jpg",1);
 Mat imgHSV; 
 cvtColor(img, imgHSV, COLOR_BGR2HSV);//转为HSV
//颜色提取
 int iLowH = 100 /2;  
 int iHighH = 120 /2;  
 
 int iLowS = 50 *255/100;   
 int iHighS = 70 *255/100;  
 
 int iLowV = 40 *255/100;  
 int iHighV = 50 *255/100; 
 Mat imgThresholded;
 inRange(imgHSV, Scalar(iLowH, iLowS, iLowV), Scalar(iHighH, iHighS, iHighV), imgThresholded);

2.3 霍夫变换

2.3.1 canny边缘检测

void Canny( InputArray image, OutputArray edges,
           double threshold1, double threshold2,
           int apertureSize = 3, bool L2gradient = false );
  • src输入图像,必须是8-bits;
  • edges输出的图像边缘
  • threshold1, threshold2对应于上述的T1,T2T1,T2;
  • apertureSizeSobel算子的大小;
  • L2gradient表示计算梯度值时是否使用L2L2(就是默认的计算方式);

2.3.2 霍夫变换

在OpenCV3.0及以上版本中,霍夫直线检测算法定义了两个函数:HoughLines、HoughLinesP,

(1)HoughLines:标准霍夫变换、多尺度霍夫变换

HoughLines函数输出检测到直线的矢量表示集合,每一条直线由具有两个元素的矢量(ρ, θ)表示,其中ρ表示直线距离原点(0, 0)的长度,θ表示直线的角度(以弧度为单位)。
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。
  //如果srn和stn同时为0,就表示HoughLines函数执行标准霍夫变换,否则就是执行多尺度霍夫变换。

 (2)HoughLinesP:渐进概率式霍夫变换

 HoughLinesP能够检测出线端,即能够检测出图像中直线的两个端点,确切地定位图像中的直线。
HoughLinesP函数输出检测到直线的矢量表示集合,每一条直线由具有四个元素的矢量(x1, y1, x2, y2)表示,其中(x1, y1)表示线段的起点,(x2, y2)表示线段的终点。
 

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,表示直线断裂的最大间隔距离阈值。即如果有两条线段是在一条直线上,但它们之间有间隙,那么如果这个间隔距离小于该值,则被认为是一条线段,否则认为是两条线段。

三、测试效果

//读取待处理图片
    Mat image = imread("alarm.jpg");
    Mat gray = imread("alarm.jpg", IMREAD_GRAYSCALE);//提取灰度图像

    //转换为HSV 格式
    Mat imgHSV;
    cvtColor(image, imgHSV,COLOR_BGR2HSV);
    //HSV颜色提取,提取红色
    int iLowH = 0;
    int iHighH = 10;

    int iLowS = 43;
    int iHighS = 255;

    int iLowV = 46;
    int iHighV = 255;
    Mat imgThresholded;
    inRange(imgHSV, Scalar(iLowH, iLowS, iLowV), Scalar(iHighH, iHighS, iHighV), imgThresholded);

    //中值滤波
    Mat medainBlurImg;
    medianBlur(imgThresholded, medainBlurImg,21);

    //直线检测
    Mat afterCanny;
    Canny(medainBlurImg, afterCanny,20,250);//阈值1,用于将间断的边缘连接起来; 阈值2 用于检测图像中明显的边缘
    int  line = 4;
    int  minLineLength = 50;
    int  maxLineGap = 150;
    vector<Vec4i> lines;
    HoughLinesP(afterCanny, lines,1, CV_PI / 180,line, minLineLength, maxLineGap);
    for (size_t i = 0; i < lines.size(); i++)
    {
        line(color_dst, Point(lines[i][0], lines[i][1]),
            Point(lines[i][2], lines[i][3]), Scalar(0, 0, 255), 1, 8);
    }

    //轮廓检测


    //旋转图片