盘算机毕设分享 在iOS上使用OpenCV实现图片中的文字框选 ...

打印 上一主题 下一主题

主题 812|帖子 812|积分 2436


0 项目说明

在iOS上使用OpenCV实现图片中的文字框选
提示:得当用于课程计划或毕业计划,工作量达标,源码开放

1 准备工作

首先,必要去OpenCV官网下载iOS的framework,下载好后拖入新建的工程中即可,由于OpenCV库是使用C++编写,所以swift无法直接使用,必要使用OC做桥接,必要使用swift的同学可以看下这篇文章Using OpenCV in an iOS app。
2 实行流程

根据OpenCV入门笔记(七) 文字区域的提取中提供的思路,我实现了OC版本的代码,通过测试,清楚的文字截图识别没有标题,但是在复杂的拍照场景中几乎无法识别任何内容,例如下图

这张是相机拍摄的屏幕上的文字,有清楚的竖纹及屏幕反光,在该算法下,终极的框选区域是整个图片,无法识别文字区域,说明这个处理流程还是不完善的,我们先来看一下他的处理过程

  • 将图片转为灰度图
  • 形态学变换的预处理,得到可以查找矩形的图片
  • 查找和筛选文字区域
  • 用绿线画出这些找到的轮廓
根据前面得到的识别结果,我们大抵可以猜测标题出在了第二步,由于竖纹影响将全部文字区域连城一片,导致整图被框选。那么在第二步中都做了哪些操作呢?

实际上上面的流程一共做了4步操作,二值化->膨胀->腐蚀->再膨胀,这个流程对于正常的白底文本截图的识别没有标题,一但图片中出现了噪点,噪点在第一次膨胀的之后被放大,对整个图像产生不可逆的污染,我们先来看一下二值化后的图像

文字还是很清楚的,但是竖纹一样明显,接着第二步膨胀,看下会怎样

一片白,不消往下看了吧。
既然如此,就必要我们修改一下在第二步的处理流程了,在反转图像(由优劣变为白黑)之前,必要对图像举行降噪处理,因为OpenCV是对亮点举行操作,在优劣图像中降噪更轻易处理(去除杂乱黑点),降噪使用的方法仍旧是上面的膨胀和腐蚀法
  1. //第一次二值化,转为黑白图片
  2. cv::Mat binary;                              
  3. cv::adaptiveThreshold(gray,binary,255,cv::ADAPTIVE_THRESH_GAUSSIAN_C,cv::THRESH_BINARY,31,10);
  4. //在第二次二值化之前 为了去除噪点 做了两次膨胀腐蚀
  5. //膨胀一次
  6. cv::Mat dilateelement = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(4,2));
  7. cv::Mat dilate1;
  8. dilate(binary, dilate1, dilateelement);
  9.    
  10. //轻度腐蚀一次,去除噪点
  11. cv::Mat element3 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(4,4));
  12. cv::Mat erode11;
  13. erode(dilate1, erode11, element3);
  14.    
  15. //第二次膨胀
  16. cv::Mat dilateelement12 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5,1));
  17. cv::Mat dilate12;
  18. dilate(erode11, dilate12, dilateelement12);
  19. //轻度腐蚀一次,去除噪点
  20. cv::Mat element12 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5,1));
  21. cv::Mat erode12;
  22. erode(dilate12, erode12, element12);
复制代码
看一下经过两次降噪之后的图像是怎么样的

竖纹基本上不见了,仍旧还有一部分黑点,但是已经不影响后面的识别了,这里降噪只能适度,过分处理大概会使文字部分丢失。

做完二值化反转之后是上面这个样子的,接下来再对图片做膨胀->腐蚀->膨胀处理
  1. //二值化 第二次二值化将黑白图像反转 文字变亮
  2. cv::Mat binary2;  
  3. cv::adaptiveThreshold(erode12,binary2,255,cv::ADAPTIVE_THRESH_GAUSSIAN_C,cv::THRESH_BINARY_INV,17,10);
  4. //横向膨胀拉伸 文字连片形成亮条
  5. cv::Mat dilateelement21 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(60,1));
  6. cv::Mat dilate21;
  7. dilate(binary2, dilate21, dilateelement21);
  8. //腐蚀一次,去掉细节,表格线等。这里去掉的是竖直的线
  9. cv::Mat erodeelement21 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(30,1));
  10. cv::Mat erode21;
  11. erode(dilate21, erode21, erodeelement21);
  12. //再次膨胀,让轮廓明显一些
  13. cv::Mat dilateelement22 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5,1));
  14. cv::Mat dilate22;
  15. dilate(erode21, dilate22, dilateelement22);
复制代码
处理的结果图如下:

终极的框选效果

当然调试过程中不止用了这一张图片,毕竟结果要有一定的普适性,下面是其他几种情况下的识别结果



