用OpenCV实现Photoshop算法(一): 图像旋转

https://blog.csdn.net/c80486/article/details/51867128

对于一张照片,PS的一般处理步骤包括:

1, 旋转图片,校正位置。

2,剪切,调整大小,重新构图。

3,调整色阶、曲线,使图片曝光正确、对比适中。

4,调整对比度、饱和度

5,印章去掉不想要的东西,液化调整形体线条

6,对于人像图片,美肤、美白

7, 用色彩平衡、可选颜色等调整色调,形成照片调性

8,加一些光效

9,锐化


以后的一系列博文将采用OpenCV逐一实现Photoshop的算法和功能, 并用计算机视觉人工智能方式,尝试超越Photoshop一点点。


本系列博文基于OpenCV,  编程语言为C++.    由于OpenCV的跨平台性,代码可以在用于Windows, Linux, 作个接口后可用于Android,IOS.


一、图像旋转

OpenCV中, 用 warpAffine()  仿射变换函数即可以实现旋转。

例如,写一个 旋转函数 imageRotate1() 如下:

[cpp] view plain copy
  1. #include <opencv2/core.hpp>  
  2. #include <opencv2/imgproc.hpp>  
  3.   
  4. //src为原图像, dst为新图像, angle为旋转角度(正值为顺时针旋转,负值为逆时针旋转)  
  5. int imageRotate1(InputArray src, OutputArray dst, double angle)  
  6. {  
  7.     Mat input = src.getMat();  
  8.     if( input.empty() ) {  
  9.         return -1;  
  10.     }  
  11.   
  12.     //得到图像大小  
  13.     int width = input.cols;  
  14.     int height = input.rows;  
  15.   
  16.     //计算图像中心点  
  17.     Point2f center;  
  18.     center.x = width / 2.0;  
  19.     center.y = height / 2.0;  
  20.   
  21.     //获得旋转变换矩阵  
  22.     double scale = 1.0;  
  23.     Mat trans_mat = getRotationMatrix2D( center, -angle, scale );  
  24.   
  25.     //仿射变换  
  26.     warpAffine( input, dst, trans_mat, Size(width, height));  
  27.   
  28.     return 0;  
  29. }  


图像旋转 -17度 的结果
用OpenCV实现Photoshop算法(一): 图像旋转


在函数 imageRotate1()中,新图像沿用原图像大小。旋转后,图像的角部被切掉了。

这样显然不正确,需要调整图像尺寸。


调整方式一: 扩大图片,将原图片包含进去,计算示意图如下:

用OpenCV实现Photoshop算法(一): 图像旋转

新图片大小为: out_width = (width*cos(a)+height*sin(a);   out_height = height*cos(a)+width*sin(a))


修改原函数为 imageRotate2() :

[cpp] view plain copy
  1. //图像旋转: src为原图像, dst为新图像, angle为旋转角度  
  2. int imageRotate2(InputArray src, OutputArray dst, double angle)  
  3. {  
  4.     Mat input = src.getMat();  
  5.     if( input.empty() ) {  
  6.         return -1;  
  7.     }  
  8.   
  9.     //得到图像大小  
  10.     int width = input.cols;  
  11.     int height = input.rows;  
  12.   
  13.     //计算图像中心点  
  14.     Point2f center;  
  15.     center.x = width / 2.0;  
  16.     center.y = height / 2.0;  
  17.   
  18.     //获得旋转变换矩阵  
  19.     double scale = 1.0;  
  20.     Mat trans_mat = getRotationMatrix2D( center, -angle, scale );  
  21.   
  22.     //计算新图像大小  
  23.     double angle1 = angle  * CV_PI / 180. ;  
  24.     double a = sin(angle1) * scale;  
  25.     double b = cos(angle1) * scale;  
  26.     double out_width = height * fabs(a) + width * fabs(b);  
  27.     double out_height = width * fabs(a) + height * fabs(b);  
  28.   
  29.     //仿射变换  
  30.     warpAffine( input, dst, trans_mat, Size(out_width, out_height));  
  31.   
  32.     return 0;  
  33. }  

图像旋转 -17度 的结果

用OpenCV实现Photoshop算法(一): 图像旋转


