图像处理之定位
图像处理中常用的定位方法
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)
垂直投影代码如下:
-
/// <summary>
-
/// 垂直投影
-
/// </summary>
-
/// <param name="srcImg:输入图像(多通道彩色或者单通道灰色)"></param>
-
/// <returns>分割后的单个字符</returns>
-
vector<Mat> verticalProjectionMat_1(Mat srcImg)//垂直投影
-
{
-
if (
nullptr == srcImg.data)
-
{
-
std::
cout <<
"No image!!!" <<
std::
endl;
-
return srcImg;
-
}
-
-
Mat binImg;
-
if (
3 == srcImg.channels())
-
{
-
cvtColor(srcImg, binImg, COLOR_RGB2GRAY);
-
//std::cout << "binImg.channels()=" << binImg.channels() << std::endl;
-
}
-
else
if (
1 == srcImg.channels())
-
{
-
srcImg.copyTo(binImg);
-
}
-
blur(binImg, binImg, Size(
3,
3));
//均值滤波
-
threshold(binImg, binImg,
0,
255,
/*CV_*/THRESH_OTSU);
-
-
cv::namedWindow(
"srcImg",
0);
-
cv::imshow(
"srcImg", srcImg);
-
cv::waitKey(
0);
-
-
cv::namedWindow(
"binImg",
0);
-
cv::imshow(
"binImg", binImg);
-
cv::waitKey(
0);
-
-
-
int perPixelValue;
//每个像素的值
-
int width = srcImg.cols;
-
int height = srcImg.rows;
-
int* projectValArry =
new
int[width];
//创建用于储存每列白色像素个数的数组
-
memset(projectValArry,
0, width *
4.0);
//初始化数组
-
for (
int col =
0; col < width; col++)
//列遍历
-
{
-
for (
int row =
0; row < height; row++)
-
{
-
perPixelValue = binImg.at<uchar>(row, col);
-
if (perPixelValue ==
0)
//如果是白底黑字
-
{
-
projectValArry[col]++;
-
}
-
}
-
}
-
//Mat verticalProjectionMat_1(height, width, CV_8UC1);//垂直投影的画布
-
//for (int i = 0; i < height; i++)
-
//{
-
// for (int j = 0; j < width; j++)
-
// {
-
// perPixelValue = 255; //背景设置为白色
-
// verticalProjectionMat_1.at<uchar>(i, j) = perPixelValue;
-
// }
-
//}
-
-
//垂直投影的画布,白底.
-
Mat verticalProjectionMat_1(height, width, CV_8UC1, cv::Scalar(255));
-
//绘制垂直投影直方图
-
for (
int i =
0; i < width; i++)
-
{
-
for (
int j =
0; j < projectValArry[i]; j++)
-
{
-
perPixelValue =
0;
//直方图设置为黑色
-
verticalProjectionMat_1.at<uchar>(height -
1 - j, i) = perPixelValue;
-
}
-
}
-
cv::namedWindow(
"垂直投影",
0);
-
cv::imshow(
"垂直投影", verticalProjectionMat_1);
-
cv::waitKey(
0);
-
-
-
vector<Mat> roiList;
//用于储存分割出来的每个字符
-
int startIndex =
0;
//记录进入字符区域的索引
-
int endIndex =
0;
//记录进入空白区域的索引
-
bool inBlock =
false;
//是否遍历到了字符区内
-
for (
int i =
0; i < srcImg.cols; i++)
//cols=width
-
{
-
if (!inBlock && projectValArry[i] !=
0)
//进入字符区
-
{
-
inBlock =
true;
-
startIndex = i;
-
}
-
else
if (projectValArry[i] ==
0 && inBlock)
//进入空白区
-
{
-
endIndex = i;
-
inBlock =
false;
-
//分割出单个字符图像
-
Mat roiImg = srcImg(Range(
0, srcImg.rows), Range(startIndex, endIndex +
1));
-
roiList.push_back(roiImg);
-
-
//cv::namedWindow("roiImg", 0);
-
//cv::imshow("roiImg", roiImg);
-
//cv::waitKey(0);
-
}
-
}
-
-
char szName[
30] = {
0 };
-
for (
int i =
0; i < roiList.size(); i++)
-
{
-
sprintf_s(szName,
"..\\IMG_output\\字符分割\\_%d.jpg", i);
-
-
cv::namedWindow(
"分割1",
0);
-
cv::imshow(
"分割1", roiList[i]);
-
cv::imwrite(szName, roiList[i]);
-
cv::waitKey(
0);
-
}
-
-
-
delete[] projectValArry;
//怎么new的,就怎么delete掉!!!
-
return roiList;
-
}
根据图像本身的颜色特征,选择R通道(前景明显,背景较干净,便于处理)的单色图像作为输入图像,进行阈值处理(图像处理阈值分割之OTSU/大津阈值原理及其实现)、投影。
二值化图像、垂直投影图像:
初次分割后得到的单个字符图像:
通过垂直投影后,得到了OCR单个字符的左右边界精确位置坐标。接下来通过水平投影,即可得到单个字符上下边界的精确位置坐标。
水平投影代码如下:
-
/// <summary>
-
/// 水平投影
-
/// </summary>
-
/// <param name="srcImg:输入图像(多通道彩色或者单通道灰色)"></param>
-
/// <returns>分割后的单个字符</returns>
-
vector<Mat> horizontalProjectionMat_1(Mat srcImg)//水平投影
-
{
-
Mat binImg;
-
if (
3 == srcImg.channels())
-
{
-
cvtColor(srcImg, binImg, COLOR_RGB2GRAY);
-
-
//std::vector<cv::Mat> rgbImg(3);
-
//split(srcImg, rgbImg);//分离出图片的B,G,R颜色通道.
-
-
//cv::namedWindow("B_rgbImg[0]", 0);
-
//cv::namedWindow("G_rgbImg[1]", 0);
-
//cv::namedWindow("R_rgbImg[2]", 0);
-
//cv::imshow("B_rgbImg[0]", rgbImg[0]);
-
//cv::imshow("G_rgbImg[1]", rgbImg[1]);
-
//cv::imshow("R_rgbImg[2]", rgbImg[2]);
-
//cv::waitKey(0);
-
-
//binImg = rgbImg[2];
-
// std::cout << "binImg.channels()=" << binImg.channels() << std::endl;
-
}
-
else
if (
1 == srcImg.channels())
-
{
-
srcImg.copyTo(binImg);
-
}
-
blur(binImg, binImg, Size(
3,
3));
-
threshold(binImg, binImg,
0,
255,
/*CV_*/THRESH_OTSU);
-
-
int perPixelValue =
0;
//每个像素的值
-
int width = srcImg.cols;
-
int height = srcImg.rows;
-
int* projectValArry =
new
int[height];
//创建一个储存每行白色像素个数的数组
-
memset(projectValArry,
0, height*
4.0);
//初始化数组
-
for (
int row =
0; row < height; row++)
//遍历每个像素点
-
{
-
for (
int col =
0; col < width; col++)
-
{
-
perPixelValue = binImg.at<uchar>(row, col);
-
if (perPixelValue ==
0)
//白底黑字
-
{
-
projectValArry[row]++;
//每一行的有效像素数.
-
}
-
}
-
}
-
-
创建画布,并设为白底.
-
//Mat horizontalProjectionMatIMG_1(height, width, CV_8UC1);//创建画布
-
//for (int i = 0; i < height; i++)
-
//{
-
// for (int j = 0; j < width; j++)
-
// {
-
// perPixelValue = 255;
-
// horizontalProjectionMatIMG_1.at<uchar>(i, j) = perPixelValue;//设置背景为白色
-
// }
-
//}
-
-
//垂直投影的画布,白底.
-
Mat horizontalProjectionMatIMG_1(height, width, CV_8UC1, cv::Scalar(255));
-
-
for (
int i =
0; i < height; i++)
//水平直方图
-
{
-
for (
int j =
0; j < projectValArry[i]; j++)
-
{
-
perPixelValue =
0;
-
horizontalProjectionMatIMG_1.at<uchar>(i, width -
1 - j) = perPixelValue;
//设置直方图为黑色
-
}
-
}
-
vector<Mat> roiList;
//用于储存分割出来的每个字符
-
int startIndex =
0;
//记录进入字符区的索引
-
int endIndex =
0;
//记录进入空白区域的索引
-
bool inBlock =
false;
//是否遍历到了字符区内
-
for (
int i =
0; i <srcImg.rows; i++)
-
{
-
if (!inBlock && projectValArry[i] !=
0)
//进入字符区
-
{
-
inBlock =
true;
-
startIndex = i;
-
}
-
else
if (inBlock && projectValArry[i] ==
0)
//进入空白区
-
{
-
endIndex = i;
-
inBlock =
false;
-
Mat roiImg = srcImg(Range(startIndex, endIndex +
1), Range(
0, srcImg.cols));
//从原图中截取有效图像的区域
-
roiList.push_back(roiImg);
-
}
-
}
-
delete[] projectValArry;
//怎么new的,就怎么delete掉!!!
-
return roiList;
-
}
最终得到的图像:
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