利用OpenCV的功能可以方便快捷的将图片里面的文字纠正过来:
文章是参考国外帖子来写的:http://felix.abecassis.me/2011/09/opencv-detect-skew-angle/
这里有我的一些iOS, Mac代码和测试结果,可以方便的自动旋转文字方向,旋转图片。
图片有一些前提,或者处理缺陷:
1、图片没有外边框干扰
2、倒立或者竖着的图片,纠正后还是倒立,或者竖着的。
比如以下这2个图片,纠正后结果都不理想:
会旋转,但是纠正后会竖立,需要进一步判断。
计算的纠正角度为0,纠正失败。
方法一、利用线条横线计算角度,然后再纠正。
// 参考 Félix Abecassis - OpenCV - Detect skew angle
// http://felix.abecassis.me/2011/09/opencv-detect-skew-angle/
+ (double)cvMatImageSkewAngleUsingSHT:(cv::Mat)gray;
{
// Load in grayscale.
//cv::Mat src = cv::imread(filename, cv::IMREAD_GRAYSCALE);
if(gray.data == NULL){
fprintf(stderr, "error: src.data == NULL\n");
return 0.0f;
}
if(gray.channels() != 1){
fprintf(stderr, "error: src.channels() != 1\n");
return 0.0f;
}
cv::bitwise_not(gray, gray);
cv::Size size = gray.size();
// 需要的是黑色背景
std::vector lines;
cv::HoughLinesP(gray, lines, 1, CV_PI/180, 100, size.width / 2.f, 20);
cv::Mat disp_lines(size, CV_8UC1, cv::Scalar(0, 0, 0));
double angle = 0.;
unsigned nb_lines = lines.size();
for (unsigned i = 0; i < nb_lines; ++i)
{
cv::line(disp_lines, cv::Point(lines[i][0], lines[i][1]), cv::Point(lines[i][2], lines[i][3]), cv::Scalar(255, 0 ,0));
angle += atan2((double)lines[i][3] - lines[i][1], (double)lines[i][2] - lines[i][0]);
}
angle /= nb_lines; // mean angle, in radians.
angle = angle * 180 / CV_PI;
cv::imwrite("/Users/James/Desktop/skew-lines.bmp", disp_lines);
fprintf(stderr, "ImageSkewAngle result: %f\n", angle);
return angle;
}
方法二、利用文字最小区域获取矩形计算角度,然后再纠正。
// 参考 Félix Abecassis - OpenCV - Bounding Box & Skew Angle
// http://felix.abecassis.me/2011/10/opencv-bounding-box-skew-angle/
+ (double)cvMatImageSkewAngleWithImageBin:(cv::Mat)image_bin
{
// 输入的是黑底白字图片
cv::Mat image = image_bin.clone();
if(image.data == NULL){
fprintf(stderr, "error: src.data == NULL\n");
return 0.0f;
}
if(image.channels() != 1){
fprintf(stderr, "error: src.channels() != 1\n");
return 0.0f;
}
cv::imwrite("/Users/James/Desktop/skew-bin.bmp", image);
// Size(23, 3)); Size(5, 3));,使用Size(3, 3)最好
cv::Mat element = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));
// 最基本的形态学操作有二:腐蚀与膨胀(Erosion 与 Dilation)
cv::erode(image, image, element);
cv::imwrite("/Users/James/Desktop/skew-erode.bmp", image);
std::vector points;
cv::Mat_::iterator it = image.begin();
cv::Mat_::iterator end = image.end();
for (; it != end; ++it)
if (*it)
points.push_back(it.pos());
cv::RotatedRect box = cv::minAreaRect(cv::Mat(points));
double angle = box.angle;
if (angle < -45.)
angle += 90.;
if(1){
cv::Point2f vertices[4];
box.points(vertices);
for(int i = 0; i < 4; ++i)
cv::line(image, vertices[i], vertices[(i + 1) % 4], cv::Scalar(255, 0, 0), 1, CV_AA);
cv::imwrite("/Users/James/Desktop/skew-rect.bmp", image);
}
fprintf(stderr, "ImageSkewAngle result: %f\n", angle);
return angle;
}
+ (double)cvMatImageSkewAngleWithImageGray:(cv::Mat)image_gray
{
// 输入的是灰度图片
// Load in grayscale.
//cv::Mat img = cv::imread(filename, cv::IMREAD_GRAYSCALE);
cv::Mat image = image_gray.clone();
if(image.data == NULL){
fprintf(stderr, "error: src.data == NULL\n");
return 0.0f;
}
if(image.channels() != 1){
fprintf(stderr, "error: src.channels() != 1\n");
return 0.0f;
}
//中值滤波器是一种非线性滤波器,常用于消除图像中的椒盐噪声。与低通滤波不同的是,中值滤波有利于保留边缘的尖锐度,但它会洗去均匀介质区域中的纹理。
//cv::medianBlur(img, img, 3);
//cv::GaussianBlur(img, img, cv::Size(3, 3), 0);
//GaussianBlur(img, img, cvSize(11,11), 0);//change from median blur to gaussian for more accuracy of square detection
// Binarize
//cv::threshold(img, img, 225, 255, CV_THRESH_BINARY_INV);
cv::threshold(image, image, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY_INV); // 二值化效果好
return [RecognitionManager cvMatImageSkewAngleWithImageBin:image];
}
// 参考 Félix Abecassis - OpenCV - Rotation (Deskewing)
// http://felix.abecassis.me/2011/10/opencv-rotation-deskewing/
+ (cv::Mat)cvMatImageDeskew:(cv::Mat)src angle:(double)angle;
{
cv::Mat img = src.clone();
cv::imwrite("/Users/James/Desktop/deskew-src.bmp", img);
cv::Mat cropped;
cv::Mat rotated;
// Load in grayscale.
//cv::Mat img = cv::imread(filename, cv::IMREAD_GRAYSCALE);
if(img.data == NULL){
fprintf(stderr, "error: src.data == NULL");
return rotated;
}
if(img.channels() != 1){
fprintf(stderr, "error: src.channels() != 1");
return rotated;
}
std::vector points;
cv::Mat_::iterator it = img.begin();
cv::Mat_::iterator end = img.end();
for (; it != end; ++it)
if (*it)
points.push_back(it.pos());
cv::RotatedRect box = cv::minAreaRect(cv::Mat(points));
cv::Mat rot_mat = cv::getRotationMatrix2D(box.center, angle, 1);
// 旋转
cv::warpAffine(img, rotated, rot_mat, img.size(), cv::INTER_CUBIC); // 默认是黑色背景
//cv::warpAffine(img, rotated, rot_mat, img.size(), CV_INTER_CUBIC, cv::BORDER_CONSTANT, cvScalarAll(0)); // 黑色背景
//cv::warpAffine(img, rotated, rot_mat, img.size(), cv::INTER_CUBIC, cv::BORDER_CONSTANT, cvScalar(255, 255, 255)); // 白色背景
cv::imwrite("/Users/James/Desktop/deskew-rotated.bmp", rotated);
if(0){
// 切割
cv::Size box_size = box.size;
if (box.angle < -45.)
std::swap(box_size.width, box_size.height);
cv::getRectSubPix(rotated, box_size, box.center, cropped);
cv::imwrite("/Users/James/Desktop/deskew-cropped.bmp", cropped);
}
return rotated;
}
测试代码:
- (void)deskew
{
if(1){
cv::Mat gray_image = [RecognitionManager cvMatImageFromCGImageRef_Grayscale:[[imageView image] CGImage_FPLib]];
//double angle = [RecognitionManager cvMatImageSkewAngleWithImageGray:gray_image];
double angle = [RecognitionManager cvMatImageSkewAngleUsingSHT:gray_image];
cv::Mat img_deskew;
img_deskew = [RecognitionManager cvMatImageDeskew:gray_image angle:angle];
imageView.image = [NSImage imageWithCGImage_FPLib:[RecognitionManager CGImageRefFromCVMat:img_deskew]];
}
}
方法一、
方法二:
更多测试:
原图:
方法二结果:
结果是误旋转了:-(。
可以看出,方法一、对于边框有干扰,无法正确纠正。
方法二、要比方法一适应性强,但是也有边框干扰,都会有一些误操作。
其实还有一种方法就是利用findContours查找最大边框,然后利用图片裁剪函数纠正。
https://stackoverflow.com/questions/8667818/opencv-c-obj-c-detecting-a-sheet-of-paper-square-detection?noredirect=1&lq=1