使用CImg实现A4纸矫正

2019-04-14 21:02发布

class="markdown_views prism-atom-one-light">

图像分割

图像分割是一个聚类问题,将相似的点聚到一块。这里我采用了mean shift方法。主要分为三个部分:模点搜索,模点聚类(合并相似区域),合并小区域。

模点搜索

在这里插入图片描述
首先确定特征空间,这里选取了空间坐标x,y和 {MOD}彩空间r,g,b,在实际计算时,是将RGB转换到Lab空间。然后对图像中的每一个点,进行如下操作,对与它位置空间距离小于hr,颜 {MOD}空间距离小于hs的点求平均值,作为新的中心点。 重复这个步骤直到中心点不再移动或者到达最大迭代次数。此时的中心点就作为起始点的聚类中心,将中心点的像素值赋予初始点。 do { PtPrev = PtCur; // Set the original point and previous one PtSum = Point5D(0, 0, 0, 0, 0); // Initial Sum vector NumPts = 0; // Count number of points that satisfy the bandwidths for (int hx = Left; hx < Right; hx++) { for (int hy = Top; hy < Bottom; hy++) { // Set point in the spatial bandwidth Pt = Point5D(hx, hy, (float)Img(hx, hy, 0), (float)Img(hx, hy, 1), (float)Img(hx, hy, 2)); Pt.PointLab(); // Check it satisfied color bandwidth or not if (Pt.MSPoint5DColorDistance(PtCur) < hr) { PtSum.MSPoint5DAccum(Pt); // Accumulate the point to Sum vector NumPts++; // Count } } } PtSum.MSPoint5DScale(1.0 / NumPts); // Scale Sum vector to average vector PtCur = PtSum; // Get new origin point step++; // One time end // filter iteration to end } while ((PtCur.MSPoint5DColorDistance(PtPrev) > MS_MEAN_SHIFT_TOL_COLOR) && (PtCur.MSPoint5DSpatialDistance(PtPrev) > MS_MEAN_SHIFT_TOL_SPATIAL) && (step < MS_MAX_NUM_CONVERGENCE_STEPS));

模点聚类

基本上就是区域生长,从某一点出发,如果和它附近的点8邻域)的颜 {MOD}值相似就合并,同时再从新合并的点出发继续合并下去,直到碰到不相似的点或者该点已经属于另一类了,此时,就退回来,直到退无可退(所有的8邻域搜索空间都已经搜索完毕)。 // Region Growing 8 Neighbours vector<Point5D> NeighbourPoints; NeighbourPoints.push_back(PtCur); while (!NeighbourPoints.empty()) { Pt = NeighbourPoints.back(); NeighbourPoints.pop_back(); // Get 8 neighbours for (int k = 0; k < 8; k++) { int hx = Pt.x + dxdy[k][0]; int hy = Pt.y + dxdy[k][1]; if ((hx > 0) && (hy > 0) && (hx < ROWS) && (hy < COLS) && (Labels[hx][hy] < 0)) { Point5D P(hx, hy, (float)Img(hx, hy, 0), (float)Img(hx, hy, 1), (float)Img(hx, hy, 2)); P.PointLab(); // Check the color if (PtCur.MSPoint5DColorDistance(P) < hr) { // Satisfied the color bandwidth Labels[hx][hy] = label; // Give the same label NeighbourPoints.push_back(P); // Push it into stack MemberModeCount[label]++; // This region number plus one // Sum all color in same region Mode[label * 3 + 0] += P.l; Mode[label * 3 + 1] += P.a; Mode[label * 3 + 2] += P.b; } } } } MemberModeCount[label]++; // Count the point itself Mode[label * 3 + 0] /= MemberModeCount[label]; // Get average color Mode[label * 3 + 1] /= MemberModeCount[label]; Mode[label * 3 + 2] /= MemberModeCount[label]; 阶段效果:(右图对图像二值化)
在这里插入图片描述在这里插入图片描述

A4纸矫正

获取角点

对每个像素,遍历它的8邻域,如果全为白 {MOD},就将它变为黑 {MOD},得到边缘图像。 auto res = grayscaled; CImg_3x3(I, uchar); cimg_for3x3(grayscaled, x, y,0,0,I,uchar) { if (Ipp==255 &&Icp == 255 &&Inp == 255 &&Ipc == 255 &&Icc == 255 &&Inc == 255 &&Ipn == 255 &&Icn == 255 &&Inn == 255) { res(x, y) = 0; } } 利用之前的霍夫变换,可以求出边缘的直线方程,并得到交点坐标。
在这里插入图片描述在这里插入图片描述

仿射变换

我们获得了角点的坐标,同时我们也知道它们对应矫正后矩形的四个顶点,利用仿射变换可以求出变换举证,然后我们根据结果图像的像素坐标利用插值获得原图对应像素值,并赋给结果图像就可以得到最终结果。
在这里插入图片描述
在这里插入图片描述 Hough hough("result/edge/2.bmp"); PerspectiveTransform temp; PerspectiveTransform H = temp.quadrilateralToQuadrilateral(0, 0, 779, 0, 0, 1051, 779, 1051, hough.points[2].x, hough.points[2].y, hough.points[0].x, hough.points[0].y, hough.points[3].x, hough.points[3].y, hough.points[1].x, hough.points[1].y); /* Method 1: Projective Transforming */ CImg<uchar> dest(780, 1052, 1, 3); cimg_forXY(dest, x, y) { double denominator = H.a13 * x + H.a23 * y + H.a33; double tx = (H.a11 * x + H.a21 * y + H.a31) / denominator; double ty = (H.a12 * x + H.a22 * y + H.a32) / denominator; //cout << tx << " " << ty << endl; cimg_forC(dest,c) dest(x, y, c) = src.linear_atXYZC(tx, ty,c); } 最终结果
在这里插入图片描述在这里插入图片描述

与canny算子的比较

对于较为简单和明显的边缘,比如A4纸,图像分割能够很好的获取到边缘,并且非常明确,干扰很少。但是canny算子能够获取到图像比较细腻的边缘,并且对于噪声有很好的抑制,计算也更快。

代码地址

Github

参考博客

http://www.voidcn.com/article/p-ukgueaom-th.html
https://bbbbyang.github.io/2018/03/19/Mean-Shift-Segmentation/