飞道的博客

图像处理之定位

395人阅读  评论(0)

图像处理之定位

图像处理中常用的定位方法

1、投影定位

投影,在立体几何中,是空间直线在某个方向上的投影,那么图像处理中也是这种投影思想。

最简单的投影:

水平投影(英文名ground plan horizontal projection),水平面方向的正投影叫水平投影。

图像中字符识别时,

水平投影是指二维图像按行向y轴方向投影,将图像数组进行行求和;

垂直投影是指二维图象按列向x轴方向投影,将图像数组进行列求和。

对于二值图像或明显特征的灰度图分割前景与背景,经常用到投影法。在OCR字符分割中,通常会用到该方法,可以很容易的获得每个OCR字符在X轴、Y轴的起始位置与终止位置坐标,进而将每个OCR字符分割出来。

思路:
1.设定大概的ROI区域,取得包含目标OCR区域的图像。
2.先通过垂直投影,获取每个字符的左右边界位置,即X轴坐标,并记录。通过取ROI获得初次分割的图像。
3.再通过对得到的图像进行水平投影,获取每个字符的上下边界,即Y轴坐标,并记录。通过取ROI获得单个目标图像。

   

原图(来自百度百科:https://baike.baidu.com/item/%E7%BE%8E%E5%85%83/484146?fr=aladdin

垂直投影代码如下:


  
  1. /// <summary>
  2. /// 垂直投影
  3. /// </summary>
  4. /// <param name="srcImg:输入图像(多通道彩色或者单通道灰色)"></param>
  5. /// <returns>分割后的单个字符</returns>
  6. vector<Mat> verticalProjectionMat_1(Mat srcImg)//垂直投影
  7. {
  8. if ( nullptr == srcImg.data)
  9. {
  10. std:: cout << "No image!!!" << std:: endl;
  11. return srcImg;
  12. }
  13. Mat binImg;
  14. if ( 3 == srcImg.channels())
  15. {
  16. cvtColor(srcImg, binImg, COLOR_RGB2GRAY);
  17. //std::cout << "binImg.channels()=" << binImg.channels() << std::endl;
  18. }
  19. else if ( 1 == srcImg.channels())
  20. {
  21. srcImg.copyTo(binImg);
  22. }
  23. blur(binImg, binImg, Size( 3, 3)); //均值滤波
  24. threshold(binImg, binImg, 0, 255, /*CV_*/THRESH_OTSU);
  25. cv::namedWindow( "srcImg", 0);
  26. cv::imshow( "srcImg", srcImg);
  27. cv::waitKey( 0);
  28. cv::namedWindow( "binImg", 0);
  29. cv::imshow( "binImg", binImg);
  30. cv::waitKey( 0);
  31. int perPixelValue; //每个像素的值
  32. int width = srcImg.cols;
  33. int height = srcImg.rows;
  34. int* projectValArry = new int[width]; //创建用于储存每列白色像素个数的数组
  35. memset(projectValArry, 0, width * 4.0); //初始化数组
  36. for ( int col = 0; col < width; col++) //列遍历
  37. {
  38. for ( int row = 0; row < height; row++)
  39. {
  40. perPixelValue = binImg.at<uchar>(row, col);
  41. if (perPixelValue == 0) //如果是白底黑字
  42. {
  43. projectValArry[col]++;
  44. }
  45. }
  46. }
  47. //Mat verticalProjectionMat_1(height, width, CV_8UC1);//垂直投影的画布
  48. //for (int i = 0; i < height; i++)
  49. //{
  50. // for (int j = 0; j < width; j++)
  51. // {
  52. // perPixelValue = 255; //背景设置为白色
  53. // verticalProjectionMat_1.at<uchar>(i, j) = perPixelValue;
  54. // }
  55. //}
  56. //垂直投影的画布,白底.
  57. Mat verticalProjectionMat_1(height, width, CV_8UC1, cv::Scalar(255));
  58. //绘制垂直投影直方图
  59. for ( int i = 0; i < width; i++)
  60. {
  61. for ( int j = 0; j < projectValArry[i]; j++)
  62. {
  63. perPixelValue = 0; //直方图设置为黑色
  64. verticalProjectionMat_1.at<uchar>(height - 1 - j, i) = perPixelValue;
  65. }
  66. }
  67. cv::namedWindow( "垂直投影", 0);
  68. cv::imshow( "垂直投影", verticalProjectionMat_1);
  69. cv::waitKey( 0);
  70. vector<Mat> roiList; //用于储存分割出来的每个字符
  71. int startIndex = 0; //记录进入字符区域的索引
  72. int endIndex = 0; //记录进入空白区域的索引
  73. bool inBlock = false; //是否遍历到了字符区内
  74. for ( int i = 0; i < srcImg.cols; i++) //cols=width
  75. {
  76. if (!inBlock && projectValArry[i] != 0) //进入字符区
  77. {
  78. inBlock = true;
  79. startIndex = i;
  80. }
  81. else if (projectValArry[i] == 0 && inBlock) //进入空白区
  82. {
  83. endIndex = i;
  84. inBlock = false;
  85. //分割出单个字符图像
  86. Mat roiImg = srcImg(Range( 0, srcImg.rows), Range(startIndex, endIndex + 1));
  87. roiList.push_back(roiImg);
  88. //cv::namedWindow("roiImg", 0);
  89. //cv::imshow("roiImg", roiImg);
  90. //cv::waitKey(0);
  91. }
  92. }
  93. char szName[ 30] = { 0 };
  94. for ( int i = 0; i < roiList.size(); i++)
  95. {
  96. sprintf_s(szName, "..\\IMG_output\\字符分割\\_%d.jpg", i);
  97. cv::namedWindow( "分割1", 0);
  98. cv::imshow( "分割1", roiList[i]);
  99. cv::imwrite(szName, roiList[i]);
  100. cv::waitKey( 0);
  101. }
  102. delete[] projectValArry; //怎么new的,就怎么delete掉!!!
  103. return roiList;
  104. }

根据图像本身的颜色特征,选择R通道(前景明显,背景较干净,便于处理)的单色图像作为输入图像,进行阈值处理(图像处理阈值分割之OTSU/大津阈值原理及其实现)、投影。

二值化图像、垂直投影图像:

初次分割后得到的单个字符图像:

通过垂直投影后,得到了OCR单个字符的左右边界精确位置坐标。接下来通过水平投影,即可得到单个字符上下边界的精确位置坐标。

水平投影代码如下:


  
  1. /// <summary>
  2. /// 水平投影
  3. /// </summary>
  4. /// <param name="srcImg:输入图像(多通道彩色或者单通道灰色)"></param>
  5. /// <returns>分割后的单个字符</returns>
  6. vector<Mat> horizontalProjectionMat_1(Mat srcImg)//水平投影
  7. {
  8. Mat binImg;
  9. if ( 3 == srcImg.channels())
  10. {
  11. cvtColor(srcImg, binImg, COLOR_RGB2GRAY);
  12. //std::vector<cv::Mat> rgbImg(3);
  13. //split(srcImg, rgbImg);//分离出图片的B,G,R颜色通道.
  14. //cv::namedWindow("B_rgbImg[0]", 0);
  15. //cv::namedWindow("G_rgbImg[1]", 0);
  16. //cv::namedWindow("R_rgbImg[2]", 0);
  17. //cv::imshow("B_rgbImg[0]", rgbImg[0]);
  18. //cv::imshow("G_rgbImg[1]", rgbImg[1]);
  19. //cv::imshow("R_rgbImg[2]", rgbImg[2]);
  20. //cv::waitKey(0);
  21. //binImg = rgbImg[2];
  22. // std::cout << "binImg.channels()=" << binImg.channels() << std::endl;
  23. }
  24. else if ( 1 == srcImg.channels())
  25. {
  26. srcImg.copyTo(binImg);
  27. }
  28. blur(binImg, binImg, Size( 3, 3));
  29. threshold(binImg, binImg, 0, 255, /*CV_*/THRESH_OTSU);
  30. int perPixelValue = 0; //每个像素的值
  31. int width = srcImg.cols;
  32. int height = srcImg.rows;
  33. int* projectValArry = new int[height]; //创建一个储存每行白色像素个数的数组
  34. memset(projectValArry, 0, height* 4.0); //初始化数组
  35. for ( int row = 0; row < height; row++) //遍历每个像素点
  36. {
  37. for ( int col = 0; col < width; col++)
  38. {
  39. perPixelValue = binImg.at<uchar>(row, col);
  40. if (perPixelValue == 0) //白底黑字
  41. {
  42. projectValArry[row]++; //每一行的有效像素数.
  43. }
  44. }
  45. }
  46. 创建画布,并设为白底.
  47. //Mat horizontalProjectionMatIMG_1(height, width, CV_8UC1);//创建画布
  48. //for (int i = 0; i < height; i++)
  49. //{
  50. // for (int j = 0; j < width; j++)
  51. // {
  52. // perPixelValue = 255;
  53. // horizontalProjectionMatIMG_1.at<uchar>(i, j) = perPixelValue;//设置背景为白色
  54. // }
  55. //}
  56. //垂直投影的画布,白底.
  57. Mat horizontalProjectionMatIMG_1(height, width, CV_8UC1, cv::Scalar(255));
  58. for ( int i = 0; i < height; i++) //水平直方图
  59. {
  60. for ( int j = 0; j < projectValArry[i]; j++)
  61. {
  62. perPixelValue = 0;
  63. horizontalProjectionMatIMG_1.at<uchar>(i, width - 1 - j) = perPixelValue; //设置直方图为黑色
  64. }
  65. }
  66. vector<Mat> roiList; //用于储存分割出来的每个字符
  67. int startIndex = 0; //记录进入字符区的索引
  68. int endIndex = 0; //记录进入空白区域的索引
  69. bool inBlock = false; //是否遍历到了字符区内
  70. for ( int i = 0; i <srcImg.rows; i++)
  71. {
  72. if (!inBlock && projectValArry[i] != 0) //进入字符区
  73. {
  74. inBlock = true;
  75. startIndex = i;
  76. }
  77. else if (inBlock && projectValArry[i] == 0) //进入空白区
  78. {
  79. endIndex = i;
  80. inBlock = false;
  81. Mat roiImg = srcImg(Range(startIndex, endIndex + 1), Range( 0, srcImg.cols)); //从原图中截取有效图像的区域
  82. roiList.push_back(roiImg);
  83. }
  84. }
  85. delete[] projectValArry; //怎么new的,就怎么delete掉!!!
  86. return roiList;
  87. }

最终得到的图像:

 

2、积分图定位

在定位OCR区域时,也可以使用积分图进行精确定位到OCR区域后,再进行垂直、水平投影分割,直接得到OCR字符小图。

关于积分图,这里就不再赘述了,详情请看:图像处理之图像积分图integral()

 

原图:

 

 

3、匹配定位

 

 

 

opencv

 

halcon

 

c++

 

 

 

 

一、

GMS了解一下

要求实时(精度也不差): orb+GMS

要求精度: A-SIFT+GMS

相同程度匹配,速度精度比RANSAC效果好

二、

OpenCV官方提出的“解决方案”:Features2D + Homography to find a known object。地址:Features2D + Homography to find a known object。该方案先用SURF来提取特征点然后用FLANN进行匹配,过滤出足够好的匹配点之后,用一个矩形来定位出被探测的物体。如下图:

 

三、

sift+ransac

sift/surf+flann+ransac,一般情况都好使。实时性的话,要看具体的要求。

四、

形状匹配

 

 

 

 

 

 

 

【36、这一秒不放弃,下一秒就有希望!坚持下去才可能成功!】

 

 

 


转载:https://blog.csdn.net/yishuihanq/article/details/109001958
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场