还是不对,新图像变大了,但图像中心点不对,需要在旋转矩阵中加入平移,在一次变换中同时完成旋转和平移,将新图像的中心点移到正确位置。 

再次修改函数为: imageRotate3()

[cpp] view plain copy
  1. //图像旋转: src为原图像, dst为新图像, angle为旋转角度  
  2. int imageRotate3(InputArray src, OutputArray dst, double angle)  
  3. {  
  4.     Mat input = src.getMat();  
  5.     if( input.empty() ) {  
  6.         return -1;  
  7.     }  
  8.   
  9.     //得到图像大小  
  10.     int width = input.cols;  
  11.     int height = input.rows;  
  12.   
  13.     //计算图像中心点  
  14.     Point2f center;  
  15.     center.x = width / 2.0;  
  16.     center.y = height / 2.0;  
  17.   
  18.     //获得旋转变换矩阵  
  19.     double scale = 1.0;  
  20.     Mat trans_mat = getRotationMatrix2D( center, -angle, scale );  
  21.   
  22.     //计算新图像大小  
  23.     double angle1 = angle  * CV_PI / 180. ;  
  24.     double a = sin(angle1) * scale;  
  25.     double b = cos(angle1) * scale;  
  26.     double out_width = height * fabs(a) + width * fabs(b);  
  27.     double out_height = width * fabs(a) + height * fabs(b);  
  28.   
  29.     //在旋转变换矩阵中加入平移量  
  30.     trans_mat.at<double>(0, 2) += cvRound( (out_width - width) / 2 );  
  31.     trans_mat.at<double>(1, 2) += cvRound( (out_height - height) / 2);  
  32.   
  33.     //仿射变换  
  34.     warpAffine( input, dst, trans_mat, Size(out_width, out_height));  
  35.   
  36.     return 0;  
  37. }  

这一次正确了,新图像变大了,同时图像中心点移到了新的中心点,原图像全部能显示出来。

用OpenCV实现Photoshop算法(一): 图像旋转



在实际照片旋转中,我们经常采用另一种剪切形式的调整方式:图像旋转后,缩小图片,使图片各个边角均不出现黑边。 下图红框即为新图象大小,如下:

用OpenCV实现Photoshop算法(一): 图像旋转

这种调整方式下,新图像大小的计算稍为有点复杂,在网上也没有找到范例,只能自己计算了。

1,如上,旋转后的外边框大小为:    out_width =(width*cos(a)+height*sin(a);     out_height = height*cos(a)+width*sin(a))

2,  画几根辅助线,如下图:(注意右边图中的粉红三角形)

     其最长的边长 len =  width*cos(a)
      角a 即旋转角度
      由于外边框大小已知,则角b 可计算出来。
      求解 Y:    Y = len / ( 1 / tan( a ) + 1 / tan( b ) )
                          X =  Y * 1 /  tan( b )

     最后求得  红框的长、宽为:   new_width = out_width - 2 * X;     new_height = out_height - 2 * Y

用OpenCV实现Photoshop算法(一): 图像旋转


再次修改函数为: imageRotate4()

增加了一个参数: isClip ,    当isClip为true时,采取缩小图片的剪切方式,否则采取放大图片的方式。

