opencv-python實戰

(一)信用卡數字識別

  • 使用模板匹配進行數字識別

  • 將信用卡里取取來的數字與模板裏的每一個數字進行匹配,得分高者即是該數字

#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)
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章