標籤:
上次寫了A4紙的邊緣提取,發現My Code還是存在著很多的問題,比如令人詬病的靜態閾值,還有非結構化的編程風格。於是我重新整理了一下,把A4紙邊緣提取的代碼整合為一個類。不過那個該死的閾值啊,我暫時還沒有找到完美的方法,使得適用於所有的映像_(:з」∠)_。
最佳化的方法倒是有一點,那就是降低標準,擇優錄取。也就是把閾值調得很低,但是峰值提取的結果只取最優的4個。當然啦,這種方法偶爾會取到奇怪的邊緣,而且由於閾值的降低,導致的計算量也成倍增長,特別是Hough變換。但綜合來看,魯棒性還是增強了不少。
另外,大家可以搜下有關 “邊緣提取動態閾值擷取” 的論文。梯度閾值動態一個簡單方法,就是取所有像素點梯度的平均值,至於效果怎樣,有待大家嘗試哦~
那麼,在上一節的基礎上,我們還能做什麼呢?
我們現在只是知道了A4紙的邊緣和角點,每一張A4紙都處於不同的的角度、位置,甚至有著不同的形狀。這些“畸形”的A4紙不利於我們進一步的影像處理,因此需要把它們矯正成統一的矩形。
輸入映像:
普通A4列印紙,上面可能有手寫筆記或者列印內容,但是拍照時可能角度不正。
輸出映像:
已經矯正好的標準普通A4紙(210:297),並裁掉無用的其他內容,只保留完整的A4紙張。
實驗中,我使用了兩種方法,一種是Projective Transform(仿射變換),一種是Morphing(變形)。
兩種方法各有優缺點,其中仿射變換處理速度快,矯正圖片準確,但是涉及矩陣演算。Morphing的映像準確度沒有仿射變換高,但是原理通俗易懂,不怎麼需要演算。
方法一:Projective Transform
主要參考這篇部落格:http://blog.csdn.net/xiaowei_cqu/article/details/26471527
但是有幾個錯誤需要指出來,不為0時,應得到:
a11= x1 - x0 +a13* x1 a12= y1 - y0 +a13* y1 a13=
a21 = x3 - x0 + a23 * x3 a22= y3 - y0 +a23* y3 a23=
a31 = x0 a32 = y0 a33 = 1
關鍵代碼:
Matrix3x3 A4ShapeCorrect::squareToQuadrilateral(double x0, double y0, double x1, double y1, double x2,double y2, double x3, double y3) {double dx3 = x0 - x1 + x2 - x3;double dy3 = y0 - y1 + y2 - y3;if (dx3 == 0.0f && dy3 == 0.0f) {Matrix3x3 result(x1 - x0, y1 - y0, 0, x2 - x1, y2 - y1, 0, x0, y0, 1);return result;}else {double dx1 = x1 - x2;double dx2 = x3 - x2;double dy1 = y1 - y2;double dy2 = y3 - y2;double denominator = dx1 * dy2 - dx2 * dy1;double a13 = (dx3 * dy2 - dx2 * dy3) / denominator;double a23 = (dx1 * dy3 - dx3 * dy1) / denominator;Matrix3x3 result(x1 - x0 + a13 * x1, y1 - y0 + a13*y1, a13, x3 - x0 + a23*x3, y3 - y0 + a23*y3, a23, x0, y0, 1);return result;}}
計算出了變換矩陣的係數後,只需要應用到每個像素座標就好了。要注意方向:從目標像素映射到原像素,並且計算插值。
Matrix3x3 H = squareToQuadrilateral(dots[0]->x, dots[0]->y, dots[1]->x, dots[1]->y, dots[2]->x, dots[2]->y, dots[3]->x, dots[3]->y);/* Method 1: Projective Transforming */cimg_forXY(*target, x, y){double _x = (double)x / _width;double _y = (double)y / _height;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;cimg_forC(*target, c)(*target)(x, y, 0, c) = bilinear(img, tx, ty, 0, c);}
方法二:Morphing
針對三角形進行Morph變換,思路類似雙線性插值:
如何計算P‘的座標::DA / BA = D‘A‘ / B‘A‘ , PC / DC = P‘C‘ / D‘C‘, 其中ABC、A‘B‘C‘和P座標已知。
有了計算變換三角形位置的方法,我們就可以把原A4紙延對角線切割成兩個三角形,把靶心圖表形也延對角線切割成兩個三角形,然後分別對這兩個三角形內的像素點座標做Morph變換即可。
其中還要解決的問題是如何判斷一個點在三角形內,參考部落格:http://www.cnblogs.com/graphics/archive/2010/08/05/1793393.html
我採用了第3種方法。
關鍵代碼:
Dot* A4ShapeCorrect::morph(Triangle source, Triangle target, Dot p){Dot a = source.a, b = source.b, c = source.c;Dot at = target.a, bt = target.b, ct = target.c;if (p == a) return new Dot(at.x, at.y);if (p == b) return new Dot(bt.x, bt.y);if (p == c) return new Dot(ct.x, ct.y);Line cp(c, p);Line ab(a, b);Dot* d = cp.intersect(ab);// DA / BAdouble ABrate = (b.x - a.x != 0) ? (d->x - a.x) / (b.x - a.x) : (d->y - a.y) / (b.y - a.y);// PC / DCdouble CDrate = (d->x - c.x != 0) ? (p.x - c.x) / (d->x - c.x) : (p.y - c.y) / (d->y - c.y);double dtx = ABrate*(bt.x - at.x) + at.x;double dty = ABrate*(bt.y - at.y) + at.y;double ptx = CDrate*(dtx - ct.x) + ct.x;double pty = CDrate*(dty - ct.y) + ct.y;return new Dot(ptx, pty);}
同樣要注意方向:從目標像素映射到原像素,並且計算插值。
/* Method 2: Morphing */Dot At(0, 0);Dot Bt(target->width() - 1, 0);Dot Ct(target->width() - 1, target->height() - 1);Dot Dt(0, target->height() - 1);Dot A(dots[0]->x, dots[0]->y);Dot B(dots[1]->x, dots[1]->y);Dot C(dots[2]->x, dots[2]->y);Dot D(dots[3]->x, dots[3]->y);cimg_forXY(*target, x, y){Dot P(x, y), *p = NULL;if (pointInTriangle(Triangle(At, Bt, Ct), P))p = morph(Triangle(At, Bt, Ct), Triangle(A, B, C), P);else if (pointInTriangle(Triangle(At, Ct, Dt), P))p = morph(Triangle(At, Ct, Dt), Triangle(A, C, D), P);if (p != NULL){cimg_forC(*target, c)(*target)(x, y, 0, c) = bilinear(img, p->x, p->y, 0, c);}}
對於以上兩種方法,都涉及一個前提:我們找到的原圖A4紙角點必須按順序ABCD排列,或者我們至少知道各角點對應的A4紙方位。
我的方法是4個點先按y軸排序,y最小的不是A點就是B點,然後找跟這點最近的點,找到後這兩個點就分別是A和B(事實上並不一定,比如扁菱形)。接著判斷矩形是橫的還是豎的,然後準確判斷AB位置,最後準確判斷CD位置。只能說So far so good!
void A4ShapeCorrect::reorderDots(std::vector<Dot*>& dots){std::sort(dots.begin(), dots.end(), [](const Dot* a, const Dot* b) { return a->y < b->y; });double min = DBL_MAX;int temp;for (int i = 1; i < dots.size(); ++i){double dis = dots[0]->distance(*dots[i]);if (min > dis){min = dis;temp = i;}}std::swap(dots[1], dots[temp]);if (dots[1]->y > dots[2]->y || dots[1]->y > dots[3]->y){std::swap(dots[0], dots[1]);if (dots[2]->y > dots[3]->y){std::swap(dots[2], dots[3]);}}else{if (dots[0]->x > dots[1]->x){std::swap(dots[0], dots[1]);}if (dots[2]->x < dots[3]->x){std::swap(dots[2], dots[3]);}}}
電腦視覺與模式識別(2)—— A4紙矯正