天天看點

OCR文本掃描 輪廓檢測 透視變換-唐宇迪筆記OCR文本掃描 輪廓檢測 透視變換

OCR文本掃描 輪廓檢測 透視變換

本項目和源代碼來自唐宇迪opencv項目實戰

OCR文本識别

什麼是OCR,百度裡的定義是:

OCR (Optical Character Recognition,光學字元識别)是指電子裝置(例如掃描器或數位相機)檢查紙上列印的字元,通過檢測暗、亮的模式确定其形狀,然後用字元識别方法将形狀翻譯成計算機文字的過程;即,針對印刷體字元,采用光學的方式将紙質文檔中的文字轉換成為黑白點陣的圖像檔案,并通過識别軟體将圖像中的文字轉換成文本格式,供文字處理軟體進一步編輯加工的技術。

輸入一張圖,然後掃描出其中文字

OCR文本掃描 輪廓檢測 透視變換-唐宇迪筆記OCR文本掃描 輪廓檢測 透視變換
OCR文本掃描 輪廓檢測 透視變換-唐宇迪筆記OCR文本掃描 輪廓檢測 透視變換

算法步驟

1、導入圖檔

image = cv2.imread('receipt.jpg')
           

2、圖檔與處理

resize 操作

resize函數的作用是按照原圖像相同長寬比,當給定長(height)或者寬(width)時将原圖resize成與原圖像同比例的大小。

至于這一步為什麼要進行resize操作,我分析

1 實驗使用的圖像多為手機拍攝的圖檔,圖檔大小至少為3500*4000,在imshow(),在螢幕顯示并不能像是完整的圖像,不利于觀察。

該函數的傳回值是resize後的圖檔;參數是原圖像和指定的變換後的width或height值。

接着對圖像進一步操作

Canny邊緣檢測步驟

1、canny邊緣檢測
  1. 使用高斯濾波器,以平滑圖像,濾除噪聲。
               
  2. 計算圖像中每個像素點的梯度強度和方向。
               
  3. 應用非極大值(Non-Maximum Suppression)抑制,以消除邊緣檢測帶來的雜散響應。
               
  4. 應用雙門檻值(Double-Threshold)檢測來确定真實的和潛在的邊緣。
               
  5. 通過抑制孤立的弱邊緣最終完成邊緣檢測。
               

1、高斯濾波器:通過高斯分布去除噪點

OCR文本掃描 輪廓檢測 透視變換-唐宇迪筆記OCR文本掃描 輪廓檢測 透視變換

2、計算Gx和Gy

OCR文本掃描 輪廓檢測 透視變換-唐宇迪筆記OCR文本掃描 輪廓檢測 透視變換

3、非極大值抑制

OCR文本掃描 輪廓檢測 透視變換-唐宇迪筆記OCR文本掃描 輪廓檢測 透視變換

4、雙門檻值檢測

OCR文本掃描 輪廓檢測 透視變換-唐宇迪筆記OCR文本掃描 輪廓檢測 透視變換
import cv2 #opencv讀取的格式是BGR
import numpy as np
import matplotlib.pyplot as plt#Matplotlib是RGB

def cv_show(name,img):
    cv2.imshow(name,img)
    #等待時間,毫秒級,0表示任意鍵終止
    cv2.waitKey(0)#任意鍵終止
    cv2.destroyAllWindows()
    
img=cv2.imread('lena.jpg',cv2.IMREAD_GRAYSCALE)

img1=cv2.Canny(img,120,250)
img2=cv2.Canny(img,50,100)
res=np.hstack((img1,img2))
cv_show('res',res)
           

對比圖:

OCR文本掃描 輪廓檢測 透視變換-唐宇迪筆記OCR文本掃描 輪廓檢測 透視變換

Canny邊緣檢測之前需要先降噪----高斯濾波

GaussianBlur(src,ksize,sigmaX [,dst [,sigmaY [,borderType]]])-> dst

第一個參數是輸入圖像,可以是Mat類型,圖像深度為CV_8U、CV_16U、CV_16S、CV_32F、CV_64F。

第二個參數是輸出圖像,與輸入圖像有相同的類型和尺寸。

Size ksize: 高斯核心大小,這個尺寸與前面兩個濾波kernel尺寸不同,ksize.width和ksize.height可以不相同但是這兩個值必須為正奇數,如果這兩個值為0,他們的值将由sigma計算。

double sigmaX: 高斯核函數在X方向上的标準偏差

double sigmaY: 高斯核函數在Y方向上的标準偏差,如果sigmaY是0,則函數會自動将sigmaY的值設定為與sigmaX相同的值,如果sigmaX和sigmaY都是0,這兩個值将由ksize.width和ksize.height計算而來。

Canny邊緣檢測函數

canny = cv2.Canny(gauss, 75, 200)

第一個參數時輸入的圖像,第二個參數是MinVal,第三個參數是MaxVal。

如果該點的梯度大于MaxVal, 則将該點處理為邊界。如果該點的梯度大于MinVal且小于MaxVal,則抗癌電視都與邊界相連,如果相連則将該點處理為邊界,否則不是邊界。如果該點小于MinVal則該點不是邊界。