好了,下面贴一下整个过程的源码
  1. + (UIImage *)detect:(UIImage *) image {
  2.    
  3.     cv::Mat img;
  4.     img = [self cvMatFromUIImage:image];
  5.    
  6.     //1.转化成灰度图
  7.     cv::Mat gray;
  8.     cvtColor(bigImg, gray, cv::COLOR_BGR2GRAY);
  9.    
  10.     //2.形态学变换的预处理,得到可以查找矩形的轮廓
  11.     cv::Mat dilation = [self preprocess:gray];
  12.    
  13.     //3.查找和筛选文字区域
  14.     std::vector<cv::RotatedRect> rects = [self findTextRegion:dilation];
  15.    
  16.     //4.用线画出这些找到的轮廓
  17.     for (int i = 0; i < rects.size(); i++) {
  18.         cv::Point2f P[4];
  19.         cv::RotatedRect rect = rects[i];
  20.         rect.points(P);
  21.         for (int j = 0; j <= 3; j++) {
  22.             cv::line(bigImg, P[j], P[(j + 1) % 4], cv::Scalar(0,0,255),2);
  23.         }
  24.     }
  25.    
  26.     return [self UIImageFromCVMat:bigImg];
  27. }
  28. + (cv::Mat) preprocess:(cv::Mat)gray {
  29.    
  30.     //第一次二值化,转为黑白图片
  31.     cv::Mat binary;                                   
  32.     cv::adaptiveThreshold(gray, binary, 255,cv::ADAPTIVE_THRESH_GAUSSIAN_C, cv::THRESH_BINARY, 31, 10);
  33.    
  34.     //在第二次二值化之前 为了去除噪点 做了两次膨胀腐蚀,OpenCV是对亮点进行操作,在黑白图像中降噪更容易处理(去除杂乱黑点)
  35.    
  36.     //膨胀一次
  37.     cv::Mat dilateelement = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(4,2));
  38.     cv::Mat dilate1;
  39.     dilate(binary, dilate1, dilateelement);
  40.    
  41.     //轻度腐蚀一次,去除噪点
  42.     cv::Mat element3 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(4,4));
  43.     cv::Mat erode11;
  44.     erode(dilate1, erode11, element3);
  45.    
  46.     //第二次膨胀
  47.     cv::Mat dilateelement12 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5,1));
  48.     cv::Mat dilate12;
  49.     dilate(erode11, dilate12, dilateelement12);
  50.    
  51.     //轻度腐蚀一次,去除噪点
  52.     cv::Mat element12 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5,1));
  53.     cv::Mat erode12;
  54.     erode(dilate12, erode12, element12);
  55.    
  56.     //
  57.     //二值化 第二次二值化将黑白图像反转 文字变亮
  58.     cv::Mat binary2;
  59.     cv::adaptiveThreshold(erode12, binary2, 255, cv::ADAPTIVE_THRESH_GAUSSIAN_C, cv::THRESH_BINARY_INV, 17, 10);
  60.    
  61.     //横向膨胀拉伸 文字连片形成亮条
  62.     cv::Mat dilateelement21 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(60,1));
  63.     cv::Mat dilate21;
  64.     dilate(binary2, dilate21, dilateelement21);
  65.     //腐蚀一次,去掉细节,表格线等。这里去掉的是竖直的线
  66.     cv::Mat erodeelement21 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(30,1));
  67.     cv::Mat erode21;
  68.     erode(dilate21, erode21, erodeelement21);
  69.     //再次膨胀,让轮廓明显一些
  70.     cv::Mat dilateelement22 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5,1));
  71.     cv::Mat dilate22;
  72.     dilate(erode21, dilate22, dilateelement22);
  73.     return dilate22;
  74. }
  75. + (std::vector<cv::RotatedRect>) findTextRegion:(cv::Mat) img {
  76.    
  77.     std::vector<cv::RotatedRect> rects;
  78.     std::vector<int> heights;
  79.     //1.查找轮廓
  80.     std::vector<std::vector<cv::Point> > contours;
  81.     std::vector<cv::Vec4i> hierarchy;
  82.     cv::Mat m = img.clone();
  83.     cv::findContours(img,contours,hierarchy,
  84.                      cv::RETR_EXTERNAL,cv::CHAIN_APPROX_SIMPLE,cv::Point(0,0));
  85.     //2.筛选那些面积小的
  86.     for (int i = 0; i < contours.size(); i++) {
  87.         //计算当前轮廓的面积
  88.         double area = cv::contourArea(contours[i]);
  89.         //面积小于1000的全部筛选掉
  90.         if (area < 1000)
  91.             continue;
  92.         //轮廓近似,作用较小,approxPolyDP函数有待研究
  93.         double epsilon = 0.001*arcLength(contours[i], true);
  94.         cv::Mat approx;
  95.         approxPolyDP(contours[i], approx, epsilon, true);
  96.         
  97.         //找到最小矩形,该矩形可能有方向
  98.         cv::RotatedRect rect = minAreaRect(contours[i]);
  99.         
  100.         //计算高和宽
  101.         int m_width = rect.boundingRect().width;
  102.         int m_height = rect.boundingRect().height;
  103.         
  104.         //筛选那些太细的矩形,留下扁的
  105.         if (m_height > m_width * 1.2)
  106.             continue;
  107.         //过滤很扁的
  108.         if (m_height < 20)
  109.             continue;
  110.         heights.push_back(m_height);
  111.         //符合条件的rect添加到rects集合中
  112.         rects.push_back(rect);
  113.     }
  114.    
  115.     return rects;
  116. }
