上圖展示了算法的處理效果,算法能夠自動(dòng)的識(shí)別到LCD屏幕上面的數(shù)字,這在現(xiàn)實(shí)場(chǎng)景中具有很大的實(shí)際應(yīng)用價(jià)值。下面我們將對(duì)它的實(shí)現(xiàn)細(xì)節(jié)進(jìn)行詳細(xì)解析。
對(duì)于數(shù)字識(shí)別這個(gè)任務(wù)而言,它并不是一個(gè)新的研究方向,很久之前就有很多的學(xué)者們?cè)陉P(guān)注這個(gè)問題,并提出了一些可行的解決方案,本小節(jié)我們將對(duì)這些方案進(jìn)行簡(jiǎn)單的總結(jié)。
在現(xiàn)實(shí)生活中,我們經(jīng)常會(huì)看到各種各樣的LCD屏幕,小到我們的MP3,大到廣場(chǎng)中的電視等,隨著各種應(yīng)用的不斷出現(xiàn),LCD屏幕頻繁的出現(xiàn)在我們現(xiàn)實(shí)生活中的多個(gè)場(chǎng)景中,而快速、準(zhǔn)確的識(shí)別出LCD上面的數(shù)字就成為了一個(gè)新的剛需,這樣可以極大的節(jié)約人力和物力成本,下面將對(duì)LCD屏幕數(shù)字識(shí)別的原理進(jìn)行說明,知其然不許知其所以然。
即,我們只對(duì)0-9這10個(gè)數(shù)字的組合比較感興趣,這其實(shí)就是我們常用的數(shù)字,我們可以通過它們組合成任意一個(gè)數(shù)字?。?!通過上圖我們可以觀察到當(dāng)我們點(diǎn)亮特定段的LED等之后,LCD就能顯示出特定的數(shù)字,那么我們可不可以通過判斷不同段的特征來判斷當(dāng)前的數(shù)字呢,這其實(shí)就是本文的實(shí)現(xiàn)思路?。?!
# coding=utf-8
# 導(dǎo)入一些python包
from imutils.perspective import four_point_transform
from imutils import contours
import imutils
import cv2
# 定義每一個(gè)數(shù)字對(duì)應(yīng)的字段
DIGITS_LOOKUP = {
(1, 1, 1, 0, 1, 1, 1): 0,
(0, 0, 1, 0, 0, 1, 0): 1,
(1, 0, 1, 1, 1, 1, 0): 2,
(1, 0, 1, 1, 0, 1, 1): 3,
(0, 1, 1, 1, 0, 1, 0): 4,
(1, 1, 0, 1, 0, 1, 1): 5,
(1, 1, 0, 1, 1, 1, 1): 6,
(1, 0, 1, 0, 0, 1, 0): 7,
(1, 1, 1, 1, 1, 1, 1): 8,
(1, 1, 1, 1, 0, 1, 1): 9
}
# 讀取輸入圖片
image = cv2.imread("example.jpg")
# 將輸入圖片裁剪到固定大小
image = imutils.resize(image, height=500)
# 將輸入轉(zhuǎn)換為灰度圖片
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 進(jìn)行高斯模糊操作
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
# 執(zhí)行邊緣檢測(cè)
edged = cv2.Canny(blurred, 50, 200, 255)
cv2.imwrite('edge.png', edged)
# 在邊緣檢測(cè)map中發(fā)現(xiàn)輪廓
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
# 根據(jù)大小對(duì)這些輪廓進(jìn)行排序
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
displayCnt = None
# 循環(huán)遍歷所有的輪廓
for c in cnts:
# 對(duì)輪廓進(jìn)行近似
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.02 * peri, True)
# 如果當(dāng)前的輪廓有4個(gè)頂點(diǎn),我們返回這個(gè)結(jié)果,即LCD所在的位置
if len(approx) == 4:
displayCnt = approx
break
# 應(yīng)用視角變換到LCD屏幕上
warped = four_point_transform(gray, displayCnt.reshape(4, 2))
cv2.imwrite('warped.png', warped)
output = four_point_transform(image, displayCnt.reshape(4, 2))
# 使用閾值進(jìn)行二值化
thresh = cv2.threshold(warped, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
cv2.imwrite('thresh1.png', thresh)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (1, 5))
# 使用形態(tài)學(xué)操作進(jìn)行處理
thresh = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
cv2.imwrite('thresh2.png', thresh)
# 在閾值圖像中查找輪廓,然后初始化數(shù)字輪廓列表
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
digitCnts = []
# 循環(huán)遍歷所有的候選區(qū)域
for c in cnts:
# 計(jì)算輪廓的邊界框
(x, y, w, h) = cv2.boundingRect(c)
# 如果當(dāng)前的這個(gè)輪廓區(qū)域足夠大,它一定是一個(gè)數(shù)字區(qū)域
if w >= 15 and (h >= 30 and h = 40):
digitCnts.append(c)
# 從左到右對(duì)這些輪廓進(jìn)行排序
digitCnts = contours.sort_contours(digitCnts, method="left-to-right")[0]
digits = []
# 循環(huán)處理每一個(gè)數(shù)字
i = 0
for c in digitCnts:
# 獲取ROI區(qū)域
(x, y, w, h) = cv2.boundingRect(c)
roi = thresh[y:y + h, x:x + w]
# 分別計(jì)算每一段的寬度和高度
(roiH, roiW) = roi.shape
(dW, dH) = (int(roiW * 0.25), int(roiH * 0.15))
dHC = int(roiH * 0.05)
# 定義一個(gè)7段數(shù)碼管的集合
segments = [
((0, 0), (w, dH)), # 上
((0, 0), (dW, h // 2)), # 左上
((w - dW, 0), (w, h // 2)), # 右上
((0, (h // 2) - dHC) , (w, (h // 2) + dHC)), # 中間
((0, h // 2), (dW, h)), # 左下
((w - dW, h // 2), (w, h)), # 右下
((0, h - dH), (w, h)) # 下
]
on = [0] * len(segments)
# 循環(huán)遍歷數(shù)碼管中的每一段
for (i, ((xA, yA), (xB, yB))) in enumerate(segments): # 檢測(cè)分割后的ROI區(qū)域,并統(tǒng)計(jì)分割圖中的閾值像素點(diǎn)
segROI = roi[yA:yB, xA:xB]
total = cv2.countNonZero(segROI)
area = (xB - xA) * (yB - yA)
# 如果非零區(qū)域的個(gè)數(shù)大于整個(gè)區(qū)域的一半,則認(rèn)為該段是亮的
if total / float(area) > 0.5:
on[i]= 1
# 進(jìn)行數(shù)字查詢并顯示結(jié)果
digit = DIGITS_LOOKUP[tuple(on)]
digits.append(digit)
cv2.rectangle(output, (x, y), (x + w, y + h), (0, 255, 0), 1)
cv2.putText(output, str(digit), (x - 10, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 255, 0), 2)
# 顯示最終的輸出結(jié)果
print(u"{}{}.{} \u00b0C".format(*digits))
cv2.imshow("Input", image)
cv2.imshow("Output", output)
cv2.waitKey(0)
上圖展示了該算法的運(yùn)行結(jié)果和一些中間結(jié)果。第1行第1列表示的是原始的輸入圖片,它和代碼中的image對(duì)應(yīng),我們需要識(shí)別的是LCD面板上面的34.5;第1行第2列表示的是Canny邊緣檢測(cè)算法的檢測(cè)結(jié)果,它對(duì)應(yīng)于代碼中的edged,通過該圖我們可以發(fā)現(xiàn)Canny邊緣檢測(cè)的結(jié)果中含有我們感興趣的目標(biāo),即中間的LCD;第1行第3列表示的是對(duì)輸入的灰度圖片應(yīng)用視角變換后的結(jié)果,即獲得了LCD屏幕所在的位置,它和代碼中的warped相互對(duì)應(yīng);第2行第1列表示的是對(duì)獲取到的LCD屏幕進(jìn)行二值化后的結(jié)果,它和代碼中的thresh 相互對(duì)應(yīng),由于LCD上面的數(shù)字和背景之間具有較大的差異,因而通過簡(jiǎn)單的二值化我們就可以獲得我們感興趣的目標(biāo)-數(shù)字;第2行第2列表示的是對(duì)二值化結(jié)果進(jìn)行形態(tài)學(xué)操作之后的結(jié)果,它和代碼中的thresh 相互對(duì)應(yīng),我們可以發(fā)現(xiàn)執(zhí)行了形態(tài)學(xué)操作之后的結(jié)果更多平滑,同時(shí)過濾掉很多的噪聲,有利于后續(xù)的識(shí)別。
上圖展示了本算法獲取到的LCD屏幕中的數(shù)字,通過上圖我們可以發(fā)現(xiàn)該算法準(zhǔn)確的獲得了這些數(shù)字的位置信息,有利于后續(xù)的識(shí)別操作。
上圖展示了算法進(jìn)行數(shù)字識(shí)別的實(shí)現(xiàn)細(xì)節(jié)。即通過遍歷每一個(gè)數(shù)字中的7個(gè)段,并統(tǒng)計(jì)該段中非零像素的個(gè)數(shù),當(dāng)其統(tǒng)計(jì)值大于整個(gè)區(qū)域的一半時(shí),認(rèn)為該段是亮的,當(dāng)統(tǒng)計(jì)完所有的這7個(gè)段之后,在預(yù)定義的數(shù)字詞典中進(jìn)行查找,并輸出最終的結(jié)果即可。
通過上面的解析你可能已經(jīng)知道了如何來很好的解決上面這個(gè)問題。細(xì)心的你也許會(huì)發(fā)現(xiàn)上述結(jié)果中輸出的點(diǎn)號(hào)是人為添加上去的,并不是算法自動(dòng)獲取的,而在現(xiàn)實(shí)場(chǎng)景中我們經(jīng)常會(huì)遇到小數(shù)點(diǎn),比如溫度、濕度等,那么我的問題來啦,如何利用算法自動(dòng)獲取圖中的小數(shù)點(diǎn),使得算法最終自動(dòng)輸出34.5的結(jié)果呢,這個(gè)問題留給聰明的你進(jìn)行思考吧?。。。ㄆ鋵?shí)數(shù)碼管是有八段的,第八段就是小數(shù)點(diǎn)的?。。。?/p>
到此這篇關(guān)于Python+Opencv實(shí)現(xiàn)數(shù)字識(shí)別的示例代碼的文章就介紹到這了,更多相關(guān)Opencv 數(shù)字識(shí)別內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!