Python利用OpenCV實(shí)現(xiàn)文檔圖像自動矯正的方法
1. 引言
在日常生活中,用手機(jī)拍攝的文檔照片往往因?yàn)榕臄z角度而產(chǎn)生透視畸變——原本方正的紙張變成了不規(guī)則的四邊形,導(dǎo)致文字歪斜,影響閱讀和OCR識別。透視變換技術(shù)可以將圖像中任意四邊形區(qū)域“拉正”為矩形,完美解決這一問題。本文將基于你提供的完整Python代碼,深入解析利用OpenCV實(shí)現(xiàn)自動文檔矯正的每一步,從圖像預(yù)處理、輪廓檢測到透視變換,并詳細(xì)講解核心函數(shù)的原理與參數(shù)含義。
2. 透視變換核心原理
透視變換是一種將圖像從一個平面投影到另一個平面的幾何變換。它用一個 3×3 的單應(yīng)性矩陣 描述,需要至少4對對應(yīng)點(diǎn)才能求解。在文檔矯正場景中,這4對點(diǎn)就是原始圖像中文檔的四個頂點(diǎn)和矯正后矩形的四個頂點(diǎn)。OpenCV提供了兩個核心函數(shù):
cv2.getPerspectiveTransform(src, dst):根據(jù)源點(diǎn)和目標(biāo)點(diǎn)計算變換矩陣。cv2.warpPerspective(src, M, dsize):應(yīng)用變換矩陣到圖像。
3. 環(huán)境準(zhǔn)備
- Python 3.x
- OpenCV (
pip install opencv-python) - NumPy (
pip install numpy)
4. 代碼逐段詳解
4.1 導(dǎo)入庫
import numpy as np import cv2
numpy:用于高效的數(shù)組運(yùn)算和數(shù)學(xué)計算(如距離、求和)。cv2:OpenCV庫,提供圖像處理、輪廓檢測、透視變換等功能。
4.2 輔助函數(shù)
4.2.1 圖像顯示函數(shù)cv_show
def cv_show(name, img):
cv2.imshow(name, img)
cv2.waitKey(0)
cv2.destroyAllWindows()- 作用:方便在調(diào)試過程中隨時顯示圖像,按下任意鍵后關(guān)閉窗口并釋放資源。
cv2.waitKey(0)等待按鍵,參數(shù)0表示無限等待。cv2.destroyAllWindows()關(guān)閉所有OpenCV創(chuàng)建的窗口。
4.2.2 圖像等比例縮放函數(shù)resize
def resize(image, width=None, height=None, inter=cv2.INTER_AREA):
dim = None
(h, w) = image.shape[:2]
if width is None and height is None:
return image
if width is None:
r = height / float(h)
dim = (int(w * r), height)
else:
r = width / float(w)
dim = (width, int(h * r))
resized = cv2.resize(image, dim, interpolation=inter)
return resized- 作用:按指定寬度或高度等比例縮放圖像,保持寬高比,避免圖像變形。
- 參數(shù):
width、height:指定目標(biāo)寬度或高度,只需提供一個,另一個自動計算。inter:插值方法,默認(rèn)為cv2.INTER_AREA,適合縮小圖像。
- 原理:根據(jù)提供的尺寸計算縮放比例
r,然后生成新尺寸dim,最后調(diào)用cv2.resize完成縮放。
4.2.3 頂點(diǎn)排序函數(shù)order_points
def order_points(pts):
# 一共4個坐標(biāo)點(diǎn)
rect = np.zeros(shape=(4, 2), dtype="float32") # 用來存儲排序之后的坐標(biāo)位置
# 按順序找到對應(yīng)坐標(biāo)0123分別是 左上,右上,右下,左下
s = pts.sum(axis=1) # 對pts矩陣的每一行進(jìn)行求和操作。(x+y)
rect[0] = pts[np.argmin(s)]
rect[2] = pts[np.argmax(s)]
diff = np.diff(pts, axis=1) # 對pts矩陣的每一行進(jìn)行求差操作。(y-x)
rect[1] = pts[np.argmin(diff)]
rect[3] = pts[np.argmax(diff)]
return rect- 為什么要排序? 透視變換要求源點(diǎn)和目標(biāo)點(diǎn)的順序必須一一對應(yīng)(例如左上對左上)。而輪廓檢測得到的四個點(diǎn)順序是隨機(jī)的,需要重新排序?yàn)?nbsp;左上、右上、右下、左下。
- 排序原理:
- 左上角的點(diǎn)具有最小的
x+y值(靠近原點(diǎn))。 - 右下角的點(diǎn)具有最大的
x+y值。 - 右上角的點(diǎn)具有最小的
y-x值(因?yàn)閥和x接近)。 - 左下角的點(diǎn)具有最大的
y-x值(x小y大)。
- 左上角的點(diǎn)具有最小的
- 代碼解析:
pts.sum(axis=1):計算每個點(diǎn)的(x, y)和,得到一維數(shù)組。np.argmin(s)和np.argmax(s)分別找出和最小及最大的點(diǎn)的索引,分別賦值給左上和右下。np.diff(pts, axis=1):計算每個點(diǎn)的y - x(因?yàn)閐iff是后減前,即x[:,1] - x[:,0],注意這里實(shí)際是y - x取決于pts的列順序,通常pts是(x, y),所以diff得到(y - x))。
- 同理,找出差最小和最大的點(diǎn)作為右上和左下。
4.2.4 透視變換核心函數(shù)four_point_transform
def four_point_transform(image, pts):
# 獲取輸入坐標(biāo)點(diǎn)
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))
# 變換后對應(yīng)坐標(biāo)位置
dst = np.array([[0, 0], [maxWidth - 1, 0],
[maxWidth - 1, maxHeight - 1], [0, maxHeight - 1]], dtype="float32")
# 獲取透視變換矩陣
M = cv2.getPerspectiveTransform(rect, dst)
# 執(zhí)行透視變換
warped = cv2.warpPerspective(image, M, dsize=(maxWidth, maxHeight))
# 返回變換后結(jié)果
return warped步驟解析:
- 排序頂點(diǎn):調(diào)用
order_points得到按順序排列的四個點(diǎn)(tl, tr, br, bl)。 - 計算目標(biāo)矩形尺寸:由于原始四邊形可能不是嚴(yán)格的矩形,上下邊長度可能不同,左右邊長度也可能不同。為了確保矯正后的圖像包含整個文檔內(nèi)容,取上下邊寬度的最大值作為目標(biāo)寬度,左右邊長度的最大值作為目標(biāo)高度。
widthA:底部邊(bl→br)的歐氏距離。widthB:頂部邊(tl→tr)的歐氏距離。maxWidth:取兩者較大值并轉(zhuǎn)為整數(shù)。- 同理計算高度。
- 定義目標(biāo)點(diǎn):目標(biāo)矩形四個頂點(diǎn)按順序?yàn)樽笊?0,0)、右上(maxWidth-1,0)、右下(maxWidth-1,maxHeight-1)、左下(0,maxHeight-1)。注意減1是因?yàn)橄袼厮饕龔?開始。
- 計算變換矩陣:
cv2.getPerspectiveTransform(rect, dst)返回3×3變換矩陣M。 - 應(yīng)用變換:
cv2.warpPerspective(image, M, (maxWidth, maxHeight))輸出矯正后的圖像。
4.3 主程序流程
4.3.1 讀取圖像并顯示原圖
image = cv2.imread('fapiao.jpg')
cv_show('image', image)- 讀取圖片
fapiao.jpg,并用自定義函數(shù)顯示。
4.3.2 縮放圖像以加快處理
ratio = image.shape[0] / 500.0 # 計算縮小比率
orig = image.copy()
image = resize(orig, height=500)
cv_show('1', image)ratio:原圖高度除以500,得到縮放比例,后續(xù)用于將檢測到的輪廓坐標(biāo)映射回原圖。orig:保存原始圖像副本,用于最終的高質(zhì)量透視變換。resize(orig, height=500):將圖像高度縮放到500像素,寬度等比例縮放。處理縮小圖能極大提高輪廓檢測速度。- 顯示縮放后的圖像(標(biāo)題'1')。
結(jié)果展示:

