写在前面的那些有的没的
学习OpenCV有一段时间了,虽然已经正式成为拉环小哥整整一个月,但是仍然想着学习自己喜欢的。说实话,想着大家都在往自己喜欢的方向奔,我不愿成为无所事事的人。
可能还是MSP的氛围好吧,想起昕羽姐辞掉微软的工作,一心想当一名插画师,去年这个时候还写明信片鼓励我。大家都这么优秀,说真的,不想拖大家后腿。
车辆段是挺好的一个地方,确实没想到会分回来。既然来了那就先这样呗,最起码我要先把自己的生活安顿下来,最起码要让生活走上正轨。
现在的我没有什么突出的能力,没有什么钱,没有什么过硬的技术。这些都只能慢慢来,说到底,还是我自己太菜了。
好了,不说这些有的没的了。
现在拿工资了,终于有钱买实验设备跟学习教程了!很开心!
言归正传,说说这两天学的Open CV切边。
什么是OpenCV切边?
七月份刚毕业的时候,在淘宝上花了15块买了份Open CV的盗版视频。
下载下来发现是51CTO的收费视频,想着这么贵讲的应该还不错,然后就学习了下。
这个老师叫贾志刚,我喜欢叫他沙雕老师,因为,讲的实在是太沙雕了。在第一部分的课里,净在讲骚话。从高数到语文,从历史到政治,不仅教你背古诗还教你撩妹!说骚话张口就来,一讲到硬核的部分就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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。