1. 程式人生 > >手把手:掃描圖片又大又不清晰?這個Python小程式幫你搞定!

手把手:掃描圖片又大又不清晰?這個Python小程式幫你搞定!

大資料文摘作品

編譯:HAPPEN、於樂源、小魚

一位樂於分享學生精彩筆記的大學教授對於掃描版的檔案非常不滿意——顏色不清晰並且檔案巨大。他因此用python自己寫了一個小程式來解決這個問題。

這個程式可以用來整理手寫筆記的掃描件哦,輸出的圖片不僅很清晰,而且檔案大小隻有100多KB!

先來看一個例子:

左:輸入掃描件(300 DPI,7.2MB PNG/790KB JPG.)右:輸出圖片(300 DPI,121KB PNG)。

如果你急於上手操作,可以直接檢視Github repo中的程式碼,或跳到本文結果部分,看看炫酷的顏色簇互動式三維圖。

關注大資料文摘微信公眾號,後臺對話方塊內回覆“壓縮”下載noteshrink.py哦~

免責宣告:上述過程或許可以用Office Lens應用程式實現,或者其他工具也可以實現。本文只是一個實用方法分享,不是什麼發明創造。

起因

一些我任課的班級沒有指定的教材,這是因為我更喜歡每週指定一個“學生記錄員”,與班裡其他同學分享他們的講義。這樣可以為學生提供一些書面資源,以便他們需要時可以進行對照。筆記以PDF的格式釋出在課程網站。

在學校,我們有一臺能夠將筆記掃描成PDF檔案的“智慧”影印機,但是它生成的檔案不夠招人喜歡。下圖是手寫筆記的輸出示例:

影印機好像隨意地決定是否將每個數學符號進行二值化,或者轉換後的JPG很不理想(如上圖中的平方根符號)。因此我決定對上述問題進行優化。

概述

我們從某位同學一頁漂亮的筆記開始處理,筆記掃描件如下:

以300 DPI精度掃描的原始PNG影象大小約為7.2MB;轉換為影象品質較高的JPG格式後,檔案大小約為790KB。由於PDF掃描件通常採用PNG或JPG作為容器格式,我們當然不希望在轉換為PDF時損失檔案資訊。

但是考慮到網頁載入時間,每頁筆記800KB已經相當大了,我希望獲得檔案大小更接近100KB/頁的影象。

雖然這位學生的筆記很整潔,但筆記的掃描件看起來有點亂。原因是影印機將這頁筆記的反面內容也進行了掃描,這會分散讀者的注意力,而對於JPG或PNG編碼器來說,這種情況比純色背景的圖片更難壓縮。

下圖是我寫的noteshrink.py程式的輸出結果:

輸出結果是一個相對較小的PNG檔案,大小隻有121KB。不僅影象記憶體變小,而且看起來更清晰!這才是我想要的!

處理過程和彩色影象基礎

以下是生成小記憶體且清晰的影象所需的步驟:

1.識別原始掃描影象的背景色。

2.根據背景色的不同閾值分離出前景色。

3.從前景色中選擇幾種“代表性顏色”,作為生成PNG過程中需要的索引色。

在深入研究這些步驟之前,先來了解下彩色影象是如何以數字形式進行儲存的。由於人類眼睛中有三種不同型別的感色細胞,因此我們可以通過組合不同強度的紅色、綠色和藍色來重建任何顏色。重構過程就是將每種顏色與RGB顏色空間中的三維點一一對應,如下所示:

儘管真正的向量空間允許無限數量的畫素亮度連續變化,但為了將顏色以數字形式儲存在計算機上,我們需要對上述畫素範圍進行離散處理——通常紅色、綠色和藍色分別用8位通道色表示。這種將畫素類比成三維色彩空間座標的分析方法將為我們接下來的理解與重建提供巨大的幫助。

識別背景色

由於頁面的大部分地方沒有墨跡或線條,也許有人會認為紙張本身的顏色將會是掃描影象中出現頻率最高的一種顏色——即影印機會將白紙的每個畫素表示為相同的RGB值。

