(一)信用卡數字識別
-
使用模板匹配進行數字識別
-
將信用卡里取取來的數字與模板裏的每一個數字進行匹配,得分高者即是該數字
#python3 ocr_template_match.py -i images/credit_card_01.png -t images/ocr_a_reference.png
# 導入工具包
from imutils import contours
import numpy as np
import argparse
import cv2
import myutils
# 設置參數
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True,
help="path to input image")
ap.add_argument("-t", "--template", required=True,
help="path to template OCR-A image")
args = vars(ap.parse_args())
# 指定信用卡類型
FIRST_NUMBER = {
"3": "American Express",
"4": "Visa",
"5": "MasterCard",
"6": "Discover Card"
}
# 繪圖展示
def cv_show(name,img):
cv2.imshow(name, img)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 讀取一個模板圖像
img = cv2.imread(args["template"])
cv_show('img',img)
# 灰度圖
ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv_show('ref',ref)
# 二值圖像
ref = cv2.threshold(ref, 10, 255, cv2.THRESH_BINARY_INV)[1]
cv_show('ref2',ref)
# 計算輪廓
#cv2.findContours()函數接受的參數爲二值圖,即黑白的(不是灰度圖),cv2.RETR_EXTERNAL只檢測外輪廓,cv2.CHAIN_APPROX_SIMPLE只保留終點座標
#返回的list中每個元素都是圖像中的一個輪廓
_,refCnts, hierarchy = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img,refCnts,-1,(0,0,255),3)
cv_show('img',img)
print (np.array(refCnts).shape)
refCnts = myutils.sort_contours(refCnts, method="left-to-right")[0] #排序,從左到右,從上到下
digits = {}
# 遍歷每一個輪廓
for (i, c) in enumerate(refCnts):
# 計算外接矩形並且resize成合適大小
(x, y, w, h) = cv2.boundingRect(c)
roi = ref[y:y + h, x:x + w]
roi = cv2.resize(roi, (57, 88))
# 每一個數字對應每一個模板
digits[i] = roi
# 初始化卷積核
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
#讀取輸入圖像,預處理
image = cv2.imread(args["image"])
cv_show('image',image)
image = myutils.resize(image, width=300)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv_show('gray',gray)
#禮帽操作,突出更明亮的區域
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel)
cv_show('tophat',tophat)
#
gradX = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0, #ksize=-1相當於用3*3的
ksize=-1)
gradX = np.absolute(gradX)
(minVal, maxVal) = (np.min(gradX), np.max(gradX))
gradX = (255 * ((gradX - minVal) / (maxVal - minVal)))
gradX = gradX.astype("uint8")
print (np.array(gradX).shape)
cv_show('gradX',gradX)
#通過閉操作(先膨脹,再腐蝕)將數字連在一起
gradX = cv2.morphologyEx(gradX, cv2.MORPH_CLOSE, rectKernel)
cv_show('gradX',gradX)
#THRESH_OTSU會自動尋找合適的閾值,適合雙峯,需把閾值參數設置爲0
thresh = cv2.threshold(gradX, 0, 255,
cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
cv_show('thresh',thresh)
#再來一個閉操作
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel) #再來一個閉操作
cv_show('thresh',thresh)
# 計算輪廓
_,threshCnts, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = threshCnts
cur_img = image.copy()
cv2.drawContours(cur_img,cnts,-1,(0,0,255),3)
cv_show('img2',cur_img)
locs = []
# 遍歷輪廓
for (i, c) in enumerate(cnts):
# 計算矩形
(x, y, w, h) = cv2.boundingRect(c)
ar = w / float(h)
# 選擇合適的區域,根據實際任務來,這裏的基本都是四個數字一組
if ar > 2.5 and ar < 4.0:
if (w > 40 and w < 55) and (h > 10 and h < 20):
#符合的留下來
locs.append((x, y, w, h))
# 將符合的輪廓從左到右排序
locs = sorted(locs, key=lambda x:x[0])
output = []
# 遍歷每一個輪廓中的數字
for (i, (gX, gY, gW, gH)) in enumerate(locs):
# initialize the list of group digits
groupOutput = []
# 根據座標提取每一個組
group = gray[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5]
cv_show('group',group)
# 預處理
group = cv2.threshold(group, 0, 255,
cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
cv_show('group',group)
# 計算每一組的輪廓
_,digitCnts,hierarchy = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
digitCnts = contours.sort_contours(digitCnts,
method="left-to-right")[0]
# 計算每一組中的每一個數值
for c in digitCnts:
# 找到當前數值的輪廓,resize成合適的的大小
(x, y, w, h) = cv2.boundingRect(c)
roi = group[y:y + h, x:x + w]
roi = cv2.resize(roi, (57, 88))
cv_show('roi',roi)
# 計算匹配得分
scores = []
# 在模板中計算每一個得分
for (digit, digitROI) in digits.items():
# 模板匹配
result = cv2.matchTemplate(roi, digitROI,
cv2.TM_CCOEFF)
(_, score, _, _) = cv2.minMaxLoc(result)
scores.append(score)
# 得到最合適的數字
groupOutput.append(str(np.argmax(scores)))
# 畫出來
cv2.rectangle(image, (gX - 5, gY - 5),
(gX + gW + 5, gY + gH + 5), (0, 0, 255), 1)
cv2.putText(image, "".join(groupOutput), (gX, gY - 15),
cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)
# 得到結果
output.extend(groupOutput)
# 打印結果
print("Credit Card Type: {}".format(FIRST_NUMBER[output[0]]))
print("Credit Card #: {}".format("".join(output)))
cv2.imshow("Image", image)
cv2.waitKey(0)
(二)答題卡識別
-
找到選項的輪廓,從上到下,從左到右依次掃描,根據閾值判斷是否選中
#python3 get_answer.py -i images/example_test.png #導入工具包 import numpy as np import argparse import imutils import cv2 # 設置參數 ap = argparse.ArgumentParser() ap.add_argument("-i", "--image", required=True, help="path to the input image") args = vars(ap.parse_args()) # 正確答案 ANSWER_KEY = {0: 1, 1: 4, 2: 0, 3: 3, 4: 1} def order_points(pts): # 一共4個座標點 rect = np.zeros((4, 2), dtype = "float32") # 按順序找到對應座標0123分別是 左上,右上,右下,左下 # 計算左上,右下 s = pts.sum(axis = 1) rect[0] = pts[np.argmin(s)] rect[2] = pts[np.argmax(s)] # 計算右上和左下 diff = np.diff(pts, axis = 1) rect[1] = pts[np.argmin(diff)] rect[3] = pts[np.argmax(diff)] return rect def four_point_transform(image, pts): # 獲取輸入座標點 rect = order_points(pts) (tl, tr, br, bl) = rect # 計算輸入的w和h值 widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2)) widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2)) maxWidth = max(int(widthA), int(widthB)) heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2)) heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2)) maxHeight = max(int(heightA), int(heightB)) # 變換後對應座標位置 dst = np.array([ [0, 0], [maxWidth - 1, 0], [maxWidth - 1, maxHeight - 1], [0, maxHeight - 1]], dtype = "float32") # 計算變換矩陣 M = cv2.getPerspectiveTransform(rect, dst) warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight)) # 返回變換後結果 return warped def sort_contours(cnts, method="left-to-right"): reverse = False i = 0 if method == "right-to-left" or method == "bottom-to-top": reverse = True if method == "top-to-bottom" or method == "bottom-to-top": i = 1 boundingBoxes = [cv2.boundingRect(c) for c in cnts] (cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes), key=lambda b: b[1][i], reverse=reverse)) return cnts, boundingBoxes def cv_show(name,img): cv2.imshow(name, img) cv2.waitKey(0) cv2.destroyAllWindows() # 預處理 image = cv2.imread(args["image"]) contours_img = image.copy() gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) blurred = cv2.GaussianBlur(gray, (5, 5), 0) cv_show('blurred',blurred) edged = cv2.Canny(blurred, 75, 200) cv_show('edged',edged) # 輪廓檢測 cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1] cv2.drawContours(contours_img,cnts,-1,(0,0,255),3) cv_show('contours_img',contours_img) docCnt = None # 確保檢測到了 if len(cnts) > 0: # 根據輪廓大小進行排序 cnts = sorted(cnts, key=cv2.contourArea, reverse=True) # 遍歷每一個輪廓 for c in cnts: # 近似 peri = cv2.arcLength(c, True) approx = cv2.approxPolyDP(c, 0.02 * peri, True) # 準備做透視變換 if len(approx) == 4: docCnt = approx break # 執行透視變換 warped = four_point_transform(gray, docCnt.reshape(4, 2)) cv_show('warped',warped) # Otsu's 閾值處理 thresh = cv2.threshold(warped, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1] cv_show('thresh',thresh) thresh_Contours = thresh.copy() # 找到每一個圓圈輪廓 cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1] cv2.drawContours(thresh_Contours,cnts,-1,(0,0,255),3) cv_show('thresh_Contours',thresh_Contours) questionCnts = [] # 遍歷 for c in cnts: # 計算比例和大小 (x, y, w, h) = cv2.boundingRect(c) ar = w / float(h) # 根據實際情況指定標準 if w >= 20 and h >= 20 and ar >= 0.9 and ar <= 1.1: questionCnts.append(c) # 按照從上到下進行排序 questionCnts = sort_contours(questionCnts, method="top-to-bottom")[0] correct = 0 # 每排有5個選項 for (q, i) in enumerate(np.arange(0, len(questionCnts), 5)): # 排序 cnts = sort_contours(questionCnts[i:i + 5])[0] bubbled = None # 遍歷每一個結果 for (j, c) in enumerate(cnts): # 使用mask來判斷結果 mask = np.zeros(thresh.shape, dtype="uint8") cv2.drawContours(mask, [c], -1, 255, -1) #-1表示填充 cv_show('mask',mask) # 通過計算非零點數量來算是否選擇這個答案 mask = cv2.bitwise_and(thresh, thresh, mask=mask) total = cv2.countNonZero(mask) # 通過閾值判斷 if bubbled is None or total > bubbled[0]: bubbled = (total, j) # 對比正確答案 color = (0, 0, 255) k = ANSWER_KEY[q] # 判斷正確 if k == bubbled[1]: color = (0, 255, 0) correct += 1 # 繪圖 cv2.drawContours(warped, [cnts[k]], -1, color, 3) score = (correct / 5.0) * 100 print("[INFO] score: {:.2f}%".format(score)) cv2.putText(warped, "{:.2f}%".format(score), (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2) cv2.imshow("Original", image) cv2.imshow("Exam", warped) cv2.waitKey(0)
(三)車道線檢測
- 利用掩膜摳出車道線區域,再利用霍夫變換找出直線,再進行左右直線的擬合
import cv2 import numpy as np from moviepy.editor import VideoFileClip # 高斯濾波核大小 blur_ksize = 5 # Canny邊緣檢測高低閾值 canny_lth = 50 canny_hth = 150 # 霍夫變換參數 rho = 1 theta = np.pi / 180 threshold = 15 min_line_len = 40 max_line_gap = 20 def process_an_image(img): # 1. 灰度化、濾波和Canny gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) blur_gray = cv2.GaussianBlur(gray, (blur_ksize, blur_ksize), 1) edges = cv2.Canny(blur_gray, canny_lth, canny_hth) # 2. 標記四個座標點用於ROI截取 rows, cols = edges.shape points = np.array([[(0, rows), (460, 325), (520, 325), (cols, rows)]]) # [[[0 540], [460 325], [520 325], [960 540]]] roi_edges = roi_mask(edges, points) # 3. 霍夫直線提取 drawing, lines = hough_lines(roi_edges, rho, theta, threshold, min_line_len, max_line_gap) # 4. 車道擬合計算 draw_lanes(drawing, lines) # 5. 最終將結果合在原圖上 result = cv2.addWeighted(img, 0.9, drawing, 0.2, 0) return result def roi_mask(img, corner_points): # 創建掩膜 mask = np.zeros_like(img) cv2.fillPoly(mask, corner_points, 255) masked_img = cv2.bitwise_and(img, mask) return masked_img def hough_lines(img, rho, theta, threshold, min_line_len, max_line_gap): # 統計概率霍夫直線變換 lines = cv2.HoughLinesP(img, rho, theta, threshold, minLineLength=min_line_len, maxLineGap=max_line_gap) # 新建一副空白畫布 drawing = np.zeros((img.shape[0], img.shape[1], 3), dtype=np.uint8) # 畫出直線檢測結果 # draw_lines(drawing, lines) return drawing, lines def draw_lines(img, lines, color=[0, 0, 255], thickness=1): for line in lines: for x1, y1, x2, y2 in line: cv2.line(img, (x1, y1), (x2, y2), color, thickness) def draw_lanes(img, lines, color=[255, 0, 0], thickness=8): # a. 劃分左右車道 left_lines, right_lines = [], [] for line in lines: for x1, y1, x2, y2 in line: k = (y2 - y1) / (x2 - x1) if k < 0: left_lines.append(line) else: right_lines.append(line) if (len(left_lines) <= 0 or len(right_lines) <= 0): return # b. 清理異常數據 clean_lines(left_lines, 0.1) clean_lines(right_lines, 0.1) # c. 得到左右車道線點的集合,擬合直線 left_points = [(x1, y1) for line in left_lines for x1, y1, x2, y2 in line] left_points = left_points + [(x2, y2) for line in left_lines for x1, y1, x2, y2 in line] right_points = [(x1, y1) for line in right_lines for x1, y1, x2, y2 in line] right_points = right_points + \ [(x2, y2) for line in right_lines for x1, y1, x2, y2 in line] left_results = least_squares_fit(left_points, 325, img.shape[0]) right_results = least_squares_fit(right_points, 325, img.shape[0]) # 注意這裏點的順序 vtxs = np.array( [[left_results[1], left_results[0], right_results[0], right_results[1]]]) # d.填充車道區域 cv2.fillPoly(img, vtxs, (0, 255, 0)) # 或者只畫車道線 # cv2.line(img, left_results[0], left_results[1], (0, 255, 0), thickness) # cv2.line(img, right_results[0], right_results[1], (0, 255, 0), thickness) def clean_lines(lines, threshold): # 迭代計算斜率均值,排除掉與差值差異較大的數據 slope = [(y2 - y1) / (x2 - x1) for line in lines for x1, y1, x2, y2 in line] while len(lines) > 0: mean = np.mean(slope) diff = [abs(s - mean) for s in slope] idx = np.argmax(diff) if diff[idx] > threshold: slope.pop(idx) lines.pop(idx) else: break def least_squares_fit(point_list, ymin, ymax): # 最小二乘法擬合 x = [p[0] for p in point_list] y = [p[1] for p in point_list] # polyfit第三個參數爲擬合多項式的階數,所以1代表線性 fit = np.polyfit(y, x, 1) fit_fn = np.poly1d(fit) # 獲取擬合的結果 xmin = int(fit_fn(ymin)) xmax = int(fit_fn(ymax)) return [(xmin, ymin), (xmax, ymax)] if __name__ == "__main__": output = 'test_videos/yellow_lane_mark.mp4' clip = VideoFileClip("test_videos/yellow_lane.mp4") out_clip = clip.fl_image(process_an_image) out_clip.write_videofile(output, audio=False)