1、原理:
计算图片里主要少数线条的平均角度,大于某个值则认为倾斜
2、效果图:
倾斜的(设角度大于12度为倾斜)
正常的
3、流程
a、先对图片进行大小缩放,加快处理速度
Mat srcPic = imread(pInFileName); int cols=configText.resizeCols; int rows=srcPic.rows/(srcPic.cols*1.00/cols);//按宽高比例缩放 Mat src =Mat::zeros(rows,cols , CV_8UC3); resize(srcPic,src,src.size());
b、如果要检索的图片具有共同的特征(都是货架且货架颜色相同)可采用阈值化二分,让边缘检测更有方向性(慎用!)
threshold(srcImage, srcThrImage, 40, 255, THRESH_BINARY); imshow("阈值化后的效果图", srcThrImage);
c、通过Canny算子进行边缘检测
高阈值求边缘低阈值平滑边缘,可以将高低阈值的比率设置为足够大如factor=5使得边缘更加的平滑
Canny(srcThrImage, midImage, cannyThreshold,cannyThreshold*factor);//Canny边缘检测算法,指定高低阈值(梯度幅值)
d、根据边缘用HoughLinesP霍夫线变换检测直线,最终需要控制检测到的直线条数
方法说明:
霍夫变换使用极坐标来表示直线(极径rho和极角theta),一条直线能够通过在平面 - 寻找交于一点的曲线数量来检测
CV_EXPORTS_W void HoughLinesP( InputArray image, OutputArray lines,
double rho, double theta, int threshold,
double minLineLength=0, double maxLineGap=0 );
rho:就是一个半径的分辨率
theta:角度分辨率
threshold:判断直线点数的阈值
minLineLength:线段长度阈值
minLineGap:线段上最近两点之间的阈值(不大于此阈值则会认为是同一条直线上的点)
注意:
1、参数 threshold、minLineLength的设置很重要,关系到是否能过检测到线段及数量,需要根据目标图片的大小特征实验得到
2、minLineGap建议设置为2会使实际的轮廓更多的识别为线段,太大也会导致识别太多的错误线段。
HoughLinesP(midImage, lines, 1, CV_PI / 180, configText.thresholdVal, configText.minLineLength, configText.maxLineGap);//HoughLinesP霍夫线变换 while (lines.size()>10)//为了保持检查到的直线条数需要对threshold进行递减或递增 { cannyThreshold+=10; Canny(srcThrImage, midImage, cannyThreshold,cannyThreshold*factor); cvtColor(midImage, dstImage, CV_GRAY2BGR); HoughLinesP(midImage, lines, 1, CV_PI / 180, configText.thresholdVal, configText.minLineLength, configText.maxLineGap); } if (!lines.size()) { cannyThreshold-=10; Canny(srcThrImage, midImage, cannyThreshold,cannyThreshold*factor); cvtColor(midImage, dstImage, CV_GRAY2BGR); HoughLinesP(midImage, lines, 1, CV_PI / 180, configText.thresholdVal, configText.minLineLength, configText.maxLineGap); }
e、对检测到的线条计算平均角度用来判断是否倾斜
注意:
1、检测到最大角度不会超过45度
2、保证检测到的线条数足够多(如50,太多会有很多无效的线段导致最终结果无意义),让计算的平均角度与实际更加的符合
double DetectionSlope::GetAvgAngle(const vector<Vec4i>& lines){//通过直线求平均角度 double totalAngle=0; for (auto & line : lines) { //360度=2π弧度 auto radian=atan(abs((line[3]-line[1])*1.0/(line[2]-line[0]))); auto angle=abs(360*radian/(2*CV_PI)); if(angle>45) angle=abs(90-angle); totalAngle+=angle; } return totalAngle/lines.size(); }
4、补充
a、对于没有扭曲只是角度倾斜的图片可通过旋转来修正
//逆时针旋转图像degree角度(原尺寸) void rotateImage(Mat src, Mat& img_rotate, double degree) { //旋转中心为图像中心 Point2f center; center.x = float(src.cols / 2.0); center.y = float(src.rows / 2.0); int length = 0; length = sqrt(src.cols*src.cols + src.rows*src.rows); //计算二维旋转的仿射变换矩阵 Mat M = getRotationMatrix2D(center, degree, 1);//获取旋转矩阵 warpAffine(src, img_rotate, M, Size(length, length), 1, 0, Scalar(255,255,255));//仿射变换,背景色填充为白色 }
b、对于存在扭曲的图片进行修正需要找到 四个角的坐标点,一般认为是检测到的直线的四个角度(这个不太靠谱,找准实际的四个点比较难)
通过检测到的四个坐标点,形成源与目标的变换矩阵,再对图片进行透视变换
Mat m1 = Mat(srcTriangle); Mat m2 = Mat(dstTriangle); Mat status; Mat h = findHomography(m1, m2, status, 0, 3);//获取单映射H(理解为两帐图片的点映射) warpPerspective(this->srcImage, this->dstImage, h, this->dstImage.size());//对图像进行透视变换,就是变形
参考:
Canny边缘检测
http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/imgproc/imgtrans/canny_detector/canny_detector.html#canny-detector
threshold阈值操作
http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/imgproc/threshold/threshold.html#basic-threshold
霍夫线变换
http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/imgproc/imgtrans/hough_lines/hough_lines.html#hough-lines
图像旋转
https://blog.csdn.net/guduruyu/article/details/70799804
Homography单映射
https://blog.csdn.net/liuphahaha/article/details/50719275
perspectiveTransform透视变换
https://blog.csdn.net/xiaowei_cqu/article/details/26478135
对倾斜的图像进行修正——基于opencv 透视变换
https://blog.csdn.net/MrCharles/article/details/73739693
极坐标
https://zhuanlan.zhihu.com/p/26172668
等