4.3.3 輪廓檢測
print("STEP 1: 輪廓檢測")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 讀取灰度圖
edged = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] # 自動尋找閾值二值化
cnts = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)[-2]
image_contours = cv2.drawContours(image.copy(), cnts, -1, color=(0, 0, 255), thickness=1)
cv_show('image_contours', image_contours)- 灰度化:
cv2.cvtColor將BGR圖像轉(zhuǎn)為灰度圖,便于二值化。 - 二值化:
cv2.threshold使用OTSU算法自動計算最優(yōu)閾值,得到黑白二值圖edged。參數(shù)cv2.THRESH_BINARY | cv2.THRESH_OTSU表示采用OTSU自動閾值且輸出為二值圖像。 - 輪廓檢測:
cv2.findContours查找所有輪廓。參數(shù):edged.copy():傳入二值圖副本,避免函數(shù)修改原圖。cv2.RETR_LIST:提取所有輪廓,不建立等級關(guān)系。cv2.CHAIN_APPROX_SIMPLE:壓縮輪廓,只保留端點(diǎn)。[-2]:兼容不同OpenCV版本,確保返回輪廓列表。
- 繪制輪廓:在圖像副本上用紅色(BGR=(0,0,255))繪制所有輪廓,便于觀察。
- 顯示結(jié)果。
結(jié)果展示:

