OpenCV 發票識別全流程:透視變換與輪廓檢測詳解

目錄

前言

一、核心技術原理:透視變換與輪廓檢測

1. 透視變換:讓傾斜發票 “正過來”

(1)什么是透視變換?

(2)透視變換的 5 個關鍵步驟

2. 輪廓檢測:精準定位發票區域

(1)什么是輪廓檢測?

(2)輪廓檢測的 5 個執行步驟

二、項目實戰:OpenCV 發票識別全流程代碼

1. 環境準備

2. 工具函數定義

(1)圖像展示函數

(2)圖像自動縮放函數

(3)輪廓點排序函數

(4)透視變換函數

3. 發票識別全流程執行

(1)步驟 1:讀取原圖并縮放

(2)步驟 2:圖像預處理與輪廓檢測

(3)步驟 3:篩選最大輪廓(定位發票區域)

(4)步驟 4:透視變換校正發票

(5)步驟 5:二值化與形態學優化(為 OCR 準備)


前言

在辦公自動化與計算機視覺結合的場景中,發票識別是典型的落地需求 —— 實際拍攝的發票常因角度傾斜、背景雜亂導致文字提取困難,而基于 OpenCV 的透視變換與輪廓檢測技術,能快速將傾斜發票校正為正視角、高對比度的規整圖像,為后續 OCR 文字識別奠定基礎。本文將從核心技術原理出發,結合完整代碼與實戰效果,拆解發票識別的全流程,適合 OpenCV 入門者與計算機視覺愛好者學習。

一、核心技術原理:透視變換與輪廓檢測

在開始項目前,需先理解兩個關鍵技術的核心邏輯 —— 它們是發票校正與目標提取的基礎。

1. 透視變換:讓傾斜發票 “正過來”

(1)什么是透視變換?

透視變換是一種將三維空間中傾斜的平面映射到二維平面的幾何變換,它能模擬人眼的透視效果,解決 “拍攝角度傾斜導致發票邊緣不規整” 的問題。例如,從斜上方拍攝的發票,其四個角會呈現梯形或不規則四邊形,通過透視變換可將其校正為標準矩形,還原發票的真實比例。

透視變換的數學本質是通過4 組對應點(源圖像 4 個角點與目標圖像 4 個角點)計算變換矩陣,再用該矩陣對源圖像進行像素映射。核心特點是:

  • 平行線可能在變換后相交(符合真實透視規律);
  • 能保留圖像的細節信息,僅改變視角。
(2)透視變換的 5 個關鍵步驟
  1. 確定源圖像與目標圖像:源圖像是拍攝的傾斜發票,目標圖像是期望得到的 “正矩形發票”;
  2. 選取 4 組對應關鍵點:需在源圖像中找到發票的 4 個頂點(左上、右上、右下、左下),并定義目標圖像中對應的 4 個頂點(如(0,0)(width,0)(width,height)(0,height));
  3. 計算變換矩陣:使用 OpenCV 的cv2.getPerspectiveTransform(),通過 4 組對應點生成 3x3 的透視變換矩陣M
  4. 執行透視變換:用cv2.warpPerspective()加載變換矩陣M,將源圖像映射為目標圖像;
  5. 插值處理:由于變換后像素可能映射到非整數坐標,需通過插值(如INTER_LINEAR)補充像素值,保證圖像清晰度。

2. 輪廓檢測:精準定位發票區域

(1)什么是輪廓檢測?

輪廓是圖像中連續的、具有相同灰度或顏色的像素邊緣,輪廓檢測本質是從圖像中提取目標物體的邊界,在發票識別中用于 “從復雜背景中精準框選出發票區域”。

與邊緣檢測(如 Canny)不同,輪廓檢測不僅能找到離散的邊緣點,還能將邊緣點連接成連續的閉合曲線,便于后續計算目標的面積、周長、頂點等特征。常用的輪廓檢測算法依賴圖像的二值化結果(黑白對比),因此預處理步驟尤為重要。

