使用OpenCV,構(gòu)建文檔掃描儀
本文將使用 OpenCV,創(chuàng)建一個(gè)簡(jiǎn)單的文檔掃描儀,就像常用的攝像頭掃描儀應(yīng)用程序一樣。這個(gè)想法很簡(jiǎn)單,因?yàn)槲覀冋J(rèn)為文檔是四邊形,我們獲取邊緣的位置并使用它來(lái)抓取文檔本身,而忽略無(wú)用的圖像背景。
簡(jiǎn)單的管道:加載圖像>>檢測(cè)邊緣和抓取位置>>使用圖像上的位置
導(dǎo)入包
首先,我們導(dǎo)入處理圖像可能需要的包。threshold_local 函數(shù)對(duì)你來(lái)說(shuō)可能看起來(lái)很新,但這段代碼其實(shí)沒有什么特別之處。該函數(shù)來(lái)自 scikit 圖像包。
# import packages
from skimage.filters import threshold_local
import numpy as np
import cv2
import imutils
加載圖像。
在這里,我們加載圖像并保留一份副本。在這里,原始的副本對(duì)于獲得清晰且未經(jīng)篡改的圖像掃描非常重要。為了處理圖像,我調(diào)整到一個(gè)合理的比例,接下來(lái)我對(duì)圖像進(jìn)行灰度化以減少顏色并使其模糊(即有助于從圖像背景中去除高頻噪聲),這些都是為了找到文件的邊緣。
#load in the image
image = cv2.imread("images/questions.jpg")
orig = image.copy()
#Resize the image.
height = image.shape[0]
width = image.shape[1]
ratio = 0.2
width = int(ratio * width)
height = int(ratio * height)
image = cv2.resize(image,(width, height))
#find edges in the image.
gray_scaled = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
#blurr the image
gray_scaled = cv2.GaussianBlur(gray_scaled,(5,5),0)
#Edge detection
edged = cv2.Canny(gray_scaled,50, 200)
cv2.imshow("Image", image)
cv2.waitKey(0)
cv2.imshow("Edges detected", edged)
cv2.waitKey(0)
找到輪廓。
使用 cv2.findcontours() 找到輪廓。接下來(lái),我們使用 imutils 庫(kù)抓取輪廓,最后,我們根據(jù)最大輪廓區(qū)域,對(duì)抓取的輪廓進(jìn)行排序。在這種情況下,我保留了最大的 5 個(gè)
# find contours in the edged image. keep only the largest contours.
contours = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
# grab contours
contours = imutils.grab_contours(contours)
# select contours based on size.
contours = sorted(contours, key=cv2.contourArea, reverse = True)[:5]
對(duì)輪廓進(jìn)行進(jìn)一步處理。
首先,我們遍歷輪廓并找到周長(zhǎng),這是將周長(zhǎng)近似為點(diǎn)所必需的。完成此操作后,我們最終搜索恰好具有 4 個(gè)點(diǎn)的輪廓,這很可能是近似矩形形狀的紙張。完成后,我們獲取這些點(diǎn)的坐標(biāo),然后將它們初始化為紙張輪廓。
# loop over the contours.
for contour in contours:
perimeter = cv2.a(chǎn)rcLength(contour, True)
# approximate your contour
approximation = cv2.a(chǎn)pproxPolyDP(contour, 0.02*perimeter, True)
# if our contour has 4 points, then surely, it should be the paper.
if len(approximation) == 4:
paper_outline = approximation
break
有了坐標(biāo),下一步就是畫輪廓,很簡(jiǎn)單。
# Draw the found contour.
cv2.drawContours(image,[paper_outline],-1,(225,0,0),2)
cv2.imshow("Found outline", image)
cv2.waitKey(0)
你心中的問(wèn)題是,我們完成了嗎?
好吧,你可能會(huì)說(shuō)是的,因?yàn)槟阍趫D像周圍設(shè)置了很好的輪廓。答案是否定的,為了獲得最佳掃描形式的圖像,我們需要 90 度的圖像視圖,尤其是在傾斜的情況下。為此,我們將創(chuàng)建一個(gè)函數(shù)來(lái)處理此任務(wù)。
管道:排列點(diǎn)>>標(biāo)記點(diǎn)>>從真實(shí)圖像中挑選點(diǎn)
arrange_points 函數(shù)。
這樣做的方法非常簡(jiǎn)單,歸功于 Adrian Rosebrock(博士)。這個(gè)函數(shù)背后的直覺是我們獲取文檔四個(gè)邊緣的坐標(biāo),并將其安排到我們認(rèn)為它應(yīng)該在的位置,我花了一些時(shí)間給出描述的圖形表示。
點(diǎn)坐標(biāo)的和
1)從上圖中我們可以看出,點(diǎn)坐標(biāo)(X,Y)的和最大的是在右上角。
2)最小的點(diǎn)總和是左下點(diǎn)。
點(diǎn)坐標(biāo)的差
3)點(diǎn)坐標(biāo)的差的最大值是左上角
4)點(diǎn)坐標(biāo)的差的最小值是左下角。
代碼。
該函數(shù)接受參數(shù)points,接下來(lái),我初始化一個(gè) NumPy 數(shù)組來(lái)表示矩形,該數(shù)組是一個(gè) 4 x 2 矩陣,因?yàn)槲覀冇?4 個(gè)點(diǎn)和 2 個(gè)坐標(biāo)(X,Y)。
最后,如上所述,我在矩形的點(diǎn)中注冊(cè)(點(diǎn)的和以及點(diǎn)的差)。最后,我正確地返回了 Rectangle 的坐標(biāo)。
def arrange_points(points):
# initialize a list of co-ordinates that will be ordered
# first entry is top-left point, second entry is top-right
# third entry is bottom-right, forth/last point is the bottom left point.
rectangle = np.zeros((4,2), dtype = "float32")
# bottom left point should be the smallest sum
# the top-right point will have the largest sum of point.
sum_points= points.sum(axis =1)
rectangle[0] = points[np.a(chǎn)rgmin(sum_points)]
rectangle[2] = points[np.a(chǎn)rgmax(sum_points)]
#bottom right will have the smallest difference
#top left will have the largest difference.
diff_points = np.diff(points, axis=1)
rectangle[1] = points[np.a(chǎn)rgmin(diff_points)]
rectangle[3] = points[np.a(chǎn)rgmax(diff_points)]
# return order of co-ordinates.
return rectangle
設(shè)置四個(gè)點(diǎn)。
這個(gè)功能很簡(jiǎn)單,這里的想法當(dāng)然是拉直紙張,只提取需要的區(qū)域。在這里,輸入是 1) 圖像本身和點(diǎn)或坐標(biāo)。首先,我們使用我們創(chuàng)建的第一個(gè)函數(shù)“arrange_points”來(lái)排列函數(shù)的點(diǎn)。接下來(lái),我相應(yīng)地分配了點(diǎn),因?yàn)槲抑耙呀?jīng)安排了點(diǎn)并且也很好地命名了它們。
計(jì)算。
對(duì)于計(jì)算,只需兩點(diǎn)之間的距離即可找到每邊的長(zhǎng)度。有了這個(gè),我們能夠在對(duì)的位置上防止錯(cuò)誤地調(diào)整圖像。顧名思義,目的地是圖像的新視圖。其中 [0,0] 表示左上角。接下來(lái),[Max-width - 1,0] 表示右上角,我們還有 [maxwidth -1, maxheight-1] 表示底部右上角,最后是左下角 [0, max-h(huán)eight -1]。
轉(zhuǎn)換矩陣
動(dòng)作完成,工作結(jié)束,我們需要完成的是使用 cv2.getPerspectiveTransform() 的變換矩陣,它接受點(diǎn)的矩形和目的地,F(xiàn)在我們有了矩陣,我們使用 cv2.warpPerspective() 應(yīng)用它,它獲取你提供給函數(shù)的圖像、變換矩陣,最后是建議掃描的(寬度和長(zhǎng)度)。全部完成,返回轉(zhuǎn)換后的圖像
# set four points.
def set_four_points(image, points):
# obtain order of points and unpack.
rectangle = arrange_points(points)
(top_left,top_right,bottom_right,bottom_left) = rectangle
# let's compute width of the rectangle.
# using formular for distance between two points
left_h(yuǎn)eight = np.sqrt(((top_left[0]-bottom_left[0])**2) + ((top_left[1]-bottom_left[1])**2))
right_h(yuǎn)eight = np.sqrt(((top_right[0]-bottom_right[0])**2) + ((top_right[1]-bottom_right[1])**2))
top_width = np.sqrt(((top_right[0]-top_left[0])**2) + ((top_right[1]-top_left[1])**2))
bottom_width = np.sqrt(((bottom_right[0]-bottom_left[0])**2) + ((bottom_right[1]-bottom_left[1])**2))
maxheight = max(int(left_h(yuǎn)eight), int(right_h(yuǎn)eight))
maxwidth = max(int(top_width), int(bottom_width))
destination = np.a(chǎn)rray([
[0,0],
[maxwidth -1,0],
[maxwidth -1, maxheight-1],
[0, maxheight - 1]], dtype = "float32")
matrix = cv2.getPerspectiveTransform(rectangle, destination)
warped = cv2.warpPerspective(image, matrix, (maxwidth,maxheight))
return warped
應(yīng)用函數(shù)
我們已經(jīng)創(chuàng)建了函數(shù),因此我們將其應(yīng)用于最初保存的原始圖像。第二個(gè)輸入是論文的大綱。我通過(guò)刪除我在開始時(shí)所做的比例縮放,將紙張輪廓重新調(diào)整回原來(lái)的大小。要獲得圖像的黑白感覺,需要使用 Threshold local,但當(dāng)然,如果你想要對(duì)圖像進(jìn)行彩色掃描,則根本不需要它。最后,我調(diào)整大小并顯示。
warped = set_four_points(orig, paper_outline.reshape(4,2)*(1/ratio))
#warped = cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY)
#threshold = threshold_local(warped, 11, offset=10, method="gaussian")
#warped = (warped > threshold).a(chǎn)stype("uint8") * 255
#show the original and scanned images
print("Image Reset in progress")
cv2.imshow("Original", cv2.resize(orig,(width, height)))
cv2.imshow("Scanned",cv2.resize(warped,(width, height)))
cv2.waitKey(0)
干得好!,你剛剛創(chuàng)建了自己的掃描儀應(yīng)用程序。
原文標(biāo)題 : 構(gòu)建文檔掃描儀