如果結果真是這樣,那麼分離背景色將不會有任何問題。遺憾的是,情況並非總是如此,由於影印機玻璃板上的灰塵和汙跡、頁面本身的顏色變化、感測器噪聲等不同的因素,畫素的RGB值會發生隨機的變化,頁面的“實際顏色”其實可能涵蓋數千個不同的RGB值。

掃描影象的原件大小為2081×2531,共5267011個畫素點。雖然我們可以逐一處理每個畫素點,但是處理輸入影象的代表性畫素點會更快。

noteshrink.py程式預設採集輸入影象5%的畫素點(在掃描精度為300 DPI的情況下)。接下來,我們先選擇一個10000點的小畫素集,結果如下圖所示:

雖然結果與筆記掃描件的頁面差異很大(沒有手寫墨跡)——但兩幅影象的顏色分佈幾乎完全相同。兩張圖片中大多畫素點呈灰白色,也有少量紅色、藍色和深灰色的畫素點。然後我們對10000個畫素點按亮度進行了排序(例如將每個畫素點的R、G和B進行求和),結果如下:

從遠處看,影象底部80-90%的區域看上去是同一種顏色;然而仔細觀察後,你會發現很多不一致的細節。事實上,上圖中主要顏色(RGB值為(240,240,242))的畫素個數僅為226——佔比還不到總畫素數10000的3%。

由於上述方法中主要顏色佔總畫素的比例很小,能否將它作為代表性顏色來描述影象的顏色分佈就值得懷疑。如果在尋找方法之前先減小影象的位深度,我們將更好地識別頁面的主要顏色。

因此我們把每個色彩通道四個最低有效位置零,將原來每個8位通道色簡化成4位通道色,結果如下所示:

現在主要顏色的RGB值為(224,224,224),並且其畫素點數為3623,佔總畫素的36%。通過減少位深度,實際上我們將相似的畫素分到更大的“組”,這將更容易在資料中找到一個強峰。

可靠性和精確度之間存在一個折衷方案:小畫素集可以更好地區分顏色,但大畫素集處理起來更可靠。最後,我決定用6位通道色表示來識別背景色,這似乎是兩個極端之間的一個最佳選擇。

分離前景色

一旦識別出背景色,就可以根據影象中每個畫素與背景色的相似程度來進行閾值計算。通常來說,通過計算兩個畫素座標的歐幾里得距離,再與預設的閥值進行比較就能得到他們之間的相似性。可這個最常用的方法卻無法正確區分下面的幾個顏色:

下表展示了每種顏色與背景色的歐幾里德距離:

從表中可以看出,筆記反面滲過來的深灰色應該被分為背景色,但它與白色背景的差值要比粉紅色的差值更大,而粉紅色應該是前景色。如果使用這種方法,就無法有效分離出粉紅色的前景色,因為總會包含滲過來的深灰色。

為了解決這個問題,我們可以將圖片從RGB空間移動到色相-飽和度-亮度(Hue-Saturation-Value,HSV)空間,HSV將RGB的立方體轉變為圓柱體,其剖面圖如下:

HSV圓柱體上表面邊緣呈現圓形分佈的彩虹色,色度(hue)是指圍繞圓柱體的中心軸旋轉的角度(紅色為0°)。圓柱體的中心軸從底部的黑色、中間的灰色漸變到頂部的白色——整個軸的飽和度(saturation)為0,外圓周上鮮豔的顏色飽和度都為1。最後,亮度(value)是指顏色的整體亮度,其變化範圍從底部的暗色調到頂部的亮色調。

現在讓我們用HSV重新區分一下之前的顏色:

從表中可以看出,白色、黑色和灰色的亮度差別很大,但它們的飽和度都很接近且數值較低——遠低於紅色或粉紅色。通過分析影象的HSV值,我們可以利用下面的標準來標記屬於前景色的畫素,只需要滿足其中一條就可以:

該畫素的亮度與背景色的差值大於0.3;該畫素的飽和度與背景色的差值大於0.2;

第一條標準可以分離出筆記中的黑色墨跡,第二條標準則可以分離出紅色墨跡和粉色線條,且這兩個標準在選取前景色時排除了筆記反面滲透過來的灰色。但不同的影象可能需要不同的飽和度或亮度閾值,詳情請參閱結果部分。

選擇一組有代表性的顏色