(2)輪廓檢測的 5 個執行步驟
  1. 圖像預處理:將彩色圖像轉為灰度圖(減少計算量),再通過高斯濾波(cv2.GaussianBlur)去除噪聲,避免噪聲干擾輪廓提取;
  2. 邊緣檢測:用二值化(如cv2.threshold+THRESH_OTSU自動閾值)將灰度圖轉為黑白圖像,突出發票與背景的對比;
  3. 輪廓提取:通過cv2.findContours()提取圖像中所有輪廓,指定輪廓檢索模式(如RETR_LIST提取所有輪廓)與逼近方法(如CHAIN_APPROX_SIMPLE簡化輪廓點);
  4. 輪廓篩選:根據輪廓的面積(cv2.contourArea)、周長(cv2.arcLength)等特征篩選出 “發票輪廓”—— 通常是面積最大的閉合輪廓;
  5. 輪廓繪制與驗證:用cv2.drawContours()將篩選后的輪廓繪制在原圖上,驗證是否準確框選出發票區域。

二、項目實戰:OpenCV 發票識別全流程代碼

1. 環境準備

  • 編程語言:Python 3.7+
  • 依賴庫:OpenCV(pip install opencv-python)、NumPy(pip install numpy
  • 測試數據:拍攝的傾斜發票圖像(命名為fapiao.jpg,建議分辨率不低于 1000x800)

2. 工具函數定義

先封裝 4 個核心工具函數,提高代碼復用性與可讀性。

(1)圖像展示函數

用于快速展示處理過程中的圖像,避免重復編寫cv2.imshowcv2.waitKey

import cv2
import numpy as npdef cv_show(name, img):"""展示圖像的通用函數:param name: 窗口名稱:param img: 輸入圖像(numpy數組)"""cv2.imshow(name, img)cv2.waitKey(0)  # 等待按鍵關閉窗口cv2.destroyWindow(name)  # 關閉指定窗口
(2)圖像自動縮放函數

解決 “原圖過大導致窗口無法完整顯示” 的問題,保持圖像寬高比不變:

def resize_image(image, width=None, height=None, inter=cv2.INTER_AREA):"""保持寬高比的圖像縮放函數:param image: 輸入圖像:param width: 目標寬度(None則按高度計算):param height: 目標高度(None則按寬度計算):param inter: 插值方式(默認INTER_AREA,適合縮放):return: 縮放后的圖像"""dim = None  # 存儲目標尺寸(寬,高)h, w = image.shape[:2]  # 獲取原圖高、寬# 若未指定寬和高,直接返回原圖if width is None and height is None:return image# 僅指定高度:按高度比例計算寬度if width is None:ratio = height / float(h)dim = (int(w * ratio), height)# 僅指定寬度:按寬度比例計算高度else:ratio = width / float(w)dim = (width, int(h * ratio))# 執行縮放resized = cv2.resize(image, dim, interpolation=inter)return resized
(3)輪廓點排序函數

透視變換需要 4 個 “有序的頂點”(左上→右上→右下→左下),該函數通過計算點的坐標和與差值實現排序:

def order_points(pts):"""對輪廓的4個頂點按“左上→右上→右下→左下”排序:param pts: 輸入的4個頂點(shape為(4,2)的numpy數組):return: 排序后的頂點數組"""rect = np.zeros((4, 2), dtype="float32")  # 初始化排序后的數組# 步驟1:按“x+y”的和排序(左上和最小,右下和最大)s = pts.sum(axis=1)rect[0] = pts[np.argmin(s)]  # 左上點(x+y最小)rect[2] = pts[np.argmax(s)]  # 右下點(x+y最大)# 步驟2:按“y-x”的差值排序(右上差值最小,左下差值最大)diff = np.diff(pts, axis=1)rect[1] = pts[np.argmin(diff)]  # 右上點(y-x最小)rect[3] = pts[np.argmax(diff)]  # 左下點(y-x最大)return rect
(4)透視變換函數

輸入圖像與 4 個頂點,返回校正后的規整圖像:

def four_point_transform(image, pts):"""基于4個頂點的透視變換函數:param image: 源圖像(原始傾斜發票):param pts: 源圖像中發票的4個頂點:return: 校正后的圖像"""# 1. 排序頂點rect = order_points(pts)tl, tr, br, bl = rect  # 解包為左上、右上、右下、左下# 2. 計算目標圖像的寬和高(取最大值避免圖像裁剪)# 計算底部寬度(右下-左下)和頂部寬度(右上-左上)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))  # 目標高度# 3. 定義目標圖像的4個頂點(標準矩形)dst = np.array([[0, 0],[maxWidth - 1, 0],[maxWidth - 1, maxHeight - 1],[0, maxHeight - 1]], dtype="float32")# 4. 計算透視變換矩陣并執行變換M = cv2.getPerspectiveTransform(rect, dst)  # 生成3x3變換矩陣warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))  # 執行變換return warped

3. 發票識別全流程執行

(1)步驟 1:讀取原圖并縮放

先加載原始發票圖像,按固定高度縮放(避免原圖過大),同時保存縮放比例(后續用于還原輪廓坐標):

# 讀取原始發票圖像
orig = cv2.imread("fapiao.jpg")
if orig is None:raise ValueError("未找到圖像文件,請檢查路徑是否正確!")# 縮放圖像(固定高度為500,保持寬高比)
ratio = orig.shape[0] / 500.0  # 縮放比例(原圖高 / 縮放后高)
image = resize_image(orig, height=500)# 展示原圖與縮放圖
cv_show("原始發票", orig)
cv_show("縮放后發票", image)

以下是縮放后的發票圖:

(2)步驟 2:圖像預處理與輪廓檢測

通過灰度化、二值化突出發票邊緣,再提取所有輪廓:

# 1. 灰度化(減少通道數,降低計算量)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)# 2. 二值化(自動閾值,突出發票與背景對比)
# THRESH_OTSU:自動計算最優閾值,適合明暗對比明顯的圖像
edged = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]# 3. 提取所有輪廓
# RETR_LIST:提取所有輪廓,不建立層次關系;CHAIN_APPROX_SIMPLE:簡化輪廓點
cnts, _ = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)# 4. 繪制所有輪廓(紅色,線寬1),驗證提取效果
image_contours = cv2.drawContours(image.copy(), cnts, -1, (0, 0, 255), 1)
cv_show("所有輪廓", image_contours)

