基于OpenCV的實時文檔掃描與矯正技術

文章目錄

    • 引言
    • 一、系統概述
    • 二、核心代碼解析
      • 1. 導入必要庫
      • 2. 輔助函數定義
      • 3. 坐標點排序函數
      • 4. 透視變換函數
      • 5. 主程序流程
    • 三、完整代碼
    • 四、結語

引言

在日常工作和學習中,我們經常需要將紙質文檔數字化。手動拍攝文檔照片常常會出現角度傾斜、透視變形等問題,影響后續使用。本文將介紹如何使用Python和OpenCV構建一個實時文檔掃描與矯正系統,能夠通過攝像頭自動檢測文檔邊緣并進行透視變換矯正。

一、系統概述

該系統主要實現以下功能:

  1. 實時攝像頭捕獲圖像
  2. 邊緣檢測和輪廓查找
  3. 文檔輪廓識別
  4. 透視變換矯正文檔
  5. 二值化處理增強可讀性

二、核心代碼解析

1. 導入必要庫

import numpy as np
import cv2

我們主要使用NumPy進行數值計算,OpenCV進行圖像處理。

2. 輔助函數定義

首先定義了一個簡單的圖像顯示函數,方便調試:

def cv_show(name,img):cv2.imshow(name,img)cv2.waitKey(10)

3. 坐標點排序函數

order_points函數用于將檢測到的文檔四個角點按順序排列(左上、右上、右下、左下):

def order_points(pts):rect = np.zeros((4,2),dtype="float32")s = pts.sum(axis=1)rect[0] = pts[np.argmin(s)]  # 左上點(x+y最小)rect[2] = pts[np.argmax(s)]  # 右下點(x+y最大)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個二維坐標點進行排序,使其按照左上、右上、右下、左下的順序排列。這在文檔掃描、圖像矯正等應用中非常重要,因為我們需要知道每個角點的確切位置才能正確地進行透視變換。

函數詳細解析

(1)排序邏輯說明

  1. 左上點(rect[0]):選擇x+y值最小的點

    • 因為左上角在坐標系中 x 和 y 值都較小,相加結果最小
  2. 右下點(rect[2]):選擇x+y值最大的點

    • 因為右下角在坐標系中 x 和 y 值都較大,相加結果最大
  3. 右上點(rect[1]):選擇y-x值最小的點

    • 右上角的特點是 y 相對較小而 x 相對較大,所以 y-x 值最小
  4. 左下點(rect[3]):選擇y-x值最大的點

    • 左下角的特點是 y 相對較大而 x 相對較小,所以 y-x 值最大

(2)示例

假設有4個點:

	A(10, 20)  # 假設是左上B(50, 20)  # 右上C(50, 60)  # 右下D(10, 60)  # 左下

計算過程:

  1. x+y值:[30, 70, 110, 70]

    • 最小30 → A(左上)
    • 最大110 → C(右下)
  2. y-x值:[10, -30, 10, 50]

    • 最小-30 → B(右上)
    • 最大50 → D(左下)

最終排序結果:[A, B, C, D] 即 [左上, 右上, 右下, 左下]

(3)為什么這種方法有效

這種方法利用了二維坐標點的幾何特性:

  • 在標準坐標系中,左上角的x和y值都較小
  • 右下角的x和y值都較大
  • 右上角的x較大而y較小
  • 左下角的x較小而y較大

通過簡單的加減運算就能可靠地區分出各個角點,不需要復雜的幾何計算。

4. 透視變換函數

four_point_transform函數實現了文檔矯正的核心功能:

def four_point_transform(image,pts):rect = order_points(pts)(tl,tr,br,bl) = rect# 計算變換后的寬度和高度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

這個函數實現了透視變換(Perspective Transformation),用于將圖像中的任意四邊形區域矯正為一個矩形(即"去透視"效果)。

函數詳細解析

  1. 輸入參數
def four_point_transform(image, pts):
  • image: 原始圖像
  • pts: 包含4個點的數組,表示要轉換的四邊形區域
  1. 坐標點排序
rect = order_points(pts)
(tl, tr, br, bl) = rect  # 分解為左上(top-left)、右上(top-right)、右下(bottom-right)、左下(bottom-left)

使用之前介紹的order_points函數將4個點按順序排列

  1. 計算輸出圖像的寬度
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))  # 取最大值作為輸出圖像寬度

計算四邊形底部和頂部的邊長,選擇較長的作為輸出寬度

  1. 計算輸出圖像的高度
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))  # 取最大值作為輸出圖像高度