# 預處理
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)#灰階
gray = cv2.GaussianBlur(gray, (5, 5), 0)#高斯濾波
edged = cv2.Canny(gray, 75, 200)#邊緣檢測:雙門檻值檢測,75為mix,200為max。讓邊緣都成白色,其餘為黑色

# 展示預處理結果
print("STEP 1: 邊緣檢測")
cv_show("Image", image)
cv_show("Edged", edged)
           
OCR文本掃描 輪廓檢測 透視變換-唐宇迪筆記OCR文本掃描 輪廓檢測 透視變換

輪廓檢測

查找輪廓,輪廓檢測使用的模型是RETR_LIST,檢測所有輪廓, 并将其儲存在一條連結清單中

cv2.findContours(img, mode, methord)

下表列出了輪廓檢測的模型mode

MODE 注釋
RETR_EXTERNAL 隻檢測最外面的輪廓
RETR_LIST 檢索所有輪廓,并将其儲存到一條連結清單中
RETR_CCOMP 檢索所有輪廓,并将他們組織為兩層 ,頂層是各部分的外界邊界,第二層是空洞的邊界
RETR_TREE 檢測所有輪廓,并重構嵌套輪廓的整個層次

檢測到所有輪廓後根據輪廓面積大小對輪廓進行排序,并保留前五個,畫出這按照面積排序的前五個輪廓(在繪制輪廓之前别忘了複制原圖像,否則顯示原圖形也會被更改。

OCR文本掃描 輪廓檢測 透視變換-唐宇迪筆記OCR文本掃描 輪廓檢測 透視變換

輪廓近似

定義一個循環,周遊輪廓,完成輪廓近似的操作。

peri = cv2.arcLength(c, True)

先計算出輪廓的周長,然後以輪廓周長乘以一個百分比作為輪廓近似的精度。

approx = cv2.approxPolyDP(c, 0.02*peri, True)

True表示輪廓是封閉的

而輪廓近似的傳回值是能夠包含圖像的點的集合。既然是逐個點确定,又包含了整個圖像,那一定是從最大的輪廓開始。當傳回值的長度為4,即傳回點的個數為4時,說明确定的就是能将最外面的最大輪廓包圍的四邊形的四個頂點。

透視變換

透視變換(Perspective Transformation)是将成像投影到一個新的視平面(Viewing Plane),也稱作投影映射(Projective Mapping)。如下圖,通過透視變換ABC變換到A’B’C’。

OCR文本掃描 輪廓檢測 透視變換-唐宇迪筆記OCR文本掃描 輪廓檢測 透視變換

對于視角變換,我們需要一個 3x3 變換矩陣。在變換前後直線還是直線。

要建構這個變換矩陣,你需要在輸入圖像上找 4 個點,以及他們在輸出圖像上對應的位置。這四個點中的任意三個都不能共線。這個變換矩陣可以有函數cv2.getPerspectiveTransform() 建構。然後把這個矩陣傳給函數cv2.warpPerspective。

1、cv2.getPerspectiveTransform(src, dst)

參數說明:

src:源圖像中待測矩形的四點坐标
sdt:目标圖像中矩形的四點坐标
           

傳回由源圖像中矩形到目标圖像矩形變換的矩陣

2、cv2.warpPerspective(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]])

參數為:

src:輸入圖像

M:變換矩陣

dsize:目标圖像shape

flags:插值方式,interpolation方法INTER_LINEAR或INTER_NEAREST

borderMode:邊界補償方式,BORDER_CONSTANT or BORDER_REPLICATE

borderValue:邊界補償大小,常值,預設為0

3、cv2.perspectiveTransform(src, m[, dst])

參數:

src:輸入的2通道或者3通道的圖檔

m:變換矩陣

傳回的是相同size的圖檔

4、差別

warpPerspective适用于圖像。perspectiveTransform适用于一組點。

透視變換有很多有趣的應用,比如可以用來做圖像傾斜校正

自定義函數 order_points()
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
           

程式中定義了兩個函數,order_point函數用一種方法來分辨這四個定位點分别對應于四邊形的那個頂點,簡單說就是給四個點起名字。

左下(bl), 右下(br), 右上(tr), 左上(tl),并将四個點按順勢者或逆時針依次存放。從那個點開始存放不重要,關鍵是要通過這四個點的坐标關系确定每一個點分别對應(四邊形)的哪一個頂點。簡單說來就是給每一個點起了一個代号,友善使用它,後面有用。

第二個函數 four_point_transform(image, pts)進行透視變換了。首先調用函數order_point,使用這四個起好名字的點。根據幾個關系利用公式 s = ((x2-x1)^2 + (y2-y1))2)1/2 。因為四個點确定的近似輪廓不一定是矩形,是以分别取長和寬最大長度,

OCR文本掃描 輪廓檢測 透視變換-唐宇迪筆記OCR文本掃描 輪廓檢測 透視變換

