天天看點

使用OpenCv中Mat進行水準投影與垂直投影并實作字元切分

因為要做圖像處理方面的工作,是以最近在學習OpenCv的使用,學習了OpenCv中Mat對象的相關使用之後,實作了使用Mat對象來進行圖像的水準投影和垂直投影,并且在投影之後,對字元進行相對應的切分。現在将相關代碼貼出,一來可以供大家參考并指正錯誤,而來也為的是防止忘記了相關知識。以下就是程式的代碼,歡迎大家指正錯誤。

#include <stdafx.h>
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
#include <stdio.h>
using namespace cv;
using namespace std;

vector<Mat> horizontalProjectionMat(Mat srcImg)//水準投影
{
	Mat binImg;
	blur(srcImg, 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);//初始化數組
	for (int col = 0; col < height; col++)//周遊每個像素點
	{
		for (int row = 0; row < width; row++)
		{
			perPixelValue = binImg.at<uchar>(col, row);
			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;//設定直方圖為黑色
		}
	}
	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);
		}
	}
	delete[] projectValArry;
	return roiList;
}
int main(int argc, char* argv[])
{
        Mat srcImg = imread("E:\\b.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(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);//儲存切分的結果
			}
		}
	 /*
		vector<Mat> a = horizontalProjectionMat(srcImg);
		char szName[30] = { 0 };
		for (int i = 0; i < a.size(); i++)
		{
			vector<Mat> b = verticalProjectionMat(a[i]);
			for (int j = 0; j<b.size();j++)
			{
				sprintf(szName, "E:\\%d.jpg", j);
				imshow(szName, b[j]);
			}
		}
		 */
		cvWaitKey(0);
		getchar();
	    return 0;
}
           

以下是程式的結果截圖:

(1)原始圖像

使用OpenCv中Mat進行水準投影與垂直投影并實作字元切分

(2)垂直投影

使用OpenCv中Mat進行水準投影與垂直投影并實作字元切分

(3)水準投影

使用OpenCv中Mat進行水準投影與垂直投影并實作字元切分

(4)字元切分

使用OpenCv中Mat進行水準投影與垂直投影并實作字元切分

由于水準切分的結果隻有一個,而垂直切分的結果有多個,是以應該要先進行垂直切分,然後再進行水準切分,這樣得到的結果才是切分出字元的大小。若是先進行水準切分,而後進行垂直切分的話,得到的結果就并非要切割出的字元的大小,改代碼已經給出,即注釋掉的代碼,而先進行水準切分,再進行垂直切分的結果如下圖所示:

(5)先水準後垂直

使用OpenCv中Mat進行水準投影與垂直投影并實作字元切分

可以看到,有的切分出的圖像,上下仍然有空白的地方。是以,先水準後垂直的切分方法不适合于本程式的要求。

以上是本人的一點見解,有什麼不正确的地方,歡迎指正。