使用 OpenCV 和 Python 識別數字
本文演示如何使用 OpenCV 和 Python 識別影像中的數字,
在本教程的第一部分,我們將討論什么是七段顯示幕,以及我們如何應用計算機視覺和影像處理操作來識別這些型別的數字(不需要機器學習!)
七段顯示
您可能已經熟悉七段顯示幕,即使您不認識特定術語, 這種顯示的一個很好的例子是您的經典數字鬧鐘:

鬧鐘上的每個數字都由一個七段組件表示,如下所示:

七段顯示幕總共可以呈現 128 種可能的狀態:

我們只對其中的 10 個感興趣——數字 0 到 9:

我們的目標是撰寫 OpenCV 和 Python 代碼來識別影像中的這十個數字狀態中的每一個,
設計OpenCV 數字識別器
我們將使用恒溫器影像作為輸入:

識別的步驟:
步驟 1:定位恒溫器上的 LCD, 這可以使用邊緣檢測來完成,因為塑料外殼和 LCD 之間有足夠的對比度,
步驟2:提取 LCD, 給定一個輸入邊緣圖,我可以找到輪廓并尋找矩形的輪廓——最大的矩形區域應該對應于 LCD, 透視變換會給我一個很好的 LCD 提取,
步驟3:提取數字區域, 一旦我有了 LCD 本身,我就可以專注于提取數字, 由于數字區域和 LCD 背景之間似乎存在對比,我相信閾值和形態操作可以實作這一點,
步驟4:識別數字, 使用 OpenCV 識別實際數字將涉及將數字 ROI 劃分為七個部分, 從那里我可以在閾值影像上應用像素計數來確定給定的片段是“開”還是“關”,
所以看看我們如何使用 OpenCV 和 Python 完成這個四步程序來進行數字識別,繼續閱讀,
使用計算機視覺和 OpenCV 識別數字
讓我們繼續開始這個例子,新建一個檔案,將其命名為 identify_digits.py ,并插入以下代碼:
# import the necessary packages
from imutils.perspective import four_point_transform
from imutils import contours
import imutils
import cv2
# define the dictionary of digit segments so we can identify
# each digit on the thermostat
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
}
匯入我們所需的 Python 包, 引入mutils,這是我的一系列便利函式,可以更輕松地使用 OpenCV + Python, 如果您還沒有安裝 imutils,現在應該花一點時間使用 pip 在您的系統上安裝該軟體包: 使用 OpenCV 和 Python 識別數字
pip install imutils
定義一個名為 DIGITS_LOOKUP 的 Python 字典, 他們對表的關鍵是七段陣列, 陣列中的 1 表示給定的段已打開,零表示該段已關閉, 該值是實際數字本身:0-9,
一旦我們識別了恒溫器顯示幕中的段,我們就可以將陣列傳遞到我們的 DIGITS_LOOKUP 表中并獲得數字值, 作為參考,該詞典使用與上面圖 2 中相同的段順序, 讓我們繼續我們的例子:
# load the example image
image = cv2.imread("example.jpg")
# pre-process the image by resizing it, converting it to
# graycale, blurring it, and computing an edge map
image = imutils.resize(image, height=500)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
edged = cv2.Canny(blurred, 50, 200, 255)
加載我們的影像,
然后我們通過以下方式預處理影像:-
- 調整大小,
- 將影像轉換為灰度,
- 使用 5×5 內核應用高斯模糊以減少高頻噪聲,
- 通過 Canny 邊緣檢測器計算邊緣圖,
應用這些預處理步驟后,我們的邊緣圖如下所示:

注意 LCD 的輪廓是如何清晰可見的——這完成了步驟 #1, 我們現在可以繼續第 2 步,提取 LCD 本身:
# find contours in the edge map, then sort them by their
# size in descending order
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
displayCnt = None
# loop over the contours
for c in cnts:
# approximate the contour
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.02 * peri, True)
# if the contour has four vertices, then we have found
# the thermostat display
if len(approx) == 4:
displayCnt = approx
break
為了找到 LCD 區域,我們需要提取邊緣圖中區域的輪廓(即輪廓),
然后我們按面積對等高線進行排序,確保將面積較大的等高線放在串列的前面,
給定我們排序的輪廓串列,逐個回圈它們并應用輪廓近似,
如果我們的近似輪廓有四個頂點,那么我們假設我們已經找到了恒溫器顯示, 這是一個合理的假設,因為我們輸入影像中最大的矩形區域應該是 LCD 本身,
獲得四個頂點后,我們可以通過四點透視變換提取 LCD:
# extract the thermostat display, apply a perspective transform
# to it
warped = four_point_transform(gray, displayCnt.reshape(4, 2))
output = four_point_transform(image, displayCnt.reshape(4, 2))
應用這種透視變換為我們提供了一個自上而下的 LCD 鳥瞰圖:

獲得 LCD 的這個視圖滿足第 2 步——我們現在準備從 LCD 中提取數字:
# threshold the warped image, then apply a series of morphological
# operations to cleanup the thresholded image
thresh = cv2.threshold(warped, 0, 255,
cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (1, 5))
thresh = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
為了獲得數字本身,我們需要對扭曲影像進行閾值處理,以在較亮的背景(即 LCD 顯示屏的背景)中顯示暗區(即數字):