計算四邊形右側和左側的邊長,選擇較長的作為輸出高度

  1. 定義目標矩形坐標
dst = np.array([[0, 0],  # 左上[maxWidth - 1, 0],  # 右上[maxWidth - 1, maxHeight - 1],  # 右下[0, maxHeight - 1]  # 左下
], dtype="float32")

定義變換后的矩形角點坐標(從(0,0)開始的正矩形)

  1. 計算透視變換矩陣并應用
M = cv2.getPerspectiveTransform(rect, dst)  # 計算變換矩陣
warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))  # 應用變換
  • getPerspectiveTransform: 計算從原始四邊形到目標矩形的3x3變換矩陣
  • warpPerspective: 應用這個變換矩陣到原始圖像
  1. 返回結果
return warped

返回矯正后的矩形圖像

  1. 透視變換原理圖示
原始圖像中的四邊形               變換后的矩形tl--------tr                    0--------maxWidth\        /                      |        |\      /                       |        |bl----br                       maxHeight
  1. 為什么需要這樣計算寬度和高度?

取最大值的原因

  • 原始四邊形可能有透視變形,兩條對邊長度可能不等
  • 選擇較大的值可以確保所有內容都能包含在輸出圖像中

減1的原因

  • 圖像坐標從0開始,所以寬度為maxWidth的圖像,最大x坐標是maxWidth-1

5. 主程序流程

主程序實現了實時文檔檢測和矯正的完整流程:

  1. 初始化攝像頭
cap = cv2.VideoCapture(0)
if not cap.isOpened():print("Cannot open camera")exit()
  1. 實時處理循環
while True:flag = 0ret,image = cap.read()orig = image.copy()if not ret:print("不能讀取攝像頭")break
  1. 圖像預處理
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray,(5,5),0)  # 高斯濾波降噪
edged = cv2.Canny(gray,75,200)  # Canny邊緣檢測
  1. 輪廓檢測與篩選