4.3.4 獲取最大輪廓并近似為四邊形
print("STEP 2: 獲取最大輪廓")
screenCnt = sorted(cnts, key=cv2.contourArea, reverse=True)[0] # 獲取面積最大的輪廓
print(screenCnt.shape)
peri = cv2.arcLength(screenCnt, closed=True) # 計算輪廓周長
screenCnt = cv2.approxPolyDP(screenCnt, 0.05 * peri, closed=True) # 輪廓近似
print(screenCnt.shape)
image_contour = cv2.drawContours(image.copy(), [screenCnt], -1, (0, 255, 0), thickness=2)
cv2.imshow("image_contour", image_contour)
cv2.waitKey(0)- 篩選最大輪廓:假設(shè)文檔是圖像中面積最大的物體,按面積降序排序,取第一個輪廓
screenCnt。 - 計算周長:
cv2.arcLength獲取輪廓周長,closed=True表示輪廓閉合。 - 多邊形近似:
cv2.approxPolyDP使用道格拉斯-普克算法簡化輪廓。參數(shù)0.05 * peri是近似精度,值越小越精細(xì),這里設(shè)為周長的5%,目的是將復(fù)雜輪廓簡化為四邊形。理想情況下,簡化后輪廓應(yīng)包含4個點(diǎn)。 - 打印形狀:查看簡化前后的頂點(diǎn)數(shù)。
- 繪制近似輪廓:在圖像副本上用綠色((0,255,0))繪制該輪廓,線條粗細(xì)2。
- 顯示結(jié)果。
結(jié)果展示:

4.3.5 透視變換
###############
# 透視變換
warped = four_point_transform(orig, screenCnt.reshape(4, 2) * ratio)
cv2.imwrite('invoice_new.jpg', warped)
cv2.namedWindow('xx', cv2.WINDOW_NORMAL)
cv2.imshow("xx", warped)
cv2.waitKey(0)- 坐標(biāo)還原:
screenCnt當(dāng)前形狀可能為(4, 1, 2),通過reshape(4, 2)轉(zhuǎn)換為標(biāo)準(zhǔn)的(4, 2)數(shù)組。- 乘以縮放比例
ratio,將坐標(biāo)從縮放后的圖像空間映射回原始圖像空間,確保透視變換在原始分辨率下進(jìn)行,保留細(xì)節(jié)。
- 調(diào)用透視變換:
four_point_transform(orig, ...)對原始圖像orig進(jìn)行矯正,得到warped。 - 保存結(jié)果:
cv2.imwrite將矯正后的圖像保存為invoice_new.jpg。 - 顯示結(jié)果:創(chuàng)建一個可調(diào)整大小的窗口
xx顯示矯正后的圖像,便于查看細(xì)節(jié)。
運(yùn)行結(jié)果展示:

