用OpenCv中Mat进行水平投影与垂直投影并实现字符切分
目前即将开始的一个新项目是进行字符识别,下面就使用水平投影机垂直投影的测试代码贴出,供大家参考。
#include<iostream>
#include<opencv2\highgui.hpp>
#include<opencv2\imgproc\imgproc.hpp>
#include<stdio.h>
using namespace cv;
using namespace std;
vector<Mat> horizontalProjectionMat(Mat srcImg)//水平投影
{
Mat binImg;
blur(srcImg, binImg, Size(3, 3));//blur均值滤波函数,滤波核大小为3*3
threshold(binImg, binImg, 0, 255, CV_THRESH_OTSU);//threshold函数是进行图像二值化处理,使用OTSU算法确定分割阈值
int perPixelValue = 0;
int width = srcImg.cols;
int height = srcImg.rows;
int* projectValArry = new int[height];//创建一个存储每行白色像素个数的数组
memset(projectValArry, 0, height * 4);//初始化数组: 比如memset(ch,0,8),就是把数组ch前八项置为零,后面的不一定为零。
for (int col = 0; col < height; col++)//遍历每个像素点
{
for (int row = 0; row < width; row++)
{
perPixelValue = binImg.at<uchar>(col, row);//img.at<uchar>(i, j)=128这是指将灰度图的第x行,第y列的置为灰度为128的灰色(0为黑色,255为白色)
//如果是彩图,则写为ima.at<cv::Vec3b>(x, y)[0],ima.at<cv::Vec3b>(x, y)[1],ima.at<cv::Vec3b>(x, y)[2],分别表示红绿蓝(顺序不确定)三种颜色的色度
if (perPixelValue == 0)//如果是白底黑字
{
projectValArry[col]++;
}
}
}
Mat horizontalProjectionMat(height, width, CV_8UC1);//创建画布
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
perPixelValue = 255;
horizontalProjectionMat.at<uchar>(i, j) = perPixelValue;//设置背景为白色
}
}
for (int i = 0; i < height; i++)//水平直方图
{
for (int j = 0; j < projectValArry[i]; j++)
{
perPixelValue = 0;
horizontalProjectionMat.at<uchar>(i, width - 1 - j) = perPixelValue;//设置直方图为黑色
}
}
imshow("水平投影", horizontalProjectionMat);
cvWaitKey(0);
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;
return roilist;
}
vector<Mat> verticalProjectionMat(Mat srcImg)//垂直投影
{
Mat binImg;
blur(srcImg, binImg, Size(3, 3));
threshold(binImg, binImg, 0, 255, CV_THRESH_OTSU);
int perPixelValue;//每个像素的值
int width = srcImg.cols;
int height = srcImg.rows;
int* projectValArry = new int[width];//创建用于存储每列白色像素个数的数组
memset(projectValArry, 0, width * 4);//初始化数组
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(height, width, CV_8UC1);//垂直投影的画布
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
perPixelValue = 255;//背景设置为白色
verticalProjectionMat.at<uchar>(i, j) = perPixelValue;
}
}
for (int i = 0; i < width; i++)//垂直投影直方图
{
for (int j = 0; j < projectValArry[i]; j++)
{
perPixelValue = 0;//直方图设置为黑色
verticalProjectionMat.at<uchar>(height - 1 - j, i) = perPixelValue;
}
}
imshow("垂直投影", verticalProjectionMat);
cvWaitKey(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);//push_back() 在Vector最后添加一个元素(参数为要插入的值)
}
}
delete[] projectValArry;
return roilist;
}
int main(int argc, char* argv[])
{
Mat srcImg = imread("E:/OpenCV/OpenCVTest/4.png", 0);
char szName[30] = { 0 };
vector<Mat> b = verticalProjectionMat(srcImg);//先进行垂直投影
for (int i = 0; i < b.size(); i++)
{
vector<Mat> a = horizontalProjectionMat(b[i]);//水平投影
sprintf_s(szName, "E:\\picture\\%d.jpg", i);
for (int j = 0; j < a.size(); j++)
{
imshow(szName, a[j]);
IplImage img = IplImage(a[j]);
cvSaveImage(szName, &img);//保存切分图像
}
}
cvWaitKey(0);
getchar();
return 0;
}
结果:
(1)原始图像:
(2)垂直投影:
(3)水平投影
(4)字符切分结果