cnts = cv2.findContours(edged,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[-2]
cnts = sorted(cnts,key=cv2.contourArea,reverse=True)[:3]  # 取面積最大的3個輪廓for c in cnts:peri = cv2.arcLength(c,True)  # 計算輪廓周長approx = cv2.approxPolyDP(c,0.05 * peri,True)  # 多邊形近似area = cv2.contourArea(approx)# 篩選四邊形且面積足夠大的輪廓if area > 20000 and len(approx) == 4:screenCnt = approxflag = 1break
  1. 文檔矯正與顯示
if flag == 1:# 繪制輪廓image_contours = cv2.drawContours(image,[screenCnt],0,(0,255,0),2)# 透視變換warped = four_point_transform(orig,screenCnt.reshape(4,2))# 二值化處理warped = cv2.cvtColor(warped,cv2.COLOR_BGR2GRAY)ref = cv2.threshold(warped,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]

三、完整代碼

# 導入工具包
import numpy as np
import cv2def cv_show(name,img):cv2.imshow(name,img)cv2.waitKey(10)
def order_points(pts):# 一共4個坐標點rect = np.zeros((4,2),dtype="float32") # 用來存儲排序之后的坐標位置# 按順序找到對應坐標0123分別是 左上、右上、右下、左下s = pts.sum(axis=1) #對pts矩陣的每一行進行求和操作,(x+y)rect[0] = pts[np.argmin(s)]rect[2] = pts[np.argmax(s)]diff = np.diff(pts,axis=1) #對pts矩陣的每一行進行求差操作,(y-x)rect[1] = pts[np.argmin(diff)]rect[3] = pts[np.argmax(diff)]return rectdef 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# 讀取輸入
import cv2
cap = cv2.VideoCapture(0)  # 確保攝像頭是可以啟動的狀態
if not cap.isOpened():   #打開失敗print("Cannot open camera")exit()while True:flag = 0 # 用于標時 當前是否檢測到文檔ret,image = cap.read()  # 如果正確讀取幀,ret為Trueorig = image.copy()if not ret: #讀取失敗,則退出循環print("不能讀取攝像頭")breakcv_show("image",image)gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)# 預處理gray = cv2.GaussianBlur(gray,(5,5),0) # 高斯濾波edged = cv2.Canny(gray,75,200)cv_show('1',edged)# 輪廓檢測cnts = cv2.findContours(edged,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[-2]cnts = sorted(cnts,key=cv2.contourArea,reverse=True)[:3]image_contours = cv2.drawContours(image,cnts,-1,(0,255,0),2)cv_show("image_contours",image_contours)# 遍歷輪廓for c in cnts:# 計算輪廓近似peri = cv2.arcLength(c,True) # 計算輪廓的周長# C 表示輸入的點集# epsilon表示從原始輪廓到近似輪廓的最大距離,它是一個準確度參數# True表示封閉的approx = cv2.approxPolyDP(c,0.05 * peri,True) # 輪廓近似area = cv2.contourArea(approx)# 4個點的時候就拿出來if area > 20000 and len(approx) == 4:screenCnt = approxflag = 1print(peri,area)print("檢測到文檔")breakif flag == 1:# 展示結果# print("STEP 2: 獲取輪廓")image_contours = cv2.drawContours(image,[screenCnt],0,(0,255,0),2)cv_show("image",image_contours)# 透視變換warped = four_point_transform(orig,screenCnt.reshape(4,2))cv_show("warped",warped)# 二值處理warped = cv2.cvtColor(warped,cv2.COLOR_BGR2GRAY)# ref = cv2.threshold(warped,220,255,cv2.THRESH_BINARY)[1]ref = cv2.threshold(warped,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]cv_show("ref",ref)
cap.release() # 釋放捕捉器
cv2.destroyAllWindows() #關閉圖像窗口

四、結語

本文介紹了一個基于OpenCV的實時文檔掃描與矯正系統,通過邊緣檢測、輪廓分析和透視變換等技術,實現了文檔的自動檢測和矯正。該系統可以方便地應用于日常文檔數字化工作,提高工作效率。

完整代碼已在上文中給出,讀者可以根據自己的需求進行修改和擴展。OpenCV提供了強大的圖像處理能力,結合Python的簡潔語法,使得開發這樣的實用系統變得簡單高效。

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

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

相關文章

jenkins pipeline實現CI/CD

在企業級的架構中,CI/CD是必不可少的一個環節,它可以讓開發人員只關注于開發,而不必去關注項目的構建和部署,從而提高開發人員的效率。 本文我們來介紹一下使用jenkins 的pipeline來進行java項目的自動構建以及部署。我們通過腳本…

InfluxDB 3 Core + Java 11 + Spring Boot:打造高效物聯網數據平臺

一、 引言:為什么選擇InfluxDB 3? 項目背景: 在我們的隧道風機監控系統中,實時數據的采集、存儲和高效查詢是至關重要的核心需求。風機運行產生的振動、傾角、電流、溫度等參數是典型的時序數據,具有高并發寫入、數據…

泰國SAP ERP實施如何應對挑戰?工博科技賦能中企出海EEC戰略

泰國正依托"東部經濟走廊(EEC)"與RCEP協定疊加優勢,為中國企業提供面向亞太市場的戰略機遇。作為2022年泰國主要外資來源國之一,中國企業通過電子制造、智能家電、數據中心及新能源車等領域的投資深度參與泰國"4.0…

【設計模式】- 創建者模式

單例模型 餓漢式 靜態方法創建對象 public class Singleton {// 私有構造方法private Singleton(){}private static Singleton instance new Singleton();// 提供一個外界獲取的方法public static Singleton getInstance(){return instance;} }靜態代碼塊創建對象 public …

邏輯與非邏輯的彌聚

非邏輯彌聚與邏輯彌聚是復雜系統中兩種不同的信息整合方式。邏輯彌聚側重于通過明確的規則、規律和結構化方法,將分散的信息或功能進行有序的組織和集中處理,強調理性和確定性。而非邏輯彌聚則更多地涉及情感、直覺、經驗等非線性、非結構化的因素&#…

Linux進程信號(三)之信號產生2

文章目錄 4. 由軟件條件產生信號5. 硬件異常產生信號模擬一下除0錯誤和野指針異常除0錯誤野指針錯誤 總結思考一下 4. 由軟件條件產生信號 SIGPIPE是一種由軟件條件產生的信號,在“管道”中已經介紹過了。 軟件條件不就緒,很明顯這個軟件條件沒有直接報錯&#xff…

讀取18B20的問題,時鐘太慢了

使用MSP430,1M時鐘,在讀取18B20數據時,一直存在問題,使用邏輯分析儀讀取的數據也是莫名其妙,查看電路圖和器件也沒有發現問題,就這樣斷斷續續的卡了一周多。 今天忽然想把時鐘升一下試試,原來1…

第12章 Java多線程機制

12.1 進程與線程 4種狀態:新建、運行、中斷和死亡。 (新建、運行、中斷和死亡) 建立線程的兩種方法:用Thread類或其子類。 線程新建后,必須調用 start () 方法使其進入就緒隊列,才有機會獲得 CPU 資源&a…

利用 Amazon Bedrock Data Automation(BDA)對視頻數據進行自動化處理與檢索

當前點播視頻平臺搜索功能主要是基于視頻標題的關鍵字檢索。對于點播平臺而言,我們希望可以通過優化視頻搜索體驗滿足用戶通過模糊描述查找視頻的需求,從而提高用戶的搜索體驗。借助 Amazon Bedrock Data Automation(BDA)技術&…

React 19版本refs也支持清理函數了。

文章目錄 前言一、refs 支持清理函數二、案例演示1.useEffect寫法2.React 19改進 的ref寫法 總結 前言 React 19版本發布了ref支持清理函數了,這樣就可以達到useEffect一樣的效果了。為啥需要清理函數呢,這是因為節約內存。 清理事件監聽(避…

城市內澇監測預警系統守護城市安全

一、系統背景 城市內澇是指由于強降水或連續性降水超過城市排水能力,導致城市內產生積水災害的現象。隨著氣候變化和城市化進程的加快,城市內澇現象愈發頻繁和嚴重。傳統的城市排水系統已難以滿足當前的城市排水需求,特別是在暴雨等極端天氣條…

Flink 作業提交流程

Apache Flink 的 作業提交流程(Job Submission Process) 是指從用戶編寫完 Flink 應用程序,到最終在 Flink 集群上運行并執行任務的整個過程。它涉及多個組件之間的交互,包括客戶端、JobManager、TaskManager 和 ResourceManager。…

ctr查看鏡像

# 拉取鏡像到 k8s.io 命名空間 sudo nerdctl --namespace k8s.io pull nginx:1.23.4 # 驗證鏡像是否已下載 sudo nerdctl --namespace k8s.io images 下載鏡像到k8s.io名稱空間下 nerdctl --namespace k8s.io pull zookeeper:3.6.2 sudo ctr image pull --namespace k8s.io …

中科院自動化研究所通用空中任務無人機!基于大模型的通用任務執行與自主飛行

作者: Ji Zhao and Xiao Lin 單位:中科院自動化研究所 論文標題:General-Purpose Aerial Intelligent Agents Empowered by Large Language Models 論文鏈接:https://arxiv.org/pdf/2503.08302 主要貢獻 硬件-軟件協同設計框…

數據結構 -- 樹形查找(三)紅黑樹

紅黑樹 為什么要發明紅黑樹 平衡二叉樹AVL:插入/刪除很容易破壞平衡性,需要頻繁調整樹的形態。如:插入操作導致不平衡,則需要先計算平衡因子,找到最小不平衡子樹(時間開銷大),在進行…

容器化-k8s-使用和部署

一、K8s 使用 1、基本概念 集群: 由 master 節點和多個 slaver 節點組成,是 K8s 的運行基礎。節點: 可以是物理機或虛擬機,是 K8s 集群的工作單元,運行容器化應用。Pod: K8s 中最小的部署單元,一個 Pod 可以包含一個或多個緊密相關的容器,這些容器共享網絡和存儲資源。…

力扣-283-移動零

1.題目描述 2.題目鏈接 283. 移動零 - 力扣&#xff08;LeetCode&#xff09; 3.題目代碼 class Solution {public void moveZeroes(int[] nums) {int dest-1;int cur0;while(cur<nums.length){if(nums[cur]0){cur;}else if(nums[cur]!0){swap(nums,cur,dest1);cur;dest…

前端開發筆記與實踐

一、Vue 開發規范與響應式機制 1. 組件命名規范 自定義組件使用大駝峰命名法&#xff08;如 MyComponent&#xff09;&#xff0c;符合 Vue 官方推薦&#xff0c;便于與原生 HTML 元素區分。 2. Proxy vs defineProperty 特性Proxy&#xff08;Vue3&#xff09;Object.defi…

如何給PSCAD添加庫文件

1、點擊Options 2、選擇藍色的選項 3、查看Intel(R) Visual Fortran Compiler XE 的版本 4、打開原文件的Library 5、打開 6、點擊這個文件的右鍵 7、然后選擇第一項project setting 9、先把第8步中link里面原有的路徑刪除&#xff0c;再點browes[A1] &#xff0c;然后選擇 [A…

milvus+flask山寨《從零構建向量數據庫》第7章case2

繼續流水賬完這本書&#xff0c;這個案例是打造文字形式的個人知識庫雛形。 create_context_db: # Milvus Setup Arguments COLLECTION_NAME text_content_search DIMENSION 2048 MILVUS_HOST "localhost" MILVUS_PORT "19530"# Inference Arguments…