OpenCV實(shí)現(xiàn)傾斜文字校正
基于OpenCV的傾斜文字校正,供大家參考,具體內(nèi)容如下
使用OpenCV里example中的的傾斜文本作為輸入,本文的目的即將該傾斜的文本校正成水平方向的文本。
主要思路為:
讀取圖像-——>Canny邊緣檢測——->形態(tài)學(xué)操作-——>提取最小外接矩形——->計(jì)算旋轉(zhuǎn)矩陣-——>仿射變換校正文本圖像
原始圖像:
提取最小外接矩形區(qū)域
校正后的圖像
主要涉及的API
創(chuàng)建滑動條
這個API可以創(chuàng)建一個滑動條,可以在不改變程序的情況下更改變量的值來顯示圖像變化的效果。在這個場景中使用創(chuàng)建滑動條來調(diào)節(jié)Canny邊緣檢測的閾值,提取合適的邊緣。
```cpp Clicking the label of each trackbar enables editing the trackbar values manually. @param trackbarname Name of the created trackbar.\\第一個參數(shù)為滑動條的名稱 @param winname Name of the window that will be used as a parent of the created trackbar. \\ 滑動條所依附的窗口名稱 @param value Optional pointer to an integer variable whose value reflects the position of the slider. Upon creation, the slider position is defined by this variable. \\ 引用值,即拖動滑動條所改變的值,需要提前定義,定義好的值即為滑動條的初始值。 @param count Maximal position of the slider. The minimal position is always 0. \\滑動條的最大位置 @param onChange Pointer to the function to be called every time the slider changes position. This function should be prototyped as void Foo(int,void\*); , where the first parameter is the trackbar position and the second parameter is the user data (see the next parameter). If the callback is the NULL pointer, no callbacks are called, but only value is updated. \\ 定義回調(diào)函數(shù),每次滑動滑動條都會調(diào)用這個回調(diào)函數(shù)。回調(diào)函數(shù)的格式為 void Foo(int,void*)其中第一個參數(shù)為軌跡條的位置,第二個參數(shù)是用戶數(shù)據(jù)userdata。 @param userdata User data that is passed as is to the callback. It can be used to handle trackbar events without using global variables. \\ 用戶傳遞給回調(diào)函數(shù)的數(shù)據(jù)userdata,這里使用的是全局變量,因此這一項(xiàng)可以忽略。 CV_EXPORTS int createTrackbar(const String& trackbarname, const String& winname, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? int* value, int count, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? TrackbarCallback onChange = 0, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? void* userdata = 0);
### Canny邊緣檢測 使用Canny邊緣檢測算法提取文本圖像的邊緣 ` ```cpp /** @brief Finds edges in an image using the Canny algorithm @cite Canny86 . The function finds edges in the input image and marks them in the output map edges using the Canny algorithm. The smallest value between threshold1 and threshold2 is used for edge linking. The largest value is used to find initial segments of strong edges. See <http://en.wikipedia.org/wiki/Canny_edge_detector> @param image 8-bit input image.? // 輸入圖像 @param edges output edge map; single channels 8-bit image, which has the same size as image . // 輸出單通道的八位圖像 @param threshold1 first threshold for the hysteresis procedure. // 閾值(低于此閾值的直接被剔除) @param threshold2 second threshold for the hysteresis procedure. // 閾值2(高于此閾值的被認(rèn)為為邊緣) @param apertureSize aperture size for the Sobel operator. // 表示Sobel算子的模板大小 @param L2gradient a flag, indicating whether a more accurate \f$L_2\f$ norm \f$=\sqrt{(dI/dx)^2 + (dI/dy)^2}\f$ should be used to calculate the image gradient magnitude (L2gradient=true ), or whether the default \f$L_1\f$ norm\f$=|dI/dx|+|dI/dy|\f$ is enough (L2gradient=false ). // 使用更精確的L2范數(shù)(True)還是默認(rèn)的L1范數(shù)(默認(rèn)) ?*/ CV_EXPORTS_W void Canny( InputArray image, OutputArray edges, ? ? ? ? ? ? ? ? ? ? ? ? ?double threshold1, double threshold2, ? ? ? ? ? ? ? ? ? ? ? ? ?int apertureSize = 3, bool L2gradient = false );
邊緣檢測的結(jié)果,由于測試的樣本圖像比較理想,調(diào)節(jié)滑動條的位置影響不是很大。如果圖像質(zhì)量較差,也可以添加圖像預(yù)處理步驟優(yōu)化源圖像。
形態(tài)學(xué)處理
使用形態(tài)學(xué)處理操作連接經(jīng)過Canny邊緣檢測處理后字符縫隙,使其變成一個連通域。
步驟:創(chuàng)建結(jié)構(gòu)元素(getStructuringElement)->膨脹處理
Mat src_dilate; Mat kernel = getStructuringElement(MORPH_RECT, Size(11, 11), Point()); // 創(chuàng)建結(jié)構(gòu)元素的大小,第一個參數(shù)為結(jié)構(gòu)元素的形狀(矩形MORPH_RECT,十字形結(jié)構(gòu)MORPH_CROSS,橢圓形結(jié)構(gòu)MORPH_ELLIPSE),第二個參數(shù)為結(jié)構(gòu)元素的大小,第三個參數(shù)為錨點(diǎn)的位置。 dilate(Canny_edge, src_dilate, kernel, Point(-1, -1), 1, BORDER_DEFAULT); // 膨脹圖像,第一個參數(shù)為輸入圖像,第二個為輸出圖像,第三個參數(shù)為結(jié)構(gòu)元素,第四個參數(shù)為錨點(diǎn)的位置,第五個參數(shù)為迭代的次數(shù),最后一個參數(shù)為邊界填充的類型
膨脹后的圖像顯示為:
查找輪廓
vector<vector<Point>> Contours; // 定義邊緣的點(diǎn)集 vector<Vec4i> hierarchy; // 定義邊緣的層次關(guān)系 findContours(src_dilate, Contours, hierarchy, RETR_TREE, CHAIN_APPROX_NONE, Point());
/** @brief Finds contours in a binary image. The function retrieves contours from the binary image using the algorithm @cite Suzuki85 . The contours are a useful tool for shape analysis and object detection and recognition. See squares.cpp in the OpenCV sample directory. @note Since opencv 3.2 source image is not modified by this function. @param image Source, an 8-bit single-channel image. Non-zero pixels are treated as 1's. Zero pixels remain 0's, so the image is treated as binary . You can use #compare, #inRange, #threshold ,#adaptiveThreshold, #Canny, and others to create a binary image out of a grayscale or color one.If mode equals to #RETR_CCOMP or #RETR_FLOODFILL, the input can also be a 32-bit integer image of labels (CV_32SC1). // 輸入圖像 @param contours Detected contours. Each contour is stored as a vector of points (e.g. std::vector<std::vector<cv::Point> >). // 輸出邊緣的點(diǎn)集 @param hierarchy Optional output vector (e.g. std::vector<cv::Vec4i>), containing information about the image topology(拓?fù)浣Y(jié)構(gòu)). It has as many elements as the number of contours. For each i-th contour contours[i], the elements hierarchy[i][0] , hierarchy[i][1] , hierarchy[i][2] , and hierarchy[i][3] are set to 0-based indices in contours of the next and previous contours at the same hierarchical level, the first child contour and the parent contour, respectively. If for the contour i there are no next, previous, parent, or nested contours, the corresponding elements of hierarchy[i] will be negative. //輪廓之間的層次結(jié)構(gòu)關(guān)系(下一個輪廓、前一個輪廓、第一個子輪廓、父輪廓) @param mode Contour retrieval mode, see #RetrievalModes // 檢索模式:RETR_EXTERNAL 僅檢索外部輪廓 RETR_LIST 在不建立層次結(jié)構(gòu)的情況下檢索所有輪廓 RETR_CCOMP 檢索具有兩級層次結(jié)構(gòu)(外部和孔)的所有輪廓。 RETR_TREE檢索所有輪廓,在輪廓之間創(chuàng)建完整的層次結(jié)構(gòu) @param method Contour approximation method, see #ContourApproximationModes // 輪廓的形狀近似方法 // CV_CHAIN_APPROX_NONE 不對輪廓應(yīng)用任何近似方法并存儲輪廓點(diǎn) // CV_CHAIN_APPROX_SIMPLE 壓縮所有水平、垂直和對角線段,僅存儲起點(diǎn)和終點(diǎn) // 等其他近似算法 @param offset Optional offset by which every contour point is shifted. This is useful if the contours are extracted from the image ROI and then they should be analyzed in the whole image context. // 定義偏移量 ?*/ CV_EXPORTS_W void findContours( InputArray image, OutputArrayOfArrays contours, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OutputArray hierarchy, int mode, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? int method, Point offset = Point());
最小外接矩形
旋轉(zhuǎn)矩形對象類型 RotatedRect
輪廓的最小外接矩形 minAreaRect
/** @brief Finds a rotated rectangle of the minimum area enclosing the input 2D point set. The function calculates and returns the minimum-area bounding rectangle (possibly rotated) for a specified point set. Developer should keep in mind that the returned RotatedRect can contain negative indices when data is close to the containing Mat element boundary. // 該函數(shù)輸入邊緣的點(diǎn)集,并返回一個最小外接矩形,當(dāng)數(shù)據(jù)靠近邊界的時候,返回的RotatedRect可能包含有負(fù)所引。 @param points Input vector of 2D points, stored in std::vector\<\> or Mat ?*/ CV_EXPORTS_W RotatedRect minAreaRect( InputArray points );
計(jì)算旋轉(zhuǎn)矩陣
Mat Rotation = getRotationMatrix2D(center,RRect_degree,1.0); Mat output; warpAffine(src,output,Rotation,src.size(),INTER_CUBIC,BORDER_CONSTANT,Scalar(255,255,255)); // 輸入要變換圖像,輸出圖像,定義旋轉(zhuǎn)矩陣,定義插值方式,邊界類型(邊界類型需要注意)
完整代碼
#include <iostream> #include <opencv2/opencv.hpp> #include <opencv2/highgui.hpp> #include <opencv2/calib3d.hpp> using namespace std; using namespace cv; // Define Mat Mat src, gray_src, dst; const char *IWindow = "InputWindow"; const char *OWindow = "OutputWindow"; int canny_threshold = 100; int threshold_level = 255; void Canny_function(int, void *); int main(int argc, char **argv) { ? ? src = imread("D:/Delete/imageTextR.png"); ? ? if (src.empty()) { ? ? ? ? cout << "Could not load image" << endl; ? ? ? ? return -1; ? ? } ? ? // Covert to GrayScale ? ? cvtColor(src, gray_src, COLOR_BGR2GRAY); ? ? namedWindow(IWindow, WINDOW_AUTOSIZE); ? ? imshow(IWindow, src); ? ? namedWindow(OWindow, WINDOW_AUTOSIZE); ? ? createTrackbar("Canny_Threshold", IWindow, &canny_threshold, threshold_level, Canny_function); ? ? waitKey(); ? ? return 0; } void Canny_function(int, void *) { ? ? Mat Canny_edge; ? ? Canny(gray_src, Canny_edge, canny_threshold, canny_threshold * 2, 3, false); ? ? //Display Canny_edge ? ? //imshow(OWindow,Canny_edge); ? ? Mat src_dilate; ? ? Mat kernel = getStructuringElement(MORPH_RECT, Size(11, 11), Point()); ? ? dilate(Canny_edge, src_dilate, kernel, Point(-1, -1), 1, BORDER_DEFAULT); ? ? //Display Dilate image ? ? imshow(OWindow,src_dilate); ? ? // Find Contour ? ? vector<vector<Point>> Contours; ? ? vector<Vec4i> hierarchy; ? ? findContours(src_dilate, Contours, hierarchy, RETR_TREE, CHAIN_APPROX_NONE, Point()); ? ? // Select the Max area Contour ? ? double MaxAreaRRect = 0; ? ? int SizeContour = 0; ? ? for (size_t t = 0; t < Contours.size(); t++) { ? ? ? ? RotatedRect RRect = minAreaRect(Contours[t]); ? ? ? ? double AreaRRect = 0; ? ? ? ? AreaRRect = RRect.size.area(); ? ? ? ? MaxAreaRRect = max(MaxAreaRRect, AreaRRect); ? ? } ? ? double RRect_degree = 0; ? ? dst = src.clone(); // 這里涉及是否復(fù)制數(shù)據(jù)的問題 ? ? for (size_t t = 0; t < Contours.size(); t++) { ? ? ? ? RotatedRect RRect = minAreaRect(Contours[t]); ? ? ? ? double AreaRRect = RRect.size.area(); ? ? ? ? if (AreaRRect == MaxAreaRRect ) { ? ? ? ? ? ? SizeContour = SizeContour + 1; ? ? ? ? ? ? // Rotate degree ? ? ? ? ? ? RRect_degree = RRect.angle; ? ? ? ? ? ? // Draw this rectangle ? ? ? ? ? ? Point2f vertex[4]; ? ? ? ? ? ? RRect.points(vertex); ? ? ? ? ? ? for (int i = 0; i < 4; i++) { ? ? ? ? ? ? ? ? line(dst, Point(vertex[i]), Point(vertex[(i + 1) % 4]), Scalar(0, 255, 0), 2, LINE_8); ? ? ? ? ? ? } ? ? ? ? } ? ? } ? ? cout << "SizeContour : \t "<< SizeContour <<endl; ? ? cout << "Rotated Rectangle angle : " << RRect_degree << endl; ? ? //imshow(OWindow, dst); ? ? Point2f center(src.cols/2,src.rows/2); ? ? Mat Rotation = getRotationMatrix2D(center,RRect_degree,1.0); ? ? Mat output; ? ? warpAffine(src,output,Rotation,src.size(),INTER_CUBIC,BORDER_CONSTANT,Scalar(255,255,255)); ? ? // Display Rotate Rectangle ? ? namedWindow("Final_Result",WINDOW_AUTOSIZE); ? ? imshow("Final_Result", output); }
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C++中g(shù)etline()、gets()等函數(shù)的用法詳解
這篇文章主要介紹了C++中g(shù)etline()、gets()等函數(shù)的用法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-02-02C語言實(shí)現(xiàn)整數(shù)逆序的情況解析
今天通過本文給大家介紹C語言實(shí)現(xiàn)整數(shù)逆序的情況,本文通過實(shí)例代碼多種舉例給大家介紹的非常詳細(xì),對C語言整數(shù)逆序相關(guān)知識感興趣的朋友跟隨小編一起看看吧2021-11-11探討C++中不能聲明為虛函數(shù)的有哪些函數(shù)
下面小編就為大家?guī)硪黄接慍++中不能聲明為虛函數(shù)的有哪些函數(shù)。希望對大家有所幫助。一起跟隨小編過來看看吧,祝大家游戲愉快哦2017-01-01IOS開發(fā)之UIScrollView實(shí)現(xiàn)圖片輪播器的無限滾動
這篇文章主要介紹了IOS開發(fā)之UIScrollView實(shí)現(xiàn)圖片輪播器的無限滾動的相關(guān)資料,需要的朋友可以參考下2017-07-07