當我們將前景色分離後,會得到與頁面上筆記的顏色相對應的一組顏色。將得到的畫素點重新放進RGB空間並計算每個畫素對應的座標,可以看到新的散點圖呈現簇狀,每一個顏色會形成自己的色塊:

由three.js提供支援的互動式三維圖

現在我們的目標是將原始的影象(24位/畫素)中的所有顏色用8種“索引色”進行替換(8並非固定的數字)。這樣做有兩種好處:首先,它縮小了檔案的大小,因為現在只需要3位就可以指定一種顏色(因為8 = 2^3);此外,它使得生成的影象在視覺上更美觀,因為在最終輸出的影象中,相似顏色的筆記都會只用一種顏色替代。

為了實現這個目標,我們通過資料驅動的方式,也就是利用上圖中的“簇狀”特性,選擇每個色簇的中心座標來表示這一組顏色。用術語說,我們將通過聚類分析來解決一個色彩量化問題(其實是向量量化)。

具體的做法是,通過k-means演算法在一個顏色簇中找到一個點,這個點到其他每個點的平均距離之和最小。對上述資料集使用這個方法,得到7個不同的顏色簇:

由three.js提供支援的互動式三維圖

在這張圖中,黑色輪廓彩色實心的點表示前景色畫素的顏色座標,通過彩色的線將它們連線到RGB色彩空間中最近的中心點。當影象轉換為索引顏色時,每個前景色畫素的顏色將被替換為距其最近的中心點的顏色。最後,包圍每個顏色簇的圓表示每個中心點距相關畫素的最遠距離。

細節調整

除了能夠設定亮度和飽和度的閾值之外,noteshrink.py還具有幾個其他值得一提的功能。預設情況下,它通過將亮度的最小和最大值重新調整為0和255來增加最終調色盤的鮮豔度和對比度。如果不進行調整,上述掃描件的8色調色盤將如下所示:

調整後的調色盤色彩更鮮明:

在完成前景色分離後,還有一個選項可以強制將背景色變為白色。通過轉換為索引顏色的影象可以進一步壓縮PNG檔案,noteshrink.py還可以執行如optipng、pngcrush或pngquant等影象優化工具。

該程式最終會將多個壓縮後的影象合併為一個PDF檔案,就像使用ImageMagick的轉換工具一樣。此外,noteshrink.py會自動對輸入檔名進行數字排序(而不是像shell globbing 那樣按字母順序排列)。當影印機輸出的檔名是scan 9.png和scan 10.png時是非常有幫助的,上述排序功能保證了壓縮後的頁面在PDF中也保持同樣的順序。

結果

以下是一些程式輸出的例子。第一個輸出的PDF使用預設的閾值設定,看起來很棒:

不同顏色簇的視覺化:

由three.js提供支援的互動式三維圖

第二個PDF需要將飽和度閾值降低到0.045,因為藍灰色的線條顏色太深不便於閱讀:

對應的顏色簇:

由three.js提供支援的互動式三維圖

最後這個PDF來自於工程師的方格紙,在這個過程中我將亮度閾值設定為0.05,因為背景和線條之間的對比度非常低:

對應的顏色簇:

由three.js提供支援的互動式三維圖

綜上,這四份PDF檔案大小約788KB,平均每頁130KB大小。

結論與展望

我很高興能開發一個實用的工具,這個工具可以將課程網站中的手寫筆記的PDF進行加工和美化。與此同時,記錄下這整個過程也讓我受益匪淺,我先後在維基百科上補充了關於顏色量化的更多內容,也促使我嘗試並學習了three.js。

如果再次啟動這個專案,我想嘗試一下其他的量化方案,就在前幾天還在想用光譜簇結合最近鄰圖的方式去嘗試一下,當時十分興奮認為這是一個絕佳的方案,然後就發現已經有一篇2012年的論文提出了完全一樣的構思,哎…

你也可以嘗試使用最大期望演算法來生成描述顏色分佈的高斯混合模型——不確定之前是否有人做過類似的實現。當然感興趣的同學也可以試試其他有趣的想法,如使用Lab這類視覺上均勻的色彩空間進行顏色聚類,並嘗試自動給出指定影象的“最佳”聚類數量。

原文連結:

https://mzucker.github.io/2016/09/20/noteshrink.html