前言
我们已经更新了不少OpenCV的基础文章了,为了巩固一下学习效果,我们就做的小案例的实战进行强化。
实现效果
今天我照了一张自己健身卡的图片,然后想到实现整体切边的效果,先上一下源图
最终我们想到实现在照片中只保留卡这块的部分。
代码演示
我们再新建一个项目名为opencv--qiebian,按照配置属性(VS2017配置OpenCV通用属性),然后在源文件写入#include和main方法.
上面我们把加载出来的图片用resize来调整了一下大小,一开始加载出来超过满屏了,不方便我们处理。
第一步 先把图片转为灰度图
显示效果为
第二步 用Canny边缘提取
我们来看看显示效果
可以看到右边就是我们通过边缘提取后的图片效果
第三步 发现寻找轮廓
运行后再看看效果
可以看出来,我们边缘提取后找到了1332个轮廓,这个轮廓中我们只需要找到我们想到的卡信息,所以我们就来到下一步
第四步 定义最小的轮廓宽高,画出想要的轮廓
可以看到图中卡片占了图片一半以上,所以我们可以直接考虑轮廓的大小大于源图像一半就是我们要少的轮廓了,其余的轮廓就可以忽略了,这样可以节省很多时间,代码如下:
然后我们看看运行效果
可以看出,我们要找到矩形完全不是我们想要的结果,在红色的轮廓里面发现卡片和桌面上的纹理连到一起了,说明我们在Canny边缘提取的时候需要进行二值化处理,去掉一些不用的结果
第五步 对源图像进行Canny阈值重新修改看看
我们回到Canny边缘提取那,把边缘提取的阈值重修改一下
然后我们重新运行起来看看什么效果
发现右边通过赋值后不像原来桌面上那么多纹理了,但是从左边获取的矩形框后发现也完全不是我们想要的东西。要怎么解决这个问题呢?
一开始我想过了在Canny边缘提取前先二值化操作一下,最终倒是实现了,但是这个要不停的修改阈值参数,不太方便,还有别的什么办法呢?这时候我想起来,刚开始学最基本的形态学操作的时候用到的,开操作和闭操作。我们这就来试一下,根据上面的情况,我选择的闭操作,也就是先膨胀后腐蚀的效果
然后我们再来看看运行的效果
经过闭操作后,可以看出右边的图我们过滤掉了非常多没有必要的东西,也获取到了最后我们的绿色画出的矩形框,自我满足了一下!!!!
第六步 提取到我们要的图像
我们先修改一下上面定义的参数,这样下在我们截取的时候就可以用到这个矩形了,下面是我们在复制一份src_gray,因为原图中src我们已经画上红色和绿色线了,画之前先存一个备份到src_gray里。
然后我们提取最后生成的矩形
最后我们看看生成的结果
右边就是我们完美截取的卡片图像,成功~~~~
总结一下实现流程
以上是我自己实验生成的效果,如果有更好的方法请消息我,毕竟我也是初学者~~
main单元代码
#include <opencv2\opencv.hpp> #include <iostream> cv::Mat src, src_gray, dst; const char* imgsrc = "源图"; const char* imgdst = "结果图"; int main(int argc, char** argv) { src = cv::imread("E:/DCIM/testcard.jpg"); if (src.empty()) { printf("could not read image....\n"); getchar(); return -1; } //由于加载图像有点大,我们先调整图像的大小为宽500,高300 cv::resize(src, src, cv::Size(500, 300)); cv::imshow(imgsrc, src); //把源图转为灰度图 cv::cvtColor(src, src_gray, CV_BGR2GRAY); cv::imshow(imgdst, src_gray); //高斯模糊后加Canny边缘提取 cv::GaussianBlur(src_gray, dst, cv::Size(3, 3), 0.5, 0.5, 4); //定义结构元素 cv::Mat element = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5), cv::Point(-1, -1)); //闭操作 cv::morphologyEx(dst, dst, CV_MOP_CLOSE, element); //Canny边缘提取 cv::Canny(dst, dst, 120, 255); //定义轮廓点及查找轮廓 std::vector<std::vector<cv::Point> > contours; std::vector<cv::Vec4i> hierarchy; //寻找轮廓 cv::findContours(dst, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE); //打印出一共找到了多少个轮廓 printf("contours count:%d\n", contours.size()); //定义要找的轮廓最小的宽高 int minw = dst.cols*0.5; int minh = dst.rows*0.5; //定义矩形的四个点 cv::RotatedRect minRect; cv::Point2f vertices[4]; for (size_t t = 0; t < contours.size(); t++) { //寻找最小矩形 minRect = cv::minAreaRect(contours[t]); //获取倾斜角度 double degree = abs(minRect.angle); //判断最小矩形太于我们设置的最小宽和高在获取数据 if (minRect.size.width > minw && minRect.size.height > minh) { printf("current rect: %d\n", t); printf("current angle : %f\n", degree); //在源图像上用红色画出轮廓样 src.copyTo(src_gray);//先复制一份src到src_gray里面,下面备用 cv::drawContours(src, contours, t, cv::Scalar(0, 0, 255), 1,8,hierarchy,0); //在源图像上用绿色画出矩形框,定义了4个点,然后用直线画4个点 minRect.points(vertices); for (int i = 0; i < 4; i++) { cv::line(src, vertices[i], vertices[(i + 1) % 4], cv::Scalar(0, 255, 0)); } break; } } cv::imshow(imgsrc, src); //根据获取的最小矩形截取出图像显示出来 cv::Rect rect = minRect.boundingRect(); dst = src_gray(rect); cv::imshow(imgdst, dst); cv::waitKey(0); return 0; }
-END-