Canny 算法
Canny 算法采用两个颜色变化阈值检测图像轮廓,默认得到二值图,其中用非零像素表示轮廓。
在低阈值时得到很多轮廓,有些是连续的线,有些离散点。
在高阈值时得到较少轮廓,原本连续的线也可能变成了离散点。
最后 Canny 算法将两部分融合:从低阈值轮廓中提取那些连续的线,叠加到高阈值轮廓中。
cv::Mat contours;
cv::Canny(image, // 输入图片
contours, // 输出图片
125, // 低阈值
350); // 高阈值
这种采用两个阈值的策略称为滞后阈值化。
Hough transform 霍夫变换
平面中的一条直线有很多种表示方式,例如
- 用斜率和截距 表示:,即在给定 的条件下,满足这个表达式的 点构成一条直线
- 用法线段 表示: ,即在给定 的条件下,满足这个式子的 构成一条直线。
对于 形式,可以进一步整理: ,其中 。从这个式子来看,如果固定 ,而把 作为变量,就可以得到经过点 的所有直线的集合,而且 和 是正弦关系,在 平面中表示为:
也就是说,在 平面中,一个点表示一条直线,一条线对应一个点,而多条线的交点对应了穿过多个点的一条直线
因此,在检测直线时,可以设定一个阈值,多条线相交于该点,则表明存在与该点对应的直线。
在进行 Hough 变换时,一般先用 Canny 算法得到二值初始轮廓图,其中非零像素表示潜在的轮廓点。
将上述二值散点图送入 Hough 算法,得到若干交点,如果交点中汇集的曲线数量超过某一阈值,则认为它对应了一条直线。函数最后返回的是一个向量,其中每个元素是一组 ,对应了一条直线。 取值在 0~ 之间, 的取值可以为负数。
程序实现:
cv::Mat contours;
cv::Canny(image, contours, 125, 350);
std::vector<cv::Vec2f> lines;
cv::HoughLines(contours, // 输入来自 Canny 算法的轮廓散点
lines, // 输出向量,里面包含了识别出的直线簇,每个都是二元组 (r, theta)
1, // 法线段 r 的分辨率,即搜索 r 时的步长为 1 个像素
M_PI/180, // 夹角 theta 的分辨率,即搜索 theta 时的步长为 1 度
60); // 最小交汇曲线数目
概率霍夫变换
在标准霍夫变换中,轮廓散点是逐行扫描,对每个扫描到的散点绘制一条 曲线。
而在概率霍夫变化中,随机选择像素点,而不是逐行扫描。当达到阈值时,不再考虑其他散点的投票。因此速度可能会比标准的霍夫变换快一些。
另外还有两个参数
- 返回线段的最小长度
- 属于一条直线的两个散点允许的最大间隔
返回的 Lines 是四维的,包含了两个端点的坐标 (x1, y1, x2, y2)
cv::HoughLinesP(binary,
lines,
deltaRho,
deltaTheta,
minVote, // 上述 5 个参数与标准霍夫变换相同。
minLength, // 返回线段的最小长度
maxGap); // 属于同一条线段的散点之间最大间隔
几何图形拟合轮廓
在检测完图形轮廓散点之后,为了描述简便,常用某些规则几何图形拟合/包裹相应的轮廓。
-
矩形拟合
std::vector<cv::Point> points; // 点集 cv::Rect r0 = cv::boundingRect(points); // r0 为拟合得到的矩形
-
圆形拟合
float radius; cv::Point2f center; cv::minEnclosingCircle(points, center, radius); // 得到圆心和半径 cv::circle(image, center, static_cast<int>(radius), 0, 2); // radius 必须是 int 类型
-
多边形拟合
std::vector<cv::Point> poly; cv::approxPolyDP(points, poly, 5, true); // 拟合时并不明确指定用几边形,根据需要计算得到。5 表示允许的最大拟合差距为 5, true 表示多边形是否是闭合的 cv::polylines(image, poly, true, 0, 2);
-
凸包拟合
std::vector<cv::Point> hull; cv::convexHull(points, hull); cv::polylines(image, hull, true, 0, 2);