5. 運(yùn)行結(jié)果
輸入圖像 fapiao.jpg 是一張傾斜拍攝的發(fā)票,程序?qū)⒁来物@示:
- 原圖
- 縮放后的圖像(高度500)
- 所有輪廓(紅色)
- 檢測到的最大輪廓近似結(jié)果(綠色四邊形)
- 最終矯正后的正面圖像(保存為
invoice_new.jpg)
矯正后的圖像將呈現(xiàn)標(biāo)準(zhǔn)的矩形,文字方向變正,極大方便后續(xù)OCR識別。
6. 總結(jié)與注意事項
6.1 總結(jié)
本文通過一個完整的OpenCV案例,詳細(xì)講解了如何利用透視變換自動矯正傾斜的文檔圖像。核心步驟包括:
- 圖像縮放加速處理
- 灰度化與OTSU二值化
- 輪廓檢測與篩選
- 多邊形近似獲取四個頂點(diǎn)
- 頂點(diǎn)排序與透視變換
代碼結(jié)構(gòu)清晰,函數(shù)封裝良好,便于復(fù)用和擴(kuò)展。
6.2 注意事項
- 頂點(diǎn)近似精度:
cv2.approxPolyDP的epsilon參數(shù)(0.05 * peri)需要根據(jù)實(shí)際圖像調(diào)整。若無法得到4個點(diǎn),可適當(dāng)減小或增大該值。 - 最大輪廓假設(shè):代碼假設(shè)文檔是面積最大的輪廓,如果圖像中包含其他更大物體(如桌面、背景雜物),可能導(dǎo)致誤檢。可增加篩選條件(如輪廓長寬比、面積范圍)提高魯棒性。
- 坐標(biāo)映射:
screenCnt.reshape(4, 2) * ratio必須在調(diào)用透視變換前執(zhí)行,確保使用原始圖像坐標(biāo)。 - 圖像方向:如果矯正后圖像方向不對(例如旋轉(zhuǎn)了90度),可用
cv2.rotate進(jìn)行修正。
通過理解和掌握這段代碼,你可以輕松將其應(yīng)用到車牌矯正、名片掃描、增強(qiáng)現(xiàn)實(shí)等更多場景中。如果在實(shí)踐中遇到問題,歡迎留言交流!
以上就是Python利用OpenCV實(shí)現(xiàn)文檔圖像自動矯正的方法的詳細(xì)內(nèi)容,更多關(guān)于Python OpenCV文檔圖像自動矯正的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
優(yōu)化Python代碼使其加快作用域內(nèi)的查找
這篇文章主要介紹了優(yōu)化Python代碼使其加快作用域內(nèi)的搜索,文中介紹了CPython相關(guān)的C代碼來對查找功能進(jìn)行優(yōu)化,加快搜索的速度,需要的朋友可以參考下2015-03-03
Python3爬蟲里關(guān)于Splash負(fù)載均衡配置詳解
在本篇文章里小編給大家分享了關(guān)于Python3爬蟲里關(guān)于Splash負(fù)載均衡配置的相關(guān)內(nèi)容,需要的朋友們可以學(xué)習(xí)參考下。2020-07-07
Pycharm沒有報錯提示(誤觸ignore)的完美解決方案
這篇文章主要介紹了Pycharm沒有報錯提示(誤觸ignore)的解決方案,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-12-12
關(guān)于數(shù)據(jù)分析Pandas的Series用法總結(jié)
這篇文章主要介紹了關(guān)于數(shù)據(jù)分析Pandas的Series用法總結(jié),Series序列,是一種一維的結(jié)構(gòu),類似于一維列表和ndarray中的一維數(shù)組,但是功能比他們要更為強(qiáng)大,Series由兩部分組成:索引index和數(shù)值values,本篇對其用法做出總結(jié)2023-07-07
Python中Timedelta轉(zhuǎn)換為Int或Float方式
這篇文章主要介紹了Python中Timedelta轉(zhuǎn)換為Int或Float方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07
對Python3中的print函數(shù)以及與python2的對比分析
下面小編就為大家分享一篇對Python3中的print函數(shù)以及與python2的對比分析,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-05-05