复制代码
这里还有几个cv::Mat 与 UIImage相互转换的方法一并提供
  1. //从UIImage对象转换为4通道的Mat,即是原图的Mat
  2. + (cv::Mat)cvMatFromUIImage:(UIImage *)image
  3. {
  4.     CGColorSpaceRef colorSpace = CGImageGetColorSpace(image.CGImage);
  5.     CGFloat cols = image.size.width;
  6.     CGFloat rows = image.size.height;
  7.    
  8.     cv::Mat cvMat(rows, cols, CV_8UC4); // 8 bits per component, 4 channels (color channels + alpha)
  9.    
  10.     CGContextRef contextRef = CGBitmapContextCreate(cvMat.data,
  11.                                                     cols,
  12.                                                     rows,
  13.                                                     8,
  14.                                                     cvMat.step[0],
  15.                                                     colorSpace,
  16.                                                     kCGImageAlphaNoneSkipLast |
  17.                                                     kCGBitmapByteOrderDefault);
  18.    
  19.     CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows), image.CGImage);
  20.     CGContextRelease(contextRef);
  21.    
  22.     return cvMat;
  23. }
  24. //从UIImage转换单通道的Mat,即灰度值
  25. + (cv::Mat)cvMatGrayFromUIImage:(UIImage *)image
  26. {
  27.     CGColorSpaceRef colorSpace = CGImageGetColorSpace(image.CGImage);
  28.     CGFloat cols = image.size.width;
  29.     CGFloat rows = image.size.height;
  30.    
  31.     cv::Mat cvMat(rows, cols, CV_8UC1); // 8 bits per component, 1 channels
  32.    
  33.     CGContextRef contextRef = CGBitmapContextCreate(cvMat.data,
  34.                                                     cols,
  35.                                                     rows,
  36.                                                     8,
  37.                                                     cvMat.step[0],
  38.                                                     colorSpace,
  39.                                                     kCGImageAlphaNoneSkipLast |
  40.                                                     kCGBitmapByteOrderDefault);
  41.    
  42.     CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows), image.CGImage);
  43.     CGContextRelease(contextRef);
  44.    
  45.     return cvMat;
  46. }
  47. //将Mat转换为UIImage
  48. + (UIImage *)UIImageFromCVMat:(cv::Mat)cvMat
  49. {
  50.     NSData *data = [NSData dataWithBytes:cvMat.data length:cvMat.elemSize()*cvMat.total()];
  51.     CGColorSpaceRef colorSpace;
  52.    
  53.     if (cvMat.elemSize() == 1) {
  54.         colorSpace = CGColorSpaceCreateDeviceGray();
  55.     } else {
  56.         colorSpace = CGColorSpaceCreateDeviceRGB();
  57.     }
  58.    
  59.     CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
  60.    
  61.     // Creating CGImage from cv::Mat
  62.     CGImageRef imageRef = CGImageCreate(cvMat.cols,
  63.                                         cvMat.rows,
  64.                                         8,
  65.                                         8 * cvMat.elemSize(),
  66.                                         cvMat.step[0],                           
  67.                                         colorSpace,
  68.                                         kCGImageAlphaNone|kCGBitmapByteOrderDefault,
  69.                                         provider,
  70.                                         NULL,
  71.                                         false,
  72.                                         kCGRenderingIntentDefault
  73.                                         );
  74.    
  75.    
  76.     // Getting UIImage from CGImage
  77.     UIImage *finalImage = [UIImage imageWithCGImage:imageRef];
  78.     CGImageRelease(imageRef);
  79.     CGDataProviderRelease(provider);
  80.     CGColorSpaceRelease(colorSpace);
  81.    
  82.     return finalImage;
  83. }
复制代码
3 末了

调试是一个反复修改流程、修改参数的过程,至于为什么是如许的流程和参数都是不断实验之后,通过主观感受得到的结果,有爱好的小伙伴可以自己修改下参数看看效果,假如有更好的方案欢迎你来和我交换探究,还有,假如真的要运用到项目中,这个方案还是不完善的,比如黑底白字就没办法识别,所以还必要加入逻辑判断,举行不同的处理,我这里只是提供一个思路。
4 项目工程

项目分享 :
https://gitee.com/asoonis/htw

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

乌市泽哥

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表