源图像:
需求:扫描仪扫描到的法律文件,需要切边,去掉边缘空白,这样看上去才真实,人工操作成本与时间花费高,希望程序自动实现,高效、准确。 实现思路:边缘检测 + 轮廓发现或直线检测最大外接矩形。
例子代码:
#include
效果图
总结:先利用 Canny 算子检测图像的轮廓,再利用 findContours 发现轮廓,因为这时候会得到很多轮廓,而我们只需要找到最大的轮廓,这时候可以根据源图像的情况设置过滤的条件(如本例中宽高设为不小于0.75)。找到这个矩形后,再把它画出来即可。
源图像:
需求:寻找英语试卷填空题的下划线,这个对后期的切图与自动识别都比较重要。 实现思路:通过图像形态学操作来寻找直线,霍夫获取位置信息与显示
例子代码:
#include
#include
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 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 图像方法去掉不需要的部分,再通过二值化和形态学操作来寻找直线,最后利用霍夫概率变换获取直线的准确位置。
源图像:
需求:对图像中对象(圆形)进行提取,获取这样的对象,去掉其它干扰和非目标对象。并获取圆形的面积和周长。 实现思路:二值分割 + 形态学 + 横纵比计算。
步骤: - 找到对象的轮廓 - 通过面积、横纵比过滤点其它不需要的对象
第一步:发现轮廓 例子代码:
#include
#include
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> contours; //存储轮廓
vector 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
#include
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> contours; //存储轮廓
vector 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图像处理算法”,主要是记录自己学习图像处理算法的历程,包括特征提取、目标跟踪、定位、机器学习和深度学习,每一个例子都会提供源码和例子所用的资料,欢迎同行的同学关注我和我一起虚度光阴吧!!!