所有輪廓如下:

(3)步驟 3:篩選最大輪廓(定位發票區域)

發票通常是圖像中面積最大的閉合區域,通過輪廓面積排序篩選出目標輪廓,并進行多邊形近似(減少輪廓點數量):

# 1. 按輪廓面積降序排序,取面積最大的輪廓(即發票輪廓)
screen_cnt = sorted(cnts, key=cv2.contourArea, reverse=True)[0]# 2. 輪廓近似(將不規則輪廓近似為多邊形)
# arcLength:計算輪廓周長(True表示閉合輪廓)
peri = cv2.arcLength(screen_cnt, True)
# approxPolyDP:輪廓近似,epsilon=0.02*peri(控制近似精度)
screen_cnt = cv2.approxPolyDP(screen_cnt, 0.02 * peri, True)# 3. 驗證輪廓是否為4個頂點(發票是四邊形,需4個頂點)
if len(screen_cnt) != 4:raise ValueError("未檢測到發票的4個頂點,請調整拍攝角度或圖像質量!")# 4. 繪制最大輪廓(綠色,線寬2)
image_max_contour = cv2.drawContours(image.copy(), [screen_cnt], -1, (0, 255, 0), 2)
cv_show("發票最大輪廓", image_max_contour)

最大輪廓如下:

(4)步驟 4:透視變換校正發票

用篩選出的 4 個頂點執行透視變換,將傾斜發票校正為正矩形:

# 1. 還原輪廓坐標(縮放后的坐標 * 縮放比例 = 原圖坐標)
screen_cnt_org = screen_cnt.reshape(4, 2) * ratio# 2. 執行透視變換(輸入原圖,避免縮放導致的細節丟失)
warped = four_point_transform(orig, screen_cnt_org)# 3. 保存并展示校正后的發票
cv2.imwrite("fapiao_corrected.jpg", warped)
cv_show("校正后發票", warped)

校正后的發票如下:

(5)步驟 5:二值化與形態學優化(為 OCR 準備)

校正后的圖像需進一步處理為 “白底黑字”,減少噪聲干擾,便于后續 OCR 文字識別:

# 1. 灰度化校正后的圖像
warped_gray = cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY)# 2. 二值化(轉為黑白圖像,THRESH_BINARY_INV表示黑底白字→白底黑字)
ref = cv2.threshold(warped_gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]# 3. 形態學閉運算(先膨脹再腐蝕,填充文字內部的小孔)
kernel = np.ones((2, 2), np.uint8)  # 2x2結構元素
ref_processed = cv2.morphologyEx(ref, cv2.MORPH_CLOSE, kernel)# 4. 縮放并展示最終結果(寬度固定為800,便于查看)
final_result = resize_image(ref_processed, width=800)
cv_show("最終白底黑字效果", final_result)# 保存最終結果
cv2.imwrite("fapiao_final.jpg", final_result)
cv2.destroyAllWindows()  # 關閉所有窗口