自定義函數 def four_point_transform()

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))   #使用的公式s = ((x2-x1)^2  + (y2-y1))^2)^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
           

dst對應的是與原圖像中輪廓大小相同,隻是進行了坐标變換的圖像。左上角坐标點為(0,0),圖像的長、寬分别為四邊形輪廓長、寬最大值的四個定位點。将原圖像中卡片的輪廓摳出來,變換了坐标。

透視變換函數包含在自定義函數four_point_transform()中

透視變換就是将原始的四個定位點,變換後定位點分别對應dst(左上角的定位點是(0, 0))中的四個定位點坐标。

M = cv2.getPerspectiveTransform(rect, dst)
warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))
           

傳回時M是由原坐标透視變換到目标坐标點的變換矩陣。第二個傳回圖像的透視變換函數cv2.warpPerspective(image, M, (maxWidth, maxHeight))第一個參數是原始圖像,第二個參數是變換矩陣(原圖檔中的小卡片是一個輪廓,變換後圖像中小卡片充滿了整張圖像), 第三個參數是變換後圖像的長和寬(場合寬是前面計算出的輪廓長寬取最大值的結果)。

最後該函數傳回的結果是透視變換後的圖像。

warped = four_point_transform(image, screenCnt.reshape(4, 2))
warped = four_point_transform(orig, screenCnt.reshape(4, 2)*ratio)
           

接下來就是灰階化、二值化一條龍服務。

終于把這張小卡片從圖像中單獨扣出來了!

結果如圖所示:

OCR文本掃描 輪廓檢測 透視變換-唐宇迪筆記OCR文本掃描 輪廓檢測 透視變換

調用four_point_transform函數

warped = four_point_transform(orig, screenCnt.reshape(4, 2)*ratio)

說一下為什麼要乘這個ratio

因為之前做的一系列圖像處理操作(最後得到了近似輪廓的四個定位點)都是在resize後的圖像上進行的。還記得resize函數是怎麼做的嗎,參數中給定width或者height的值,按照與原圖像相同的比例對圖像進行縮放。那麼圖像中每一個點的坐标都發生了相應的變化(不是面積大小,而是點的坐标發生了變化)

用plt顯示圖檔驗證一下

是以我們在程式一開始就把原圖坐标和變化後圖像的坐标的比例ratio記錄下來。

我們剛剛說是圖檔上的每個像素點坐标發生了相應的變化,那麼我們輪廓近似的四個定位點當然不例外就在其中。

是以相當于是吧原圖像上的卡片四角的點坐标透視變換到與原圖像相同大小的平面上,而four_point_transform函數的第二個參數scrrnCnt

中存儲的坐标點應該與第一個參數中圖像大小保持一緻(都是原圖或者都是resize變換後的,一緻即可)。那我為什麼要從一開始就做resize這一步呢?(因為圖太大電腦螢幕放不下, 我要是有個巨大的螢幕是不是會省去很多麻煩)。

對于 M = cv2.getPerspectiveTransform(rect, dst)

以下是原理:實際上就是先把歪的二維原圖轉化為三維,再轉化為二維,在這其中,先把二維的(x,y)轉化為(x,y,1),然後乘一個33的矩陣,轉化為(x,y,z),最後轉化為二維(x/z,y/z)。

因為33的矩陣中,有8個未知數,就有8個方程,最後一位是1,一組坐标可以确定2個方程,是以需要4組坐标,即一個矩形的四個角的坐标。這個函數就是幫我們算矩陣m

OCR文本掃描 輪廓檢測 透視變換-唐宇迪筆記OCR文本掃描 輪廓檢測 透視變換

3 調用pytesseract進行OCR文本識别

接着在test.py中 import pytesseract 進行OCR識别

# https://digi.bib.uni-mannheim.de/tesseract/
# 配置環境變量如E:\Program Files (x86)\Tesseract-OCR
# tesseract -v進行測試
# tesseract XXX.png 得到結果 
# pip install pytesseract
# anaconda lib site-packges pytesseract pytesseract.py
# tesseract_cmd 修改為絕對路徑即可
from PIL import Image
import pytesseract
import cv2
import os

preprocess = 'blur' #thresh

image = cv2.imread('scan.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

if preprocess == "thresh":
    gray = cv2.threshold(gray, 0, 255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]

if preprocess == "blur":
    gray = cv2.medianBlur(gray, 3)
    
filename = "{}.png".format(os.getpid())
cv2.imwrite(filename, gray)
    
text = pytesseract.image_to_string(Image.open(filename))
print(text)
os.remove(filename)

cv2.imshow("Image", image)
cv2.imshow("Output", gray)
cv2.waitKey(0)                                   

           

首先對圖像進行灰階化處理, 然後進行均值濾波。

gray = cv2.medianBlur(gray, 3)

調用pytesseract.image_to_string()函數

pytesseract.image_to_string(Image.open(filename))

感謝https://blog.csdn.net/weixin_43227526/article/details/104692787

上一篇: 舵機控制3.17
下一篇: 權限驗證MVC