1. 切边
源图像:
需求:扫描仪扫描到的法律文件,需要切边,去掉边缘空白,这样看上去才真实,人工操作成本与时间花费高,希望程序自动实现,高效、准确。 实现思路:边缘检测 + 轮廓发现或直线检测最大外接矩形。
例子代码:
#include
效果图
总结:先利用 Canny 算子检测图像的轮廓,再利用 findContours 发现轮廓,因为这时候会得到很多轮廓,而我们只需要找到最大的轮廓,这时候可以根据源图像的情况设置过滤的条件(如本例中宽高设为不小于0.75)。找到这个矩形后,再把它画出来即可。
2. 直线检测
源图像:
需求:寻找英语试卷填空题的下划线,这个对后期的切图与自动识别都比较重要。 实现思路:通过图像形态学操作来寻找直线,霍夫获取位置信息与显示
例子代码:
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;
Mat srcImg, roiImg;
void detectLine();
void test()
{srcImg = imread("test2.jpg",IMREAD_GRAYSCALE);if (srcImg.empty()){cout << "could not load image...n" << endl;}namedWindow("Original image", CV_WINDOW_NORMAL); //CV_WINDOW_NORMAL 鼠标控制显示窗口的大小imshow("Original image", srcImg);//因为是截图,所以要去掉白边,用ROI图像的方法Rect roi = Rect(10, 10, srcImg.cols - 15, srcImg.rows - 15); //切掉白边roiImg = srcImg(roi);namedWindow("Roi image", CV_WINDOW_NORMAL);imshow("Roi image", roiImg);detectLine();
}void detectLine()
{
Mat binaryImg, morhpImg;threshold(roiImg, binaryImg, 0, 255, THRESH_BINARY_INV | THRESH_OTSU); //二值化namedWindow("Binary Result", CV_WINDOW_NORMAL);imshow("Binary Result", binaryImg);//形态学操作Mat kernel = getStructuringElement(MORPH_RECT, Size(60, 1), Point(-1, -1)); morphologyEx(binaryImg, morhpImg, MORPH_OPEN, kernel, Point(-1, -1));namedWindow("Morphology Result", CV_WINDOW_NORMAL);imshow("Morphology Result", morhpImg);//膨胀,使得直线更加明显kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));dilate(morhpImg, morhpImg, kernel);namedWindow("Dilate Result", CV_WINDOW_NORMAL);imshow("Dilate Result", morhpImg);//霍夫变换标定直线vector<Vec4i> lines; //每条线有两个点,把四个点的坐标存储起来HoughLinesP(morhpImg, lines, 1, CV_PI / 180.0, 30, 20.0, 0); // 1 为极坐标方向上的步长Mat resultImg = roiImg.clone(); //输出结果cvtColor(resultImg, resultImg, COLOR_GRAY2BGR);for (int i = 0; i < lines.size(); i++){Vec4i ln = lines[i]; //取出直线line(resultImg, Point(ln[0], ln[1]), Point(ln[2], ln[3]), Scalar(0, 0, 255), 2, 8, 0); //Point(ln[0], ln[1]), Point(ln[2], ln[3])两个坐标}namedWindow("Hough Lines-Final Result", CV_WINDOW_NORMAL);imshow("Hough Lines-Final Result", resultImg);
}
int main()
{test();waitKey(0);return 0;
}
效果图
总结:因为源图像是截图,所以最外围有白边,通常这种情况下可以利用 ROI 图像方法去掉不需要的部分,再通过二值化和形态学操作来寻找直线,最后利用霍夫概率变换获取直线的准确位置。
3. 对象提取
源图像:
需求:对图像中对象(圆形)进行提取,获取这样的对象,去掉其它干扰和非目标对象。并获取圆形的面积和周长。 实现思路:二值分割 + 形态学 + 横纵比计算。
步骤: - 找到对象的轮廓 - 通过面积、横纵比过滤点其它不需要的对象
第一步:发现轮廓 例子代码:
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;
//第一步
Mat srcImg, binaryImg, dstImg;
void test()
{srcImg = imread("case3.png", IMREAD_GRAYSCALE);if (srcImg.empty()){cout << "could not load image...n" << endl;}namedWindow("Original image", CV_WINDOW_NORMAL);imshow("Original image", srcImg);//二值化threshold(srcImg, binaryImg, 0, 255, THRESH_BINARY | THRESH_OTSU);namedWindow("Binary Result", CV_WINDOW_NORMAL);imshow("Binary Result", binaryImg);//形态学操作,开操作,去掉小的对象,闭操作,连接里面的洞(开闭操作要先获得结构元素)Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1)); //Point(-1, -1)是中心点,这里是 2 x 2位置morphologyEx(binaryImg, dstImg, MORPH_CLOSE, kernel, Point(-1, -1));namedWindow("Close Result", CV_WINDOW_NORMAL);imshow("Close Result", dstImg);kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1)); morphologyEx(dstImg, dstImg, MORPH_OPEN, kernel, Point(-1, -1));namedWindow("Open Result", CV_WINDOW_NORMAL);imshow("Open Result", dstImg);//轮廓发现vector<vector<Point>> contours; //存储轮廓vector<Vec4i> hireachy;findContours(dstImg, contours, hireachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());Mat resultImg = Mat::zeros(srcImg.size(), CV_8SC3);for (int i = 0; i < contours.size(); i++){drawContours(resultImg, contours, i, Scalar(0, 0, 255), 2, 8, Mat(), 0, Point()); //画出来}namedWindow("Final Result", CV_WINDOW_NORMAL);imshow("Final Result", resultImg);
}
int main()
{test();waitKey(0);return 0;
}
效果图
第二步:过滤并计算面积和周长 例子代码:
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;
//第二步 通过面积过滤,通过纵横比的测量,圆形的纵横比应该在1:1左右,如果不是1:1,就把它过滤掉
Mat srcImg, binaryImg, dstImg;
void test()
{srcImg = imread("case3.png", IMREAD_GRAYSCALE);if (srcImg.empty()){cout << "could not load image...n" << endl;}namedWindow("Original image", CV_WINDOW_NORMAL); //CV_WINDOW_NORMAL 使得鼠标可以控制显示窗口的大小imshow("Original image", srcImg);//二值化threshold(srcImg, binaryImg, 0, 255, THRESH_BINARY | THRESH_OTSU);namedWindow("Binary Result", CV_WINDOW_NORMAL);imshow("Binary Result", binaryImg);//形态学操作,开操作,去掉小的对象,闭操作,连接里面的洞(开闭操作要先获得结构元素)Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1)); //Point(-1, -1)是中心点,这里是 2 x 2位置morphologyEx(binaryImg, dstImg, MORPH_CLOSE, kernel, Point(-1, -1));namedWindow("Close Result", CV_WINDOW_NORMAL);imshow("Close Result", dstImg);kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));morphologyEx(dstImg, dstImg, MORPH_OPEN, kernel, Point(-1, -1));namedWindow("Open Result", CV_WINDOW_NORMAL);imshow("Open Result", dstImg);//轮廓发现vector<vector<Point>> contours; //存储轮廓vector<Vec4i> hireachy;findContours(dstImg, contours, hireachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());//通过面积过滤,通过纵横比的测量,圆形的纵横比应该在1:1左右,如果不是1:1,就把它过滤掉Mat resultImg = Mat::zeros(srcImg.size(), CV_8SC3);Point cc;for (int i = 0; i < contours.size(); i++){//面积过滤double area = contourArea(contours[i]); //循环获取指定的面积if (area < 100) //通过循环过滤掉小于100的面积continue;//横纵比过滤Rect rect = boundingRect(contours[i]);float ratio = float(rect.width) / float(rect.height);if (ratio < 1.1 && ratio> 0.9) //把满足条件的保留画出来{drawContours(resultImg, contours, i, Scalar(0, 0, 255), -1, 8, Mat(), 0, Point()); //画出来 第五个参数改为 -1 ,使得整个圆形填充cout << "circle area: " << area << endl; //面积和周长打印出来(像素度量)cout << "circle length: " << arcLength(contours[i],true) << endl;//找中心点int x = rect.x + rect.width / 2;int y = rect.y + rect.height / 2;cc = Point(x, y);circle(resultImg, cc, 2, Scalar(0, 0, 255), 2, 8, 0); //画出中心点}}namedWindow("Final Result", CV_WINDOW_NORMAL);imshow("Final Result", resultImg);//在原图上定位显示中心点Mat circleImg = srcImg.clone();cvtColor(circleImg, circleImg, COLOR_GRAY2BGR); circle(circleImg, cc, 2, Scalar(0, 0, 255), 2, 8, 0);namedWindow("Center Point", CV_WINDOW_NORMAL);imshow("Center Point", circleImg);
}int main()
{test();waitKey(0);return 0;
}
效果图
总结:首先把图像二值化,通过闭操作把中间小的洞连接上,再通过开操作把周围小的点去掉,再通过轮廓发现找到对象的轮廓。再通过面积、纵横比过滤点其它不需要的对象,圆形的纵横比应该在1:1左右,如果不是1:1,就把它过滤掉。然后再计算面积和周长。
欢迎关注我的微信公众号“OpenCV图像处理算法”,主要是记录自己学习图像处理算法的历程,包括特征提取、目标跟踪、定位、机器学习和深度学习,每一个例子都会提供源码和例子所用的资料,欢迎同行的同学关注我和我一起虚度光阴吧!!!