運行結果如下:

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/96773.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/96773.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/96773.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

并發:使用volatile和不可變性實現線程安全

《Java并發編程實戰》中的VolatileCachedFactorizer展示了如何使用volatile和不可變性來實現線程安全。解決了簡單緩存實現中可能出現的線程安全問題,同時避免了全量同步帶來的性能開銷。 場景背景 假設有一個服務(如因數分解服務)&#xff0…

Linux x86 stability和coredump

1 POSIX pthread_create原理 1)fork()、pthread_create()、vfork()對應的系統調用分別是sys_fork()、sys_clone()、sys_vfork(),它們在內核中都是通過do_fork()實現的。 2)系統中所有的進程都組織在init_task.tasks鏈表下面,每個進…

【PyTorch】多對象分割

對象分割任務的目標是找到圖像中目標對象的邊界。實際應用例如自動駕駛汽車和醫學成像分析。這里將使用PyTorch開發一個深度學習模型來完成多對象分割任務。多對象分割的主要目標是自動勾勒出圖像中多個目標對象的邊界。 對象的邊界通常由與圖像大小相同的分割掩碼定義&#xf…

RabbitMQ---面試題

總結我們所學內容,這里推薦博客進行復習 RabbitMQ---面試題_rabbitmq常問面試題-CSDN博客

MasterGo自動布局(Auto Layout)

自動布局是用來表示 子元素與子元素之間互相影響的一種排版方式,是一種響應式布局技術。一般是將所有元素設計完成后再使用自動布局進行設置。 自動布局就是響應式布局,就是在不同尺寸的手機上寬度不同都應該怎么展示。 一般頁面的一級元素使用約束進行相對定位,二級元素及里…

還在重啟應用改 Topic?Spring Boot 動態 Kafka 消費的“終極形態”

場景描述: 你的一個微服務正在穩定地消費 Kafka 的 order_topic。現在,上游系統為了做業務隔離,新增加了一個 order_topic_vip,并開始向其中投遞 VIP 用戶的訂單。你需要在不重啟、不發布新版本的情況下,讓你現有的消費…

使用vllm部署neo4j的text2cypher-gemma-2-9b-it-finetuned-2024v1模型

使用vllm部署neo4j的text2cypher-gemma-2-9b-it-finetuned-2024v1模型 系統環境準備 由于使用的基于 nvcr.io/nvidia/cuda:12.1.1-cudnn8-runtime-ubuntu22.04 的 workbench,需要進行以下準備(其他系統環境可忽略) ldconfig -p | grep libcudnn 找到 libcudnn 的so庫,然…

Coze源碼分析-資源庫-創建知識庫-前端源碼-核心組件

概述 本文深入分析Coze Studio中用戶創建知識庫功能的前端實現。該功能允許用戶在資源庫中創建、編輯和管理知識庫資源,為開發者提供了強大的知識管理和數據處理能力。通過對源碼的詳細解析,我們將了解從資源庫入口到知識庫配置彈窗的完整架構設計、組件…

基于時空數據的網約車訂單需求預測與調度優化

一、引言隨著共享出行行業的蓬勃發展,網約車已成為城市交通的重要組成部分。如何精準預測訂單需求并優化車輛調度,是提升平臺運營效率、改善用戶體驗的關鍵。本文提出一種基于時空數據的網約車訂單需求預測與調度優化方案,通過網格化城市空間…

數據結構 Java對象的比較

在Java中&#xff0c;凡是涉及到比較的&#xff0c;可以分為兩類情況&#xff1a;一類是基本數據類型的比較&#xff0c;另一類是引用數據類型的比較。對于基本數據類型的比較&#xff0c;我們通過關系運算符&#xff08;、>、<、!、>、<&#xff09;進行它們之間的…

企智匯建筑施工項目管理系統:全周期數字化管控,賦能工程企業降本增效!?建筑工程項目管理軟件!建筑工程項目管理系統!建筑項目管理軟件企智匯軟件