然后我們應用一系列形態學操作來清理閾值影像:

現在我們有一個很好的分割影像,我們再次需要應用輪廓過濾,只是這次我們正在尋找實際的數字:
# find contours in the thresholded image, then initialize the
# digit contours lists
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
digitCnts = []
# loop over the digit area candidates
for c in cnts:
# compute the bounding box of the contour
(x, y, w, h) = cv2.boundingRect(c)
# if the contour is sufficiently large, it must be a digit
if w >= 15 and (h >= 30 and h <= 40):
digitCnts.append(c)
為此,我們在閾值影像中找到輪廓, 初始化digitsCnts 串列——這個串列將存盤數字本身的輪廓,
在每個輪廓上回圈,
對于每個輪廓,我們計算邊界框,確保寬度和高度是可接受的大小,如果是,則更新digitsCnts 串列,
如果我們回圈遍歷digitsCnts內部的輪廓并在影像上繪制邊界框,結果將如下所示:

果然,我們在液晶顯示屏上找到了數字! 最后一步是實際識別每個數字:
# sort the contours from left-to-right, then initialize the
# actual digits themselves
digitCnts = contours.sort_contours(digitCnts,
method="left-to-right")[0]
digits = []
在這里,我們只是根據 (x, y) 坐標從左到右對數字輪廓進行排序,
這個排序步驟是必要的,因為不能保證輪廓已經從左到右排序(與我們讀取數字的方向相同),
接下來是實際的數字識別程序:
# loop over each of the digits
for c in digitCnts:
# extract the digit ROI
(x, y, w, h) = cv2.boundingRect(c)
roi = thresh[y:y + h, x:x + w]
# compute the width and height of each of the 7 segments
# we are going to examine
(roiH, roiW) = roi.shape
(dW, dH) = (int(roiW * 0.25), int(roiH * 0.15))
dHC = int(roiH * 0.05)
# define the set of 7 segments
segments = [
((0, 0), (w, dH)), # top
((0, 0), (dW, h // 2)), # top-left
((w - dW, 0), (w, h // 2)), # top-right
((0, (h // 2) - dHC) , (w, (h // 2) + dHC)), # center
((0, h // 2), (dW, h)), # bottom-left
((w - dW, h // 2), (w, h)), # bottom-right
((0, h - dH), (w, h)) # bottom
]
on = [0] * len(segments)
遍歷每個數字輪廓,
對于這些區域中的每一個,我們計算邊界框并提取數字 ROI,
我在下面包含了每個數字 ROI 的 GIF 影片:

給定數字 ROI,我們現在需要定位和提取數字顯示的七個部分,
根據 ROI 尺寸計算每個段的大致寬度和高度, 然后我們定義一個 (x, y) 坐標串列,這些坐標對應七個線段, 此串列遵循與上面圖 2 相同的段順序, 這是一個示例 GIF 影片,它在正在調查的當前片段上繪制一個綠色框:

最后,初始化我們的 on 串列——該串列中的值 1 表示給定的段是“打開”的,而值為零表示該段是“關閉的”, 給定七個顯示段的 (x, y) 坐標,識別一個段是打開還是關閉是相當容易的:
# loop over the segments
for (i, ((xA, yA), (xB, yB))) in enumerate(segments):
# extract the segment ROI, count the total number of
# thresholded pixels in the segment, and then compute
# the area of the segment
segROI = roi[yA:yB, xA:xB]
total = cv2.countNonZero(segROI)
area = (xB - xA) * (yB - yA)
# if the total number of non-zero pixels is greater than
# 50% of the area, mark the segment as "on"
if total / float(area) > 0.5:
on[i]= 1
# lookup the digit and draw it on the image
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)
我們開始回圈遍歷每個線段的 (x, y) 坐標,
我們提取片段 ROI,然后計算非零像素數(即片段中“開啟”的像素數),
如果非零像素與段總面積的比率大于 50%,那么我們可以假設該段是“on”并相應地更新我們的 on 串列, 在回圈七段之后,我們可以將串列傳遞給 DIGITS_LOOKUP 以獲取數字本身,
然后我們在數字周圍繪制一個邊界框并在輸出影像上顯示數字, 最后,我們的最后一個代碼塊將數字列印到我們的螢屏上并顯示輸出影像:
# display the digits
print(u"{}{}.{} \u00b0C".format(*digits))
cv2.imshow("Input", image)
cv2.imshow("Output", output)
cv2.waitKey(0)
請注意我們如何使用 Python 和 OpenCV 正確識別 LCD 螢屏上的數字:

總結
在今天的博客文章中,我演示了如何利用 OpenCV 和 Python 來識別影像中的數字,
這種方法專門用于七段顯示幕(即您通常會在數字鬧鐘上看到的數字顯示幕),
通過提取七個段中的每一個并應用基本的閾值和形態學操作,我們可以確定哪些段是“開”的,哪些是“關”的,
從那里,我們可以在 Python 字典資料結構中查找開/關段以快速確定實際數字——無需機器學習!
正如我在這篇博文的開頭提到的,應用計算機視覺來識別恒溫器影像中的數字往往會使問題本身過于復雜——使用資料記錄溫度計會更可靠,并且需要的作業量要少得多,
我希望你喜歡今天的博文!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/353251.html
標籤:AI
