Coding/Image

Bank Check OCR

linguana 2021. 4. 19. 12:35

 

Bank check OCR with OpenCV and Python (Part I) - PyImageSearch

 

Bank check OCR with OpenCV and Python (Part I) - PyImageSearch

Learn how to detect and recognize bank check and routing numbers using OCR, Python, and computer vision + image processing (Part I).

www.pyimagesearch.com

※ 주 의: 2017년에 작성된 포스팅


STEP 1: 데이터 준비

1. 필요한 모듈 임포트

bank_check_ocr.py

2. 글자 추출 함수 정의

bank_check_ocr.py

파라미터 (이미지, 글자 컨투어, 최소 너비, 최소 높이)

파이썬에는 자바와 달리 hasNext 같은 메소드 없음, 그래서 예외처리로 다뤄야 함.

3. 반복문 시작

bank_check_ocr.py

4. 최소 기준 확인 및 roi & loc 추출

bank_check_ocr.py

5. 글자 아니면 기호라고 간주하고 처리

bank_check_ocr.py

np.inf == infinite

6. 반복문 탈출 (roi & loc 반환)

bank_check_ocr.py

7. 아규먼트 파싱

bank_check_ocr.py

8. 정답지 만들기 part 1

bank_check_ocr.py

9. 정답지 만들기 part 2

bank_check_ocr.py

읽어들이고 전처리 실시 (흑백 전환, 크기 조정, 이진화)

결과:

<그림 1> 정답지 이미지


STEP 2: 바운딩 박스 추출

10. 바운딩박스 그리는 간단한 방식

bank_check_ocr.py

결과:

<그림 2> 간단한 추출 방식, 정확하지 않음

11. 바운딩박스 그리는 더 정교한 방식

bank_check_ocr.py

코드 초반에 정의한 extract_digits_and_symbols 함수 활용함

결과:

<그림 3> 더 정교한 방식, 정확함


cmd로 실행하기

cmd


코드 정리:

더보기
# import the necessary packages
from skimage.segmentation import clear_border
from imutils import contours
import numpy as np
import argparse
import imutils
import cv2

def extract_digits_and_symbols(image, charCnts, minW=5, minH=15):
	# grab the internal Python iterator for the list of character
	# contours, then  initialize the character ROI and location
	# lists, respectively
	charIter = charCnts.__iter__()
	rois = []
	locs = []
    
    # keep looping over the character contours until we reach the end
	# of the list
	while True:
		try:
			# grab the next character contour from the list, compute
			# its bounding box, and initialize the ROI
			c = next(charIter)
			(cX, cY, cW, cH) = cv2.boundingRect(c)
			roi = None
            
			# check to see if the width and height are sufficiently
			# large, indicating that we have found a digit
			if cW >= minW and cH >= minH:
				# extract the ROI
				roi = image[cY:cY + cH, cX:cX + cW]
				rois.append(roi)
				locs.append((cX, cY, cX + cW, cY + cH))
                
			# otherwise, we are examining one of the special symbols
			else:
				# MICR symbols include three separate parts, so we
				# need to grab the next two parts from our iterator,
				# followed by initializing the bounding box
				# coordinates for the symbol
				parts = [c, next(charIter), next(charIter)]
				(sXA, sYA, sXB, sYB) = (np.inf, np.inf, -np.inf,
					-np.inf)
                    
				# loop over the parts
				for p in parts:
					# compute the bounding box for the part, then
					# update our bookkeeping variables
					(pX, pY, pW, pH) = cv2.boundingRect(p)
					sXA = min(sXA, pX)
					sYA = min(sYA, pY)
					sXB = max(sXB, pX + pW)
					sYB = max(sYB, pY + pH)
                    
				# extract the ROI
				roi = image[sYA:sYB, sXA:sXB]
				rois.append(roi)
				locs.append((sXA, sYA, sXB, sYB))
                
		# we have reached the end of the iterator; gracefully break
		# from the loop
		except StopIteration:
			break
            
	# return a tuple of the ROIs and locations
	return (rois, locs)
                
   # construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True,
	help="path to input image")
ap.add_argument("-r", "--reference", required=True,
	help="path to reference MICR E-13B font")
args = vars(ap.parse_args())

# initialize the list of reference character names, in the same
# order as they appear in the reference image where the digits
# their names and:
# T = Transit (delimit bank branch routing transit #)
# U = On-us (delimit customer account number)
# A = Amount (delimit transaction amount)
# D = Dash (delimit parts of numbers, such as routing or account)
charNames = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0",
	"T", "U", "A", "D"]
    
# load the reference MICR image from disk, convert it to grayscale,
# and threshold it, such that the digits appear as *white* on a
# *black* background
ref = cv2.imread(args["reference"])
ref = cv2.cvtColor(ref, cv2.COLOR_BGR2GRAY)
ref = imutils.resize(ref, width=400)
ref = cv2.threshold(ref, 0, 255, cv2.THRESH_BINARY_INV |
	cv2.THRESH_OTSU)[1]
    
# find contours in the MICR image (i.e,. the outlines of the
# characters) and sort them from left to right
refCnts = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL,
	cv2.CHAIN_APPROX_SIMPLE)
refCnts = imutils.grab_contours(refCnts)
refCnts = contours.sort_contours(refCnts, method="left-to-right")[0]

# create a clone of the original image so we can draw on it
clone = np.dstack([ref.copy()] * 3)

# loop over the (sorted) contours
for c in refCnts:
	# compute the bounding box of the contour and draw it on our
	# image
	(x, y, w, h) = cv2.boundingRect(c)
	cv2.rectangle(clone, (x, y), (x + w, y + h), (0, 255, 0), 2)
    
# show the output of applying the simple contour method
cv2.imshow("Simple Method", clone)

# extract the digits and symbols from the list of contours, then
# initialize a dictionary to map the character name to the ROI
(refROIs, refLocs) = extract_digits_and_symbols(ref, refCnts,
	minW=10, minH=20)
chars = {}

# re-initialize the clone image so we can draw on it again
clone = np.dstack([ref.copy()] * 3)

# loop over the reference ROIs and locations
for (name, roi, loc) in zip(charNames, refROIs, refLocs):
	# draw a bounding box surrounding the character on the output
	# image
	(xA, yA, xB, yB) = loc
	cv2.rectangle(clone, (xA, yA), (xB, yB), (0, 255, 0), 2)
    
	# resize the ROI to a fixed size, then update the characters
	# dictionary, mapping the character name to the ROI
	roi = cv2.resize(roi, (36, 36)) 
	chars[name] = roi
    
	# display the character ROI to our screen
	cv2.imshow("Char", roi)
	cv2.waitKey(0)
    
# show the output of our better method
cv2.imshow("Better Method", clone)
cv2.waitKey(0)
cv2.waitKey(0)

# cmd
# $ python bank_check_ocr.py --image example_check.png \
#	--reference micr_e13b_reference.png