在建筑施工行業&#xff0c;項目進度滯后、成本超支、質量安全隱患頻發、多方協同不暢等問題&#xff0c;一直是制約企業發展的痛點。傳統依賴人工記錄、Excel 統計的管理模式&#xff0c;不僅效率低下&#xff0c;更易因信息斷層導致決策失誤。企智匯建筑施工項目管理系統憑借…

k8s-臨時容器學習

臨時容器學習1. 什么是臨時容器2. 實驗1. 什么是臨時容器 在官網&#xff1a;https://kubernetes.io/zh-cn/docs/concepts/workloads/pods/ephemeral-containers/ 中有介紹 臨時容器是用于調試Pod中崩潰的容器或者不具備調試工具&#xff0c;比如在一個運行著業務的容器中&am…

Python 2025:低代碼開發與自動化運維的新紀元

從智能運維到無代碼應用&#xff0c;Python正在重新定義企業級應用開發范式在2025年的企業技術棧中&#xff0c;Python已經從一個"開發工具"演變為業務自動化的核心平臺。根據Gartner 2025年度報告&#xff0c;68%的企業在自動化項目中使用Python作為主要開發語言&am…

Netty 在 API 網關中的應用篇(請求轉發、限流、路由、負載均衡)

Netty 在 API 網關中的應用篇&#xff08;請求轉發、限流、路由、負載均衡&#xff09;隨著微服務架構的普及&#xff0c;API 網關成為服務之間通信和安全控制的核心組件。在構建高性能網關時&#xff0c;Netty 因其高吞吐、低延遲和異步非阻塞 IO 的特性&#xff0c;成為不少開…

基于STM32設計的青少年學習監控系統(華為云IOT)_282

文章目錄 一、前言 1.1 項目介紹 【1】項目開發背景 【2】設計實現的功能 【3】項目硬件模塊組成 【4】設計意義 【5】國內外研究現狀 【6】摘要 1.2 設計思路 1.3 系統功能總結 1.4 開發工具的選擇 【1】設備端開發 【2】上位機開發 1.5 參考文獻 1.6 系統框架圖 1.7 系統原理…

手寫Spring底層機制的實現【初始化IOC容器+依賴注入+BeanPostProcesson機制+AOP】

摘要&#xff1a;建議先看“JAVA----Spring的AOP和動態代理”這個文章&#xff0c;解釋都在代碼中&#xff01;一&#xff1a;提出問題依賴注入1.單例beans.xml<?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.springframe…

5G NR-NTN協議學習系列:NR-NTN介紹(2)

NTN網絡作為依賴衛星的通信方式&#xff0c;需要面對的通信距離&#xff0c;通信雙方的移動速度都和之前TN網絡存在巨大差異。在距離方面相比蜂窩地面網絡Terrestrial Network通信距離從最小幾百米到最大幾十km的情況&#xff0c;NTN非地面網絡的通信距離即使是近地軌道的LEO衛…

線掃相機采集圖像起始位置不正確原因總結

1、幀觸發開始時間問題 問題描述: 由于幀觸發決定了線掃相機的開始采集圖像位置,比如正確的位置是A點開始采集,結果你從B點開始觸發幀信號,這樣出來的圖像起始位置就不對 解決手段: 軟件需要記錄幀觸發時軸的位置 1)控制卡控制軸 一般使用位置比較觸發,我們可以通過監…

校園管理系統練習項目源碼-前后端分離-【node版】

今天給大家分享一個校園管理系統&#xff0c;前后端分離項目。這是最近在練習前端編程&#xff0c;結合 node 寫的一個完整的項目。 使用的技術&#xff1a; Node.js&#xff1a;版本要求16.20以上。 后端框架&#xff1a;Express框架。 數據庫&#xff1a; MySQL 8.0。 Vue2&a…

【項目】 :C++ - 仿mudou庫one thread one loop式并發服務器實現(模塊劃分)

【項目】 &#xff1a;C - 仿mudou庫one thread one loop式并發服務器實現一、HTTP 服務器與 Reactor 模型1.1、HTTP 服務器概念實現步驟難點1.2、Reactor 模型概念分類1. 單 Reactor 單線程2. 單 Reactor 多線程3. 多 Reactor 多線程目標定位總結二、功能模塊劃分2.1、SERVER …