發(fā)表評(píng)論
請(qǐng)輸入評(píng)論內(nèi)容...
請(qǐng)輸入評(píng)論/評(píng)論長(zhǎng)度6~500個(gè)字
最新活動(dòng)更多
-
3月27日立即報(bào)名>> 【工程師系列】汽車電子技術(shù)在線大會(huì)
-
4月30日立即下載>> 【村田汽車】汽車E/E架構(gòu)革新中,新智能座艙挑戰(zhàn)的解決方案
-
5月15-17日立即預(yù)約>> 【線下巡回】2025年STM32峰會(huì)
-
即日-5.15立即報(bào)名>>> 【在線會(huì)議】安森美Hyperlux™ ID系列引領(lǐng)iToF技術(shù)革新
-
5月15日立即下載>> 【白皮書】精確和高效地表征3000V/20A功率器件應(yīng)用指南
-
5月16日立即參評(píng) >> 【評(píng)選啟動(dòng)】維科杯·OFweek 2025(第十屆)人工智能行業(yè)年度評(píng)選
推薦專題
- 1 UALink規(guī)范發(fā)布:挑戰(zhàn)英偉達(dá)AI統(tǒng)治的開始
- 2 北電數(shù)智主辦酒仙橋論壇,探索AI產(chǎn)業(yè)發(fā)展新路徑
- 3 降薪、加班、裁員三重暴擊,“AI四小龍”已折戟兩家
- 4 “AI寒武紀(jì)”爆發(fā)至今,五類新物種登上歷史舞臺(tái)
- 5 國(guó)產(chǎn)智駕迎戰(zhàn)特斯拉FSD,AI含量差幾何?
- 6 光計(jì)算迎來(lái)商業(yè)化突破,但落地仍需時(shí)間
- 7 東陽(yáng)光:2024年扭虧、一季度凈利大增,液冷疊加具身智能打開成長(zhǎng)空間
- 8 地平線自動(dòng)駕駛方案解讀
- 9 封殺AI“照騙”,“淘寶們”終于不忍了?
- 10 優(yōu)必選:營(yíng)收大增主靠小件,虧損繼續(xù)又逢關(guān)稅,能否乘機(jī)器人東風(fēng)翻身?