ANN在訓練和識別時都只能將一個單獨的數字作爲樣本,因此對於掃描圖像中的多個連續數字需要進行分割。具體算法如下:
1,確定圖像中字符的大致高度範圍:先自下而上對圖像進行逐行掃描,直到遇到第一個黑素像素,記下行號,然後自上而下對圖像進行逐行掃描,直到遇到第一個黑素像素,記下行號。這兩個行號就標誌出了字符大致的高度範圍。
2,確定每個字符的左起始和右終止位置:在第一步得到的高度範圍內進行自左向右逐列掃描,遇到第一個黑色像素時,認爲是字符分割的起始位,然後繼續掃描,直到遇到有一列中沒有黑色像素,認爲是這個字符的右終止位置,準備開始進行下一個字符的分割。按照上述方法繼續掃描,直到掃描到圖像的最右端。這樣就得到了每個字符的比較精確的快讀範圍。
3,在已知的每個字符比較精確的寬度範圍內,再按照第一步的方法,分別自下而上和自上而下,逐行掃描,來獲取每個字符精確的高度範圍。
完整實現代碼如下:
/****************************************************************
參數: 對前景目標(如字符)進行劃分,將各個字符的輪廓矩形返回
注 : 只能處理二值圖像
返回值:無
***************************************************************/
void Ctry::ObjectSegment()
{
// TODO: 在此添加命令處理程序代碼
IplImage* img = cvLoadImage("C:\\Users\\Administrator\\Desktop\\dst.jpg", -1);
vector<RECT> vecRoughRECT; //粗略對象輪廓的矩形向量數組
vector<RECT> vecRECT; //精化後對象輪廓的矩形向量數組
//清空用來表示每個對象的vector
vecRoughRECT.clear();
vecRECT.clear();
int nTop, nBttom; //整體前景區域的上下邊界
int nObjCnt = 0; //對象數目
//從上向下掃描,找到整體區域的前景的上邊界
for (int i = 0; i < img->height; i++)
{
for (int j = 0; j < img->width; j++)
{
double pixel = cvGetReal2D(img, i, j);
if (int(pixel)==0)
{
nTop = i;
i = img->height; //對i賦大值,使得在break跳出內存循環後,直接在跳出外層循環
break;
}
}
}
//從下向上掃描,找到整體區域的前景的下邊界
for (int i = img->height -1; i >=0; i--)
{
for (int j = 0; j < img->width; j++)
{
double pixel = cvGetReal2D(img, i, j);
if (int(pixel) == 0)
{
nBttom = i;
i = -1; //對i賦小值,使得在break跳出內存循環後,直接在跳出外層循環
break;
}
}
}
bool bStartSeg = false; //是否已經開始某一個對象的分割
bool bBlackInCol; //某一列中是否包含黑色像素
RECT rt;
//按列掃描,找到每一個目標的左右邊界
for (int j = 0; j < img->width; j++)
{
bBlackInCol = false;
for (int i = 0; i < img->height; i++)
{
double pixel = cvGetReal2D(img, i, j);
if (int(pixel) == 0)
{
bBlackInCol = true; //該列中發現黑點
if (!bStartSeg) //還沒有進入一個對象的分割
{
bStartSeg = true;//進入一個對象的分割
rt.left = j;
}
else
break;
}
}
if (j == (img->width - 1)) //掃描到最後一列了,說明整個圖像掃描完畢
{
break;
}
//正處在分割狀態,且掃描完一列都沒有發現黑像素,表明當前對象分割完畢
if (bStartSeg && !bBlackInCol)
{
rt.right = j; //對象右邊界確定
//對象的粗略上下邊界(有待精化)
rt.top = nTop;
rt.bottom = nBttom;
::InflateRect(&rt, 1, 1); //矩形框膨脹一個像素,以免繪製時壓到字符
vecRoughRECT.push_back(rt); //插入vector
bStartSeg = false; //當前分割結束
nObjCnt++; //對象數目加1
}
}
RECT rtNEW; //存放精化對象區域的矩形框
//由於得到了精確的左右邊界,現在可以精化矩形框的上下邊界
int nSize = vecRoughRECT.size();
for (int nObj = 0; nObj < nSize; nObj++)
{
rt = vecRoughRECT[nObj];
rtNEW.left = rt.left - 1;
rtNEW.right = rt.right + 1;
//從上向下逐行掃描邊界
for (int i = rt.top; i < rt.bottom; i++)
{
for (int j = rt.left; j < rt.right; j++)
{
double pixel = cvGetReal2D(img, i, j);
if (int(pixel) == 0)
{
rtNEW.top = i - 1;
//對i賦大值,使得在break跳出內存循環後,直接在跳出外層循環
i = rt.bottom;
break;
}
}
}
//從下向上逐行掃描邊界
for (int i = rt.bottom - 1; i > rt.top; i--)
{
for (int j = rt.left; j < rt.right; j++)
{
double pixel = cvGetReal2D(img, i, j);
if (int(pixel) == 0)
{
rtNEW.bottom = i + 1;
//對i賦小值,使得在break跳出內存循環後,直接在跳出外層循環
i = rt.top-1;
break;
}
}
}
vecRECT.push_back(rtNEW);
}
//畫矩形框,顯示分割字符
for (int i = 0; i < vecRECT.size(); i++)
{
int x = vecRECT[i].left-1;
int y = vecRECT[i].top-1;
int x1 = vecRECT[i].right+1;
int y1 = vecRECT[i].bottom + 1;
CvPoint pt1(x, y);
CvPoint pt2(x1, y1);
cvRectangle(img, pt1, pt2, CV_RGB(255, 0, 0), 1);
}
cvSaveImage("C:\\Users\\Administrator\\Desktop\\rect.jpg", img);
cvNamedWindow("image1", 1);
cvShowImage("image1", img);
cvWaitKey(0);
cvDestroyWindow("image1");
cvReleaseImage(&img);
}
效果圖:
注:此算法對圖像的進行處理前,需要對圖形進行預處理,尤其是去噪,這裏代碼沒有給出。圖片質量不好,或預處理方法不對,效果也就不理想。