学习OpenCV有一段时间了,虽然已经正式成为拉环小哥整整一个月,可是仍然想着学习本身喜欢的。说实话,想着你们都在往本身喜欢的方向奔,我不肯成为无所事事的人。函数
可能仍是MSP的氛围好吧,想起昕羽姐辞掉微软的工做,一心想当一名插画师,去年这个时候还写明信片鼓励我。你们都这么优秀,说真的,不想拖你们后腿。学习
车辆段是挺好的一个地方,确实没想到会分回来。既然来了那就先这样呗,最起码我要先把本身的生活安顿下来,最起码要让生活走上正轨。测试
如今的我没有什么突出的能力,没有什么钱,没有什么过硬的技术。这些都只能慢慢来,说到底,仍是我本身太菜了。spa
好了,不说这些有的没的了。.net
如今拿工资了,终于有钱买实验设备跟学习教程了!很开心!code
言归正传,说说这两天学的Open CV切边。orm
七月份刚毕业的时候,在淘宝上花了15块买了份Open CV的盗版视频。视频
下载下来发现是51CTO的收费视频,想着这么贵讲的应该还不错,而后就学习了下。blog
这个老师叫贾志刚,我喜欢叫他沙雕老师,由于,讲的实在是太沙雕了。在第一部分的课里,净在讲骚话。从高数到语文,从历史到政治,不只教你背古诗还教你撩妹!说骚话张口就来,一讲到硬核的部分就emmm...真是服气。听不懂也没办法只能在网上搜一下相关的文章,看看原理,补一补高数。教程
抄抄老师的代码,运行一下,总结一下,就这样混过去了。
直到这两天,开始作小案例。
小案例就是把歪着的图片矫正,而且切除多余部分。
看,就是下面这个dogB 王源
而后要矫正。
这个沙雕老师真是坑的一匹,代码写的只有他的图片能用,原理也讲不通,水的要死。
原理是这样的:
Canny检测边缘->比较最大的轮廓->肯定最大轮廓->按照轮廓画图->矫正切边
从代码能够看出来:
代码我有至关一部分看的是这篇文章:
https://blog.csdn.net/weixin_...
int main(int,void*) { //...imread..xxxx Check_Skew(); } void Check_Skew(); { Mat canny_output; cvtColor(src_img, gray_img, COLOR_BGR2GRAY); //将原图转化为灰度图 Canny(gray_img, canny_output, threshold_value, threshold_value * 2, 3, false); // canny边缘检测 vector<vector<Point>> contours; vector<Vec4i> hireachy; findContours(canny_output, contours, hireachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0)); // 找到全部轮廓 Mat drawImg = Mat::zeros(src_img.size(), CV_8UC3); float max_width = 0; // 定义最大宽度 float max_height = 0; // 定义最大高度 double degree = 0; // 定义旋转角度 for (auto t = 0; t < contours.size(); ++t) // 遍历每个轮廓 { RotatedRect minRect = minAreaRect(contours[t]); // 找到每个轮廓的最小外包旋转矩形,RotatedRect里面包含了中心坐标、尺寸以及旋转角度等信息 degree = abs(minRect.angle); if (degree > 0) { max_width = max(max_width, minRect.size.width); max_height = max(max_height, minRect.size.height); } } RNG rng(12345); for (auto t = 0; t < contours.size();++t) { RotatedRect minRect = minAreaRect(contours[t]); if (max_width == minRect.size.width && max_height == minRect.size.height) { degree = minRect.angle; // 保存目标轮廓的角度 Point2f pts[4]; minRect.points(pts); Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)); //产生随机颜色 for (int i = 0; i < 4; ++i) { line(drawImg, pts[i], pts[(i + 1) % 4], color, 2, 8, 0); } } } imshow("找到的矩形轮廓", drawImg); Point2f center(src_img.cols / 2, src_img.rows / 2); Mat rotm = getRotationMatrix2D(center, degree, 1.0); //获取仿射变换矩阵 Mat dst; warpAffine(src_img, dst, rotm, src_img.size(), INTER_LINEAR, 0, Scalar(255, 255, 255)); // 进行图像旋转操做 imwrite("123.png", dst); //将校订后的图像保存下来 imshow("Correct Image", dst); }
这些代码看起来比较长,我分几部分。
这些是找轮廓的:
Mat canny_output; cvtColor(src_img, gray_img, COLOR_BGR2GRAY); //将原图转化为灰度图 Canny(gray_img, canny_output, threshold_value, threshold_value * 2, 3, false); // canny边缘检测 vector<vector<Point>> contours; vector<Vec4i> hireachy; findContours(canny_output, contours, hireachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0)); // 找到全部轮廓
这些是找最大的轮廓并记录它的高度宽度,以用于后面绘制它
Mat drawImg = Mat::zeros(src_img.size(), CV_8UC3); float max_width = 0; // 定义最大宽度 float max_height = 0; // 定义最大高度 double degree = 0; // 定义旋转角度 for (auto t = 0; t < contours.size(); ++t) // 遍历每个轮廓 { RotatedRect minRect = minAreaRect(contours[t]); // 找到每个轮廓的最小外包旋转矩形,RotatedRect里面包含了中心坐标、尺寸以及旋转角度等信息 degree = abs(minRect.angle); if (degree > 0) { max_width = max(max_width, minRect.size.width); max_height = max(max_height, minRect.size.height); } }
这些是挨个比较,看大家这一堆轮廓中哪一个是我刚刚找到的最大轮廓,而且绘制出来。
(这地方就是沙雕老师水平不行的地方,代码写得稀烂,明明能一次作完的事儿非要干第二次,有点蠢)
RNG rng(12345); for (auto t = 0; t < contours.size();++t) { RotatedRect minRect = minAreaRect(contours[t]); if (max_width == minRect.size.width && max_height == minRect.size.height) { degree = minRect.angle; // 保存目标轮廓的角度 Point2f pts[4]; minRect.points(pts); Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)); //产生随机颜色 for (int i = 0; i < 4; ++i) { line(drawImg, pts[i], pts[(i + 1) % 4], color, 2, 8, 0); } } }
剩下的只有一行比较关键:
warpAffine(src_img, dst, rotm, src_img.size(), INTER_LINEAR, 0, Scalar(255, 255, 255)); // 进行图像旋转操做
这是旋转图像的API,关键在于旋转以后,你要按着这个沙雕老师抄就会发现图片的空余地方是白色的。你须要修改的是INTER_LINEAR后面边界填充参数,就是问你想用什么东西填充空白。沙雕老师写了个0,我这是BORDER_REPLICATE看我的状况吧,不知道咋用能够看看这个
https://blog.csdn.net/qq_2494...
矫正完了就成这样了。
值得注意的是,我并非按照上面这样写的。个人代码加了中值滤波,为了消除噪声。还有很头疼的阴影。
edianBlur(src, mBlur,11);
(11的模糊力度特别大,边缘特别容易识别。)
接下来就是切去周围多余的边了,然而坑爹的就在这儿了。
矫正以后反而还识别不了了。What The Fuck?你原图都识别出来了,矫正还识别不出来了?
我仔细找了下缘由:
矫正后,
if (max_width == minRect.size.width && max_height == minRect.size.height)
该条件没法成立,
按道理来讲咱们应该选绿框,那怎么才能挑出绿框呢?
那就记录找出知足任意max条件的面积最大的,或者你你看max_Width不能小于多少,把红框过滤掉。
这样就解决了最头疼的问题。
大功告成~
PS:期间找不到边缘的时候我曾怀疑过是否是我边缘不够亮,还用PS修了一下。。妈个鸡想一想都以为丢人。
最后,切边总算是快要结束了~
void findROI(int, void*) { printf("**************当前阈值:%d******************************\n", threshold_value); //cvtColor(src_img, gray_img, COLOR_BGR2GRAY); //将原图转化为灰度图 Mat canny_output; Mat mBlur; medianBlur(src, mBlur, 11); Canny(mBlur, canny_output, threshold_value, threshold_value * 2, 3, false); // canny边缘检测 imshow("canny_output", canny_output); vector<vector<Point>> contours; vector<Vec4i> hireachy; findContours(canny_output, contours, hireachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0)); // 调用API,找到轮廓 // 筛选contours中的轮廓,咱们须要最大的那个轮廓 float max_width = 0; // 定义最大宽度 float max_height = 0; // 定义最大高度 double degree = 0; // 定义旋转角度 //这个for是为了找最大的框 for (auto t = 0; t < contours.size(); ++t) // 遍历每个轮廓 { RotatedRect minRect = minAreaRect(contours[t]); // 找到每个轮廓的最小外包旋转矩形,RotatedRect里面包含了中心坐标、尺寸以及旋转角度等信息 degree = abs(minRect.angle); max_width = max(max_width, minRect.size.width); max_height = max(max_height, minRect.size.height); } RNG rng(12345); //定义一个随机数产生器,用来产生不一样颜色的矩形框 Mat drawImage = Mat::zeros(src_img.size(), CV_8UC3); Rect bbox; for (auto t = 0; t < contours.size(); ++t) // 遍历每个轮廓 { RotatedRect minRect = minAreaRect(contours[t]); // 找到每个轮廓的最小外包旋转矩形,RotatedRect里面包含了中心坐标、尺寸以及旋转角度等信息 if ((minRect.size.width == max_width || minRect.size.height == max_height) && minRect.size.width > 620) //筛选最小外包旋转矩形 { printf("current angle : %f\n", degree); Mat vertices; // 定义一个4行2列的单通道float类型的Mat,用来存储旋转矩形的四个顶点 boxPoints(minRect, vertices); // 计算旋转矩形的四个顶点坐标 bbox = boundingRect(vertices); //找到输入点集的最小外包直立矩形,返回Rect类型 cout << "最小外包矩形:" << bbox << endl; Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)); //产生随机颜色 for (int i = 0; i < 4; i++) // 遍历每一个旋转矩形的四个顶点坐标 { // 在相邻的顶点之间绘制直线 Mat p1 = vertices.row(i); // 使用成员函数row(i)和col(j)获得矩阵的第i行或者第j列,返回值仍然是一个单通道的Mat类型 int j = (i + 1) % 4; Mat p2 = vertices.row(j); Point p1_point = Point(p1.at<float>(0, 0), p1.at<float>(0, 1)); //将Mat类型的顶点坐标转换为Point类型 Point p2_point = Point(p2.at<float>(0, 0), p2.at<float>(0, 1)); line(src, p1_point, p2_point, color, 2, 8, 0); // 根据获得的四个顶点,经过链接四个顶点,将最小旋转矩形绘制出来 } } } imshow(output_win, src); if (bbox.width > 0 && bbox.height > 0) { Mat roiImg = src_img(bbox); //从原图中截取兴趣区域 namedWindow(roi_win, CV_WINDOW_AUTOSIZE); imshow(roi_win, roiImg); } return; }
有啥不懂的能够问我~也能够单独分模块测试。Sangyu.Li@outlook.com