這是用什麼演算法實現的?
補充:我前天蛋疼隨便寫寫來著,這個流程應該算能運行,但我只是寫個答案而已完全沒考慮穩定性和實用性(不過話說回來有grabcut嘛),並且因為沒有meta data所以一個非常關鍵的步驟我直接略過了:Camera Calibration,還有其他答案里提到的大角度下的抗鋸齒等。就我寫的這個流程的話,後來總結了個稍微詳細的步驟放在這裡:利用OpenCV檢測圖像中的長方形畫布或紙張並提取圖像內容
=========== 分割線 ===========
寫個基於OpenCV的思路供參考。
紙張四角的坐標(圖中紅點)已知的情況
如果左圖的4個紅點是可以手動標註,那麼就簡單了:用OpenCV的Perspective Transform就可以了。具體步驟如下:將標註好的點存入一個叫corner的變數里,而對應的正方形圖案的四個對應的corner放到canvas_corner的變數里,假設原圖叫image,代碼如下:M= cv2.getPerspectiveTransform(corners, canvas_corner)
corrected_image = cv2.warpPerspective(image, M, (0, 0))
用題目里的照片試了試,結果如下:
紙張四角的坐標未知或難以準確標註的情況一個思路是先手動標出大概的紙張區域,然後檢測紙張邊緣,然後生成直線和四角,再用Perpective Transform就可以了。試了一下下面這套流程大概可以:1) 大概標定紙張區域引入手動輔助的分割,這裡我用的是OpenCV的grabcut,mask和結果如下:
可以看到,分割後的結果雖然能基本區分紙張形狀了,可是邊緣並不準確。另外鍵盤和部分桌面沒能區分開來,這時可以繼續用grabcut得到只有紙張的分割,或者為了用戶友好的話可以先檢測邊緣,再做後期處理。那麼得到的mask可以這麼用:保留mask邊緣附近的信息用於真正的邊緣檢測,而把其他部分都模糊處理,也就是說基於上面得到的mask做出下面的mask:基於這個mask把除了mask以外的區域全都模糊處理掉:
然後用OpenCV的Canny演算法得到邊緣:接下來OpenCV的Hough(我用的是cv2.HoughLinesP())直線檢測:可以看到,有些線幾乎重合在一起了,這是難以避免的,上圖中一共檢測到9條線,其中兩對(下、右邊緣)重合。可以通過距離判斷和直線相對角度來判斷並把重合線段合為一條:透視變換就不說了,
我貢獻一個找矩形的思路,類似於 OpenCV 自帶例子 squares.cpp 里的弄法。方法是:- RGB -&> HSV,然後通過飽和度來確定白色(白色飽和度低)
- 用一系列閾值進行閾值化,findContours 找聯通區域,approxPolyDP來擬合多邊形。(閾值化時用到了一個非常呵呵的判斷策略:如果70%的圖像全白了,那麼沒必要繼續往上加閾值了,不這樣的話,整幅圖像全白時,就是最大的矩形,我們不希望如此)
- 找到符合條件的最大矩形區域(的四個頂點)。
優點是不用人工參與,不足之處是,太簡單粗暴了,所以只能處理題主這種簡單情況,無法處理光照這類複雜問題。
代碼:double angle(Point pt1, Point pt2, Point pt0)
{
double dx1 = pt1.x - pt0.x;
double dy1 = pt1.y - pt0.y;
double dx2 = pt2.x - pt0.x;
double dy2 = pt2.y - pt0.y;
return (dx1*dx2 + dy1*dy2) / sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10);
}
vector&
{
Mat hsv;
cvtColor(image, hsv, CV_BGR2HSV);
vector&
split(hsv, channels);
vector&
double maxArea = 0.0;
for (int i = 0; i &<= 90; i += 1)
{
// 提取 Hue 通道
Mat binaryImage = channels[1] &< i;
// 如果超過 70% 的像素都變白了,那麼閾值化過頭了,直接退出
if (countNonZero(binaryImage) &> image.rows * image.cols * 0.7)
{
break;
}
// 找到連通區域
vector&
findContours(binaryImage, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
// 多邊形近似
for (int i = 0; i &< contours.size(); ++i)
{
vector&
approxPolyDP(contours[i], polygon, arcLength(contours[i], 1) * 0.02, 1);
double area = fabs(contourArea(polygon));
// 把不可能是矩形的區域丟掉
if (isContourConvex(polygon)
polygon.size() == 4
area &> maxArea)
{
double maxCosine = 0;
for (int j = 2; j &< 5; j++)
{
double cosine =
fabs(angle(polygon[j % 4], polygon[j - 2], polygon[j - 1]));
maxCosine = MAX(maxCosine, cosine);
}
if (maxCosine &< 0.3)
{
maxArea = area;
rect = polygon;
}
}
}
}
return rect;
}
效果:
@達聞西 答案很贊!
補充一下,從圖中效果看,似乎還涉及到點陣圖矢量化(或者點陣圖的抗鋸齒變換)。有許多種不同的方案可以做到。轉到頻率域求旋轉角度
哥們 你用我的圖啊 這個演算法我早就搞定了 非常精確 角度隨便
這是我偉大的ps 10分鐘搞定 改變你一下透視角 剪切 調對比色階具體教程網上找
前幾天剛寫過跟這個一模一樣的,透視變換,加上插值四個角點是手動標吧
推薦閱讀:
※遞歸有什麼意義?
※計算機是怎樣進行開方和冪運算的?
※求一千萬以內由兩個素數相乘的數,並按從小到大排列,如:6=2*3,10=2*5。有什麼比較好的思路嗎?
※在學習數據結構與演算法的時候,一旦出現遞歸就很難理解。請問對於遞歸有沒有什麼好的理解方法?
※世界上最複雜的程序演算法有哪些?是如何設計和用來做什麼的?
TAG:演算法 |