java车牌识别字符分割_车牌识别之字符分割
由于在車牌定位中,我們使用了歸一化過程。因此所需要處理的車牌的大小是統(tǒng)一的,在目前的版本中這個(gè)值是136*36。
那么字符分割的結(jié)果就是將車牌中的所有文字一一分割開來,形成單一的字符塊。生成的字符塊就可以輸入下一步的字符識(shí)別部分進(jìn)行識(shí)別。我們可以使用人工神經(jīng)網(wǎng)絡(luò),也就是ANN來識(shí)別字符。
具體而言,字符分割過程是如何做的呢?簡(jiǎn)單說,就是:灰度化->顏色判斷->二值化->取輪廓->找外接矩形->截取圖塊。
1.灰度化
首先,我們把彩色的圖片轉(zhuǎn)化為灰度化圖片。注意:為了以后可以利用彩色信息,在前面的車牌檢測(cè)過程中,我們的輸出結(jié)果不是灰度化圖片,而是彩色圖片。這樣以后當(dāng)我們改正算法,想利用彩色信息時(shí)就可以使用了。
但是在這里,我們的算法還是針對(duì)的是灰度化圖片,因此首先進(jìn)行灰度化處理。
灰度化后的圖片見下圖:
2.顏色判斷
灰度化之后,為了分割字符。我們需要獲取字符的輪廓。注意:分割字符有很多種方法。例如投影法,滑動(dòng)窗口判斷法,在這里,使用的是取字符輪廓法。
因?yàn)樾枰≥喞?#xff0c;就需要把圖片轉(zhuǎn)化成一個(gè)二值化圖片。不過,由于藍(lán)色和黃色車牌圖片的區(qū)別,兩者需要用的二值化參數(shù)不一樣,因此這里需要對(duì)車牌圖片的顏色進(jìn)行一個(gè)判斷。車牌顏色對(duì)二值化的影響的分析見后面“其他細(xì)節(jié)”章節(jié)。
這里顏色判斷的使用的是前面顏色定位詳解里的模板匹配法。
3.二值化
獲取顏色后,就可以選擇不同的參數(shù)進(jìn)行大津閾值法來進(jìn)行二值化。對(duì)于本示例圖片中的藍(lán)色車牌而言,使用的參數(shù)為CV_THRESH_BINARY。
二值化后的效果見下圖:
4.取輪廓
接下來,使用被多次用到的取輪廓方法findContours。關(guān)于這個(gè)方法的具體內(nèi)容,在前面的開發(fā)詳解中已做過介紹,這里不再贅述。
取輪廓后的結(jié)果如下圖:
注意:直接使用findContours方法取輪廓時(shí),在處理中文字符,也就是“蘇”時(shí),會(huì)發(fā)生斷裂現(xiàn)象。因此為了處理中文字符,EasyPR換了一種思路,使用了額外的步驟來解決這個(gè)問題。具體可以見后面的“中文字符處理”章節(jié)。
5.找外接矩形
使用了中文字符處理方法以后,成功獲取了所有的字符的外接矩形。
具體見下圖:
6.截取圖塊
最后,把圖中的外接矩形一一截取出來,歸一化到統(tǒng)一格式。留待輸入下個(gè)步驟–字符識(shí)別模塊處理。
歸一化后字符圖塊見下圖:
中文字符處理
上面的流程在處理英文車牌時(shí),效果是很好的。但是在處理中文車牌時(shí),存在一個(gè)很大的問題。
在取輪廓時(shí),中文由于自身的特性,例如有筆畫區(qū)間,取輪廓會(huì)造成斷裂現(xiàn)象。例如下圖中的“蘇”。英文字符通過取輪廓都被完整的包括了,而“蘇”字則分成了兩個(gè)連通區(qū)域。
雖然并不是所有的中文都會(huì)存在這個(gè)問題(例如下圖的“津”字),但直接用取輪廓操作已經(jīng)不合適了。
那么如何解決這個(gè)問題的呢?其實(shí)想法很簡(jiǎn)單。那就是既然有些中文字符沒辦法用取輪廓處理,那么就干脆先不處理中文字符,而是用取輪廓操作處理中文字符后面的字符。例如“蘇A88M88”,其中“A88M88”這六個(gè)字符我都能用取輪廓操作獲得。我先獲取這六個(gè)字符,再想辦法獲取中文字符。
獲取這六個(gè)字符后,接下來該如何獲取“蘇”這個(gè)中文字符的輪廓呢?
這里的關(guān)鍵就是“蘇”字符后面的“A”字符,這個(gè)字符在中文車牌里代表城市的代碼,我們?cè)谶@里簡(jiǎn)稱它為“城市字符”或者“特殊字符”。
這個(gè)字符有一個(gè)特征,就是與后面的字符存在一定的間隔。但是與前面的中文字符靠的較緊。倘若我獲取了這個(gè)特殊字符的外接矩形,只要把這個(gè)外接矩形向左做一些的偏移(偏移的大小可以通過經(jīng)驗(yàn)指定,例如設(shè)置為字符寬度的1.15倍),這樣這個(gè)外接矩形就成了包含中文字符的一個(gè)矩形了。下面就可以截取中文字符的圖塊。
下圖就是“特殊字符”與被反推得到的“中文字符”的矩形,在圖中用紅色矩形表示。
下面的問題就是如何獲取“特殊字符”的位置?
一種方法是把所有取輪廓操作獲取到的矩形進(jìn)行排序,最左邊的就是特殊字符的圖塊。但是有些中文字符會(huì)被取輪廓操作截取為一個(gè)連通區(qū)域。在這種情況下,最左邊的圖塊矩形是中文字符的矩形,而不是特殊字符的矩形了。所以這個(gè)方法不能用。
另一種方法就是依次判斷所有取輪廓操作得到的矩形的位置,設(shè)矩形的中點(diǎn)恰好在整個(gè)車牌的1/7到2/7之間時(shí)的矩形為特殊矩形。這樣操作的前提是我們的車牌定位的非常準(zhǔn)確,恰到把整個(gè)車牌截取的正正好。在這種情況下,只要外接矩形滿足這些條件,就可以判斷為特殊字符的矩形。
這個(gè)方法思路很簡(jiǎn)單,實(shí)際中應(yīng)用效果也不錯(cuò),因此也是我們目前采用的方法。
//! 找出指示城市的字符的Rect,例如蘇A7003X,就是"A"的位置
int CCharsSegment::GetSpecificRect(const vector& vecRect) {
vector xpositions;
int maxHeight = 0;
int maxWidth = 0;
for (size_t i = 0; i < vecRect.size(); i++) {
xpositions.push_back(vecRect[i].x);
if (vecRect[i].height > maxHeight) {
maxHeight = vecRect[i].height;
}
if (vecRect[i].width > maxWidth) {
maxWidth = vecRect[i].width;
}
}
int specIndex = 0;
for (size_t i = 0; i < vecRect.size(); i++) {
Rect mr = vecRect[i];
int midx = mr.x + mr.width / 2;
//如果一個(gè)字符有一定的大小,并且在整個(gè)車牌的1/7到2/7之間,則是我們要找的特殊字符
//當(dāng)前字符和下個(gè)字符的距離在一定的范圍內(nèi)
if ((mr.width > maxWidth * 0.8 || mr.height > maxHeight * 0.8) &&
(midx < int(m_theMatWidth / 7) * 2 &&
midx > int(m_theMatWidth / 7) * 1)) {
specIndex = i;
}
}
return specIndex;
}
以上就是EasyPR能處理中文車牌的主要原因。原先的taotao1233的代碼中無法處理中文的原因就是沒有這樣一步預(yù)處理。其實(shí)這是一個(gè)很簡(jiǎn)單的思想,但在之前并沒有被實(shí)現(xiàn)。EasyPR里實(shí)現(xiàn)了這個(gè)思路,同時(shí)發(fā)現(xiàn),這個(gè)方法效果出奇的好。基本可以應(yīng)對(duì)所有的情況。所以說,這個(gè)方法可以說是一個(gè)簡(jiǎn)單,有效的處理中文車牌的方法.
其他一些細(xì)節(jié)
1.顏色判斷
在進(jìn)行二值化前,需要進(jìn)行一次顏色判斷,這是因?yàn)閷?duì)于藍(lán)色和黃色車牌而言,使用的二值化策略必須不同。
對(duì)于藍(lán)色車牌而言,使用的參數(shù)為CV_THRESH_BINARY。
而對(duì)于黃色車牌而言,使用的參數(shù)為CV_THRESH_BINARY_INV。
假設(shè)黃色車牌使用了CV_THRESH_BINARY作為參數(shù),則會(huì)發(fā)生如下圖一樣的二值化結(jié)果,其中字符部分變成了黑色,而背景則是白色(同理,藍(lán)色車牌使用CV_THRESH_BINARY_INV也是一樣的效果)。
在這種不正確的參數(shù)帶來的二值化情況下,取輪廓操作將無法按照預(yù)期的行為進(jìn)行處理。因此,必須使用正確的二值化參數(shù)。
在顏色判斷時(shí),有一個(gè)小技巧,就是先把四周的“邊”截取后再進(jìn)行顏色的判斷,這樣可以消除車牌定位時(shí)一些多余的四周的干擾
1 Mat tmpMat = input(Rect_(w * 0.1, h * 0.1, w * 0.8, h * 0.8));
2
3 // 判斷車牌顏色以此確認(rèn)threshold方法
4 Color plateType = getPlateType(tmpMat, true);
顏色判斷代碼如下
// getPlateType
//判斷車牌的類型
Color getPlateType(const Mat& src, const bool adaptive_minsv) {
float max_percent = 0;
Color max_color = UNKNOWN;
float blue_percent = 0;
float yellow_percent = 0;
float white_percent = 0;
if (plateColorJudge(src, BLUE, adaptive_minsv, blue_percent) == true) {
// cout << "BLUE" << endl;
return BLUE;
} else if (plateColorJudge(src, YELLOW, adaptive_minsv, yellow_percent) ==
true) {
// cout << "YELLOW" << endl;
return YELLOW;
} else if (plateColorJudge(src, WHITE, adaptive_minsv, white_percent) ==
true) {
// cout << "WHITE" << endl;
return WHITE;
} else {
// cout << "OTHER" << endl;
// 如果任意一者都不大于閾值,則取值最大者
max_percent = blue_percent > yellow_percent ? blue_percent : yellow_percent;
max_color = blue_percent > yellow_percent ? BLUE : YELLOW;
max_color = max_percent > white_percent ? max_color : WHITE;
return max_color;
}
}
2.排除縫隙
在獲得中文字符圖塊以后,下面一步就是把剩下的圖塊獲取了。不過由于中文車牌一般只有7個(gè)字符,所以可以把后面的圖塊從左到右排序,依次選擇6個(gè)即可。一些會(huì)被誤判為“I”的縫隙可以通過這種方法排除出去。
例如下圖中,最右邊的一個(gè)縫隙會(huì)被誤識(shí)別為”1”。但是倘若從左到右依次選擇的話,這個(gè)縫隙并不會(huì)被選入候選集合中,因?yàn)樗呀?jīng)是“第八個(gè)”字符了。
//! 這個(gè)函數(shù)做兩個(gè)事情
// 1.把特殊字符Rect左邊的全部Rect去掉,后面再重建中文字符的位置。
// 2.從特殊字符Rect開始,依次選擇6個(gè)Rect,多余的舍去。
int CCharsSegment::RebuildRect(const vector& vecRect,
vector& outRect, int specIndex) {
int count = 6;
for (size_t i = specIndex; i < vecRect.size() && count; ++i, --count) {
outRect.push_back(vecRect[i]);
}
return 0;
}
3.去除柳釘
有些中國的車牌中有一個(gè)非常妨礙識(shí)別的東西,那就是柳釘。倘若對(duì)一副含有柳釘?shù)膱D進(jìn)行二值化,極有可能會(huì)出現(xiàn)下圖的結(jié)果。一些字符圖塊(下圖的”9”和”1”)通過柳釘?shù)脑蚵?lián)系到了一體,那樣的話就無法通過取輪廓操作來分割了。
因此在二值化之后,還需要一個(gè)去除柳釘?shù)牟僮鳌?/p>
去除柳釘?shù)乃枷胍膊⒉粡?fù)雜,就是依次掃描每行,判斷跳變次數(shù)。車牌字符所在的行的跳變次數(shù)是很多的,而柳釘所在的行就會(huì)偏少。因此當(dāng)發(fā)現(xiàn)某行跳變次數(shù)較少,則可以把該行的所有像素值賦值為0,這樣就會(huì)大幅度消除柳釘?shù)挠绊懥恕?/p>
下圖就是去除柳釘后的效果。
//去除車牌上方的鈕釘
//計(jì)算每行元素的階躍數(shù),如果小于X認(rèn)為是柳丁,將此行全部填0(涂黑)
// X的推薦值為,可根據(jù)實(shí)際調(diào)整
bool clearLiuDing(Mat& img) {
vector fJump;
int whiteCount = 0;
const int x = 7;
Mat jump = Mat::zeros(1, img.rows, CV_32F);
for (int i = 0; i < img.rows; i++) {
int jumpCount = 0;
for (int j = 0; j < img.cols - 1; j++) {
if (img.at(i, j) != img.at(i, j + 1)) jumpCount++;
if (img.at(i, j) == 255) {
whiteCount++;
}
}
jump.at(i) = (float)jumpCount;
}
int iCount = 0;
for (int i = 0; i < img.rows; i++) {
fJump.push_back(jump.at(i));
if (jump.at(i) >= 16 && jump.at(i) <= 45) {
//車牌字符滿足一定跳變條件
iCount++;
}
}
這樣的不是車牌
if (iCount * 1.0 / img.rows <= 0.40) {
//滿足條件的跳變的行數(shù)也要在一定的閾值內(nèi)
return false;
}
//不滿足車牌的條件
if (whiteCount * 1.0 / (img.rows * img.cols) < 0.15 ||
whiteCount * 1.0 / (img.rows * img.cols) > 0.50) {
return false;
}
for (int i = 0; i < img.rows; i++) {
if (jump.at(i) <= x) {
for (int j = 0; j < img.cols; j++) {
img.at(i, j) = 0;
}
}
}
return true;
}
總結(jié)
最后回顧一下整體的處理流程,首先是對(duì)車牌圖像進(jìn)行灰度化,然后根據(jù)車牌的不同顏色來進(jìn)行不同的二值化處理。二值化完后首先去除柳釘,然后進(jìn)行取輪廓操作。
取輪廓操作以后,在所有的輪廓中根據(jù)先驗(yàn)知識(shí),找到代表城市的字符,也就是“蘇A”中“A”的位置,根據(jù)“A”的位置來反推“蘇”的位置。
最后將找到的這些輪廓依次排序,從左到右依次選擇6個(gè),和第一個(gè)的中文字符組成7個(gè)字符的圖塊數(shù)組,輸入到下一步字符識(shí)別模塊中進(jìn)行處理。
整個(gè)字符分割流程就到此結(jié)束了,還是比較簡(jiǎn)單的。其中的中文字符位置的確定使用了“先驗(yàn)知識(shí)”這種方法。這種方法在面對(duì)固定已知場(chǎng)景中是較好的方法,但是面對(duì)特殊情況時(shí)就可能會(huì)有不太好的效果,因此要根據(jù)具體情況來權(quán)衡。
總結(jié)
以上是生活随笔為你收集整理的java车牌识别字符分割_车牌识别之字符分割的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 打印机打印第一页的问题
- 下一篇: 信息安全官谁:逼近的挑战,你准备好了吗?