[cpp] view plain copy
  1. //图像旋转: src为原图像, dst为新图像, angle为旋转角度, isClip表示是采取缩小图片的方式  
  2. int imageRotate4(InputArray src, OutputArray dst, double angle, bool isClip)  
  3. {  
  4.     Mat input = src.getMat();  
  5.     if( input.empty() ) {  
  6.         return -1;  
  7.     }  
  8.   
  9.     //得到图像大小  
  10.     int width = input.cols;  
  11.     int height = input.rows;  
  12.   
  13.     //计算图像中心点  
  14.     Point2f center;  
  15.     center.x = width / 2.0;  
  16.     center.y = height / 2.0;  
  17.   
  18.     //获得旋转变换矩阵  
  19.     double scale = 1.0;  
  20.     Mat trans_mat = getRotationMatrix2D( center, -angle, scale );  
  21.   
  22.     //计算新图像大小  
  23.     double angle1 = angle  * CV_PI / 180. ;  
  24.     double a = sin(angle1) * scale;  
  25.     double b = cos(angle1) * scale;  
  26.     double out_width = height * fabs(a) + width * fabs(b); //外边框长度  
  27.     double out_height = width * fabs(a) + height * fabs(b);//外边框高度  
  28.   
  29.     int new_width, new_height;  
  30.     if ( ! isClip ) {  
  31.         new_width = cvRound(out_width);  
  32.         new_height = cvRound(out_height);  
  33.     } else {  
  34.         //calculate width and height of clip rect  
  35.         double angle2 = fabs(atan(height * 1.0 / width)); //即角度 b  
  36.         double len = width * fabs(b);  
  37.         double Y = len / ( 1 / fabs(tan(angle1)) + 1 / fabs(tan(angle2)) );  
  38.         double X = Y * 1 / fabs(tan(angle2));  
  39.         new_width = cvRound(out_width - X * 2);  
  40.         new_height= cvRound(out_height - Y * 2);  
  41.     }  
  42.   
  43.     //在旋转变换矩阵中加入平移量  
  44.     trans_mat.at<double>(0, 2) += cvRound( (new_width - width) / 2 );  
  45.     trans_mat.at<double>(1, 2) += cvRound( (new_height - height) / 2);  
  46.   
  47.     //仿射变换  
  48.     warpAffine( input, dst, trans_mat, Size(new_width, new_height));  
  49.   
  50.     return 0;  
  51. }  

以下是 isClip为true,  旋转角度为 10 的结果,可见图片旋转了、缩小了,没有黑边

用OpenCV实现Photoshop算法(一): 图像旋转


由于不注意,人们拍照时经常拍歪了,一般歪得也不多,但照片就不好看了。

因此,有这么一个问题:  能否智能判别图像是否拍歪了,如果歪了,则自动计算出要旋转摆正的角度。从而使得人们一拍照,就自动拍正。

(PS:这个功能是Photoshop没有的,如果能实现,算不算超越Photoshop一点点呢?)


解决思路是这样的:

    1, 图像一般有一个或两条长直线(通常这个可能是地平线、建筑物等),且倾斜角度不大

    2, 利用 OpenCV图像识别能力,识别出图中有哪些直线。

    3, 分析这些直线,  如果长度足够长、且位置相对居中,选取最长的两条直线,测算摆正它所需的角度,做为返回值。


事实上,人工纠正图片的Photoshop操作方式也是这样的:我们在图中人眼找一个基准线,用“度量工具”画一条线,再点菜单“图象/ 旋转画布/ 任意角度", 则Photoshop将计算出需要旋转的角度。


尝试写了一个函数:   detectRotation(),  用于自动检测摆正图像的所需的旋转角度, 如下: 

[cpp] view plain copy
  1. /** 
  2.  * 智能检测图像倾斜度 
  3.  * 返回值:返回0表示无检测结果,返回非0表示摆正图象需要旋转的角度(-10至10度) 
  4.  */  
  5. double detectRotation(InputArray src)  
  6. {  
  7.     double max_angle = 6; //可旋转的最大角度  
  8.   
  9.     Mat in = src.getMat();  
  10.     if( in.empty() ) return 0;  
  11.   
  12.     Mat input;  
  13.   
  14.     //转为灰度图  
  15.     if ( in.type() == CV_8UC1 )  
  16.         input = in;  
  17.     else if ( in.type() == CV_8UC3 )  
  18.         cvtColor(in, input, CV_BGR2GRAY);  
  19.     else if ( in.type() == CV_8UC3 )  
  20.         cvtColor(in, input, CV_BGRA2GRAY);  
  21.     else  
  22.         return 0;  
  23.   
  24.     Mat dst, cdst;  
  25.   
  26.     //执行Canny边缘检测(检测结果为dst, 为黑白图)  
  27.     double threshold1 = 90;  
  28.     Canny(src, dst, threshold1, threshold1 * 3, 3);  
  29.   
  30.     //将Canny边缘检测结果转化为灰度图像(cdst)  
  31.     cvtColor(dst, cdst, CV_GRAY2BGR);  
  32.   
  33.     //执行霍夫线变换,检测直线  
  34.     vector<Vec4i> lines; //存放检测结果的vector  
  35.     double minLineLength = std::min(dst.cols, dst.rows) * 0.25; //最短线长度  
  36.     double maxLineGap = std::min(dst.cols, dst.rows) * 0.03 ; //最小线间距  
  37.     int threshold = 90;  
  38.     HoughLinesP(dst, lines, 1, CV_PI / 180, threshold, minLineLength, maxLineGap );  
  39.   
  40.     //分析所需变量  
  41.     int x1, y1, x2 , y2; //直线的两个端点  
  42.     int x, y;  //直线的中点  
  43.     double angle, rotate_angle; //直线的角度,摆正直线需要旋转的角度  
  44.     double line_length; //直线长度  
  45.     double position_weighted; //直线的位置权重:靠图像*的线权重为1, 越靠边的线权重越小  
  46.     double main_lens[2]; //用于存放最长的二条直线长度的数组 (这两条直线即是主线条)  
  47.     double main_angles[2];//用于存放最长的二条直线的摆正需要旋转的角度  
  48.     main_lens[0] = main_lens[1] = 0;  
  49.     main_angles[0] = main_angles[1] = 0;  
  50.   
  51.     //逐个分析各条直线,判断哪个是主线条  
  52.     forsize_t i = 0; i < lines.size(); i++ ) {  
  53.         //取得直线的两个端点座标  
  54.         x1 = lines[i][0]; y1 = lines[i][1]; x2 = lines[i][2]; y2 = lines[i][3];  
  55.         x = (x1 + x2 ) / 2; y = (y1 + y2) / 2;  
  56.         //计算直线的角度  
  57.         angle = (x1 == x2) ? 90 : ( atan ( (y1 - y2) * 1.0 / (x2 - x1) ) ) / CV_PI * 180;  
  58.         //摆正直线需要旋转的角度. 如果超出可旋转的最大角度,则忽略这个线。  
  59.         if ( fabs(angle - 0) <= max_angle ) {  
  60.             rotate_angle = angle - 0;  
  61.         } else if ( fabs(angle - 90) <= max_angle ) {  
  62.             rotate_angle = angle - 90;  
  63.         } else {  
  64.             continue;  
  65.         }  
  66.   
  67.         //计算线的长度  
  68.         line_length = sqrt( (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)  );  
  69.         //计算直线的位置权重:靠图像*的线权重为1, 越靠边的线权重越小  
  70.         position_weighted = 1;  
  71.         if ( x < dst.cols / 4 || x > dst.cols * 3 / 4  ) position_weighted *= 0.8;  
  72.         if ( x < dst.cols / 6 || x > dst.cols * 5 / 6  ) position_weighted *= 0.5;  
  73.         if ( x < dst.cols / 8 || x > dst.cols * 7 / 8  ) position_weighted *= 0.5;  
  74.         if ( y < dst.rows / 4 || y > dst.rows * 3 / 4  ) position_weighted *= 0.8;  
  75.         if ( y < dst.rows / 6 || y > dst.rows * 5 / 6  ) position_weighted *= 0.5;  
  76.         if ( y < dst.rows / 8 || y > dst.rows * 7 / 8  ) position_weighted *= 0.5;  
  77.   
  78.         //如果 直线长度 * 位置权重 < 最小长度, 则这条线无效  
  79.         line_length = line_length * position_weighted;  
  80.         if ( line_length < minLineLength ) continue;  
  81.   
  82.   
  83.   
  84.         //如果长度为前两名,则存入数据  
  85.         if ( line_length > main_lens[1] )  {  
  86.             if (line_length > main_lens[0]) {  
  87.                  main_lens[1] = main_lens[0];  
  88.                  main_lens[0] = line_length;  
  89.                  main_angles[1] = main_angles[0];  
  90.                  main_angles[0] = rotate_angle;  
  91.                  //如果定义了 SHOW_LINE, 则将该线条画出来  
  92.                  #ifdef SHOW_LINE  
  93.                  line( cdst, Point(x1, y1), Point(x2, y2), Scalar(0,0,255), 3, CV_AA);  
  94.                  #endif  
  95.             } else {  
  96.                 main_lens[1] = line_length;  
  97.                 main_angles[1] = rotate_angle;  
  98.             }  
  99.         }  
  100.     }  
  101.   
  102.     //如果定义了 SHOW_LINE, 则在source_window中显示cdst  
  103.        #ifdef SHOW_LINE  
  104.     imshow(source_window, cdst);  
  105.     #endif  
  106.   
  107.     //最后,分析最长的二条直线,得出结果  
  108.     if ( main_lens[0] > 0 ) {  
  109.         //如果最长的线 与 次长的线 两者长度相近,则返回两者需要旋转的角度的平均值  
  110.         if (main_lens[1] > 0 && (main_lens[0] - main_lens[1] / main_lens[0] < 0.2 )) {  
  111.             return (main_angles[0] + main_angles[1] ) / 2;  
  112.         } else {  
  113.             return main_angles[0];   //否则,返回最长的线需要旋转的角度  
  114.         }  
  115.     } else {  
  116.         return 0;  
  117.     }  
  118. }  


使用detectRotation()函数自动测试角度,并显示出主要线条,运行结果:

用OpenCV实现Photoshop算法(一): 图像旋转

恩,有那么一点意思, 找出了几个主线条,得出旋转 -5 度,则可以摆正图片。


当然,这个 detectRotation()函数还不是很智能,可用性还有待改进。



最后, 把本文所有代码和主程序贴上来(有点长,不过方便复制)。配置好OpenCV开发环境,把代码复制下来,就可以调试了。

代码中需要说明的是:  由于opencv的滚动条只能显示正值。 本例中rotation 的 滚动条,值为100时表示旋转角度为0。 如果小于100, 表示旋转角度为负。

[cpp] view plain copy
  1. #include <iostream>  
  2. #include "opencv2/core.hpp"  
  3. #include "opencv2/imgproc.hpp"  
  4. #include "opencv2/highgui.hpp"  
  5. #include <cmath>  
  6.   
  7. using namespace std;  
  8. using namespace cv;  
  9.   
  10.   
  11. #define SHOW_LINE  
  12.   
  13. #define BASE 100  
  14.   
  15. static string source_window = "source";  
  16. static string window_name = "image rotate";  
  17. static Mat src;  
  18. static int rotateDegree = 0 + BASE;  
  19. static int clip = 0;  
  20.   
  21. //图像旋转: src为原图像, dst为新图像, angle为旋转角度(正值为顺时针旋转,负值为逆时针旋转)  
  22. int imageRotate1(InputArray src, OutputArray dst, double angle)  
  23. {  
  24.     Mat input = src.getMat();  
  25.     if( input.empty() ) {  
  26.         return -1;  
  27.     }  
  28.   
  29.     //得到图像大小  
  30.     int width = input.cols;  
  31.     int height = input.rows;  
  32.   
  33.     //计算图像中心点  
  34.     Point2f center;  
  35.     center.x = width / 2.0;  
  36.     center.y = height / 2.0;  
  37.   
  38.     //获得旋转变换矩阵  
  39.     double scale = 1.0;  
  40.     Mat trans_mat = getRotationMatrix2D( center, -angle, scale );  
  41.   
  42.     //仿射变换  
  43.     warpAffine( input, dst, trans_mat, Size(width, height));  
  44.   
  45.     return 0;  
  46. }  
  47.   
  48. //图像旋转: src为原图像, dst为新图像, angle为旋转角度  
  49. int imageRotate2(InputArray src, OutputArray dst, double angle)  
  50. {  
  51.     Mat input = src.getMat();  
  52.     if( input.empty() ) {  
  53.         return -1;  
  54.     }  
  55.   
  56.     //得到图像大小  
  57.     int width = input.cols;  
  58.     int height = input.rows;  
  59.   
  60.     //计算图像中心点  
  61.     Point2f center;  
  62.     center.x = width / 2.0;  
  63.     center.y = height / 2.0;  
  64.   
  65.     //获得旋转变换矩阵  
  66.     double scale = 1.0;  
  67.     Mat trans_mat = getRotationMatrix2D( center, -angle, scale );  
  68.   
  69.     //计算新图像大小  
  70.     double angle1 = angle  * CV_PI / 180. ;  
  71.     double a = sin(angle1) * scale;  
  72.     double b = cos(angle1) * scale;  
  73.     double out_width = height * fabs(a) + width * fabs(b);  
  74.     double out_height = width * fabs(a) + height * fabs(b);  
  75.   
  76.     //仿射变换  
  77.     warpAffine( input, dst, trans_mat, Size(out_width, out_height));  
  78.   
  79.     return 0;  
  80. }  
  81.   
  82. //图像旋转: src为原图像, dst为新图像, angle为旋转角度  
  83. int imageRotate3(InputArray src, OutputArray dst, double angle)  
  84. {  
  85.     Mat input = src.getMat();  
  86.     if( input.empty() ) {  
  87.         return -1;  
  88.     }  
  89.   
  90.     //得到图像大小  
  91.     int width = input.cols;  
  92.     int height = input.rows;  
  93.   
  94.     //计算图像中心点  
  95.     Point2f center;  
  96.     center.x = width / 2.0;  
  97.     center.y = height / 2.0;  
  98.   
  99.     //获得旋转变换矩阵  
  100.     double scale = 1.0;  
  101.     Mat trans_mat = getRotationMatrix2D( center, -angle, scale );  
  102.   
  103.     //计算新图像大小  
  104.     double angle1 = angle  * CV_PI / 180. ;  
  105.     double a = sin(angle1) * scale;  
  106.     double b = cos(angle1) * scale;  
  107.     double out_width = height * fabs(a) + width * fabs(b);  
  108.     double out_height = width * fabs(a) + height * fabs(b);  
  109.   
  110.     //在旋转变换矩阵中加入平移量  
  111.     trans_mat.at<double>(0, 2) += cvRound( (out_width - width) / 2 );  
  112.     trans_mat.at<double>(1, 2) += cvRound( (out_height - height) / 2);  
  113.   
  114.     //仿射变换  
  115.     warpAffine( input, dst, trans_mat, Size(out_width, out_height));  
  116.   
  117.     return 0;  
  118. }  
  119.   
  120.   
  121. //图像旋转: src为原图像, dst为新图像, angle为旋转角度, isClip表示是采取缩小图片的方式  
  122. int imageRotate4(InputArray src, OutputArray dst, double angle, bool isClip)  
  123. {  
  124.     Mat input = src.getMat();  
  125.     if( input.empty() ) {  
  126.         return -1;  
  127.     }  
  128.   
  129.     //得到图像大小  
  130.     int width = input.cols;  
  131.     int height = input.rows;  
  132.   
  133.     //计算图像中心点  
  134.     Point2f center;  
  135.     center.x = width / 2.0;  
  136.     center.y = height / 2.0;  
  137.   
  138.     //获得旋转变换矩阵  
  139.     double scale = 1.0;  
  140.     Mat trans_mat = getRotationMatrix2D( center, -angle, scale );  
  141.   
  142.     //计算新图像大小  
  143.     double angle1 = angle  * CV_PI / 180. ;  
  144.     double a = sin(angle1) * scale;  
  145.     double b = cos(angle1) * scale;  
  146.     double out_width = height * fabs(a) + width * fabs(b); //外边框长度  
  147.     double out_height = width * fabs(a) + height * fabs(b);//外边框高度  
  148.   
  149.     int new_width, new_height;  
  150.     if ( ! isClip ) {  
  151.         new_width = cvRound(out_width);  
  152.         new_height = cvRound(out_height);  
  153.     } else {  
  154.         //calculate width and height of clip rect  
  155.         double angle2 = fabs(atan(height * 1.0 / width)); //即角度 b  
  156.         double len = width * fabs(b);  
  157.         double Y = len / ( 1 / fabs(tan(angle1)) + 1 / fabs(tan(angle2)) );  
  158.         double X = Y * 1 / fabs(tan(angle2));  
  159.         new_width = cvRound(out_width - X * 2);  
  160.         new_height= cvRound(out_height - Y * 2);  
  161.     }  
  162.   
  163.     //在旋转变换矩阵中加入平移量  
  164.     trans_mat.at<double>(0, 2) += cvRound( (new_width - width) / 2 );  
  165.     trans_mat.at<double>(1, 2) += cvRound( (new_height - height) / 2);  
  166.   
  167.     //仿射变换  
  168.     warpAffine( input, dst, trans_mat, Size(new_width, new_height));  
  169.   
  170.     return 0;  
  171. }  
  172.   
  173. /** 
  174.  * 检测图像倾斜度 
  175.  * 返回值:返回0表示无检测结果,返回非0表示摆正图象需要旋转的角度(-10至10度) 
  176.  */  
  177. double detectRotation(InputArray src)  
  178. {  
  179.     double max_angle = 6; //可旋转的最大角度  
  180.   
  181.     Mat in = src.getMat();  
  182.     if( in.empty() ) return 0;  
  183.   
  184.     Mat input;  
  185.   
  186.     //转为灰度图  
  187.     if ( in.type() == CV_8UC1 )  
  188.         input = in;  
  189.     else if ( in.type() == CV_8UC3 )  
  190.         cvtColor(in, input, CV_BGR2GRAY);  
  191.     else if ( in.type() == CV_8UC3 )  
  192.         cvtColor(in, input, CV_BGRA2GRAY);  
  193.     else  
  194.         return 0;  
  195.   
  196.     Mat dst, cdst;  
  197.   
  198.     //执行Canny边缘检测(检测结果为dst, 为黑白图)  
  199.     double threshold1 = 90;  
  200.     Canny(src, dst, threshold1, threshold1 * 3, 3);  
  201.   
  202.     //将Canny边缘检测结果转化为灰度图像(cdst)  
  203.     cvtColor(dst, cdst, CV_GRAY2BGR);  
  204.   
  205.     //执行霍夫线变换,检测直线  
  206.     vector<Vec4i> lines; //存放检测结果的vector  
  207.     double minLineLength = std::min(dst.cols, dst.rows) * 0.25; //最短线长度  
  208.     double maxLineGap = std::min(dst.cols, dst.rows) * 0.03 ; //最小线间距  
  209.     int threshold = 90;  
  210.     HoughLinesP(dst, lines, 1, CV_PI / 180, threshold, minLineLength, maxLineGap );  
  211.   
  212.     //分析所需变量  
  213.     int x1, y1, x2 , y2; //直线的两个端点  
  214.     int x, y;  //直线的中点  
  215.     double angle, rotate_angle; //直线的角度,摆正直线需要旋转的角度  
  216.     double line_length; //直线长度  
  217.     double position_weighted; //直线的位置权重:靠图像*的线权重为1, 越靠边的线权重越小  
  218.     double main_lens[2]; //用于存放最长的二条直线长度的数组 (这两条直线即是主线条)  
  219.     double main_angles[2];//用于存放最长的二条直线的摆正需要旋转的角度  
  220.     main_lens[0] = main_lens[1] = 0;  
  221.     main_angles[0] = main_angles[1] = 0;  
  222.   
  223.     //逐个分析各条直线,判断哪个是主线条  
  224.     forsize_t i = 0; i < lines.size(); i++ ) {  
  225.         //取得直线的两个端点座标  
  226.         x1 = lines[i][0]; y1 = lines[i][1]; x2 = lines[i][2]; y2 = lines[i][3];  
  227.         x = (x1 + x2 ) / 2; y = (y1 + y2) / 2;  
  228.         //计算直线的角度  
  229.         angle = (x1 == x2) ? 90 : ( atan ( (y1 - y2) * 1.0 / (x2 - x1) ) ) / CV_PI * 180;  
  230.         //摆正直线需要旋转的角度. 如果超出可旋转的最大角度,则忽略这个线。  
  231.         if ( fabs(angle - 0) <= max_angle ) {  
  232.             rotate_angle = angle - 0;  
  233.         } else if ( fabs(angle - 90) <= max_angle ) {  
  234.             rotate_angle = angle - 90;  
  235.         } else {  
  236.             continue;  
  237.         }  
  238.   
  239.         //计算线的长度  
  240.         line_length = sqrt( (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)  );  
  241.         //计算直线的位置权重:靠图像*的线权重为1, 越靠边的线权重越小  
  242.         position_weighted = 1;  
  243.         if ( x < dst.cols / 4 || x > dst.cols * 3 / 4  ) position_weighted *= 0.8;  
  244.         if ( x < dst.cols / 6 || x > dst.cols * 5 / 6  ) position_weighted *= 0.5;  
  245.         if ( x < dst.cols / 8 || x > dst.cols * 7 / 8  ) position_weighted *= 0.5;  
  246.         if ( y < dst.rows / 4 || y > dst.rows * 3 / 4  ) position_weighted *= 0.8;  
  247.         if ( y < dst.rows / 6 || y > dst.rows * 5 / 6  ) position_weighted *= 0.5;  
  248.         if ( y < dst.rows / 8 || y > dst.rows * 7 / 8  ) position_weighted *= 0.5;  
  249.   
  250.         //如果 直线长度 * 位置权重 < 最小长度, 则这条线无效  
  251.         line_length = line_length * position_weighted;  
  252.         if ( line_length < minLineLength ) continue;  
  253.   
  254.   
  255.   
  256.         //如果长度为前两名,则存入数据  
  257.         if ( line_length > main_lens[1] )  {  
  258.             if (line_length > main_lens[0]) {  
  259.                  main_lens[1] = main_lens[0];  
  260.                  main_lens[0] = line_length;  
  261.                  main_angles[1] = main_angles[0];  
  262.                  main_angles[0] = rotate_angle;  
  263.                  //如果定义了 SHOW_LINE, 则将该线条画出来  
  264.                  #ifdef SHOW_LINE  
  265.                  line( cdst, Point(x1, y1), Point(x2, y2), Scalar(0,0,255), 3, CV_AA);  
  266.                  #endif  
  267.             } else {  
  268.                 main_lens[1] = line_length;  
  269.                 main_angles[1] = rotate_angle;  
  270.             }  
  271.         }  
  272.     }  
  273.   
  274.     //如果定义了 SHOW_LINE, 则在source_window中显示cdst  
  275.     #ifdef SHOW_LINE  
  276.     imshow(source_window, cdst);  
  277.     #endif  
  278.   
  279.     //最后,分析最长的二条直线,得出结果  
  280.     if ( main_lens[0] > 0 ) {  
  281.         //如果最长的线 与 次长的线 两者长度相近,则返回两者需要旋转的角度的平均值  
  282.         if (main_lens[1] > 0 && (main_lens[0] - main_lens[1] / main_lens[0] < 0.2 )) {  
  283.             return (main_angles[0] + main_angles[1] ) / 2;  
  284.         } else {  
  285.             return main_angles[0];   //否则,返回最长的线需要旋转的角度  
  286.         }  
  287.     } else {  
  288.         return 0;  
  289.     }  
  290. }  
  291.   
  292.   
  293. static void callbackAdjust(int , void *)  
  294. {  
  295.     Mat dst;  
  296.   
  297.     //imageRotate1(src, dst, rotateDegree - BASE);  
  298.     //imageRotate2(src, dst, rotateDegree - BASE);  
  299.     //imageRotate3(src, dst, rotateDegree - BASE);  
  300.   
  301.     bool isClip = ( clip == 1 );  
  302.     imageRotate4(src, dst, rotateDegree - BASE,  isClip );  
  303.   
  304.     imshow(window_name, dst);  
  305. }  
  306.   
  307.   
  308. int main()  
  309. {  
  310.     src = imread("building.jpg");  
  311.   
  312.     if ( !src.data ) {  
  313.         cout << "error read image" << endl;  
  314.         return -1;  
  315.     }  
  316.   
  317.     namedWindow(source_window);  
  318.     imshow(source_window, src);  
  319.   
  320.     namedWindow(window_name);  
  321.     createTrackbar("rotate", window_name, &rotateDegree, BASE * 2, callbackAdjust);  
  322.     createTrackbar("clip", window_name, &clip, 1, callbackAdjust);  
  323.   
  324.     //自动检测旋转角度  
  325.     double angle = detectRotation(src);  
  326.     if ( angle != 0 ) {  
  327.         rotateDegree = angle + BASE;  
  328.         setTrackbarPos("rotate", window_name, rotateDegree);  
  329.     }  
  330.   
  331.     callbackAdjust(0, 0);  
  332.   
  333.     waitKey();  
  334.   
  335.         return 0;  
  336.   
  337. }