
寫在前面
近期好多網友私信我,問我編程該怎么學習、怎么入門。我覺得編程學習,就像寫文章一樣,需要積累。
如果把代碼每個字符拆開,大伙都認識,但是組合在一起,就是另外一回事了。所以我的建議是,學習編程,從項目入手,從自己感興趣的項目入手,遇到不懂的語法、算法,就去翻閱書、看視頻。
如果一開始就去看生硬的語法、晦澀的算法,就像背單詞一樣,背到第一個單詞abandon,就放棄了。
廢話不多說,直接上項目,這次是一個批量去除水印的項目。
- 環境配置:
python版本: 3.6.0
編輯器: pycharm
ps: 每一步都有代碼和排版截圖,方便學習
- 代碼目錄結構

切記剛開始學習的時候,目錄結構保持和源碼一致
第一步:導入相關的python包
# encoding:utf-8
import os
from PIL import Image
import numpy as np
import imghdr
python包的作用:
os: 本項目只用到了對文件、文件夾的操作。
PIL: Python Imaging Library,是Python平臺的圖像處理標準庫。PIL功能非常強大,API也非常簡單易用。安裝命令:pip install pillow
numpy: (Numerical Python) 是 Python 語言的一個擴展程序庫,支持大量的維度數組與矩陣運算,此外也針對數組運算提供大量的數學函數庫。安裝命令: pip install numpy
imghdr: 是一個用來檢測圖片類型的模塊,傳遞給它的可以是一個文件對象,也可以是一個字節流。

第二步:參數配置類
class CONF:input_path = "input_img" # 待處理的圖片存放的位置output_path = "output_img" # 去除水印后的圖片存放位置level_black = 108 # 用于去除水印的特征值level_white = 170 # 用于去除水印的特征值is_log = True # 是否打印日志信息
這里是個人編程的習慣,我習慣把一些配置,例如:文件路徑、模型存放路徑、模型參數統一放在一個類中。當然,實際項目開發的時候,是用config 文本文件存放,不會直接寫在代碼里,這里為了演示方便,就寫在一起,也方便運行。這塊代碼放在代碼文件的開頭也方便查看和修改。

第三步:類的初始化
class DocWipe:def __init__(self, input_path, output_path, level_black, level_white, is_log):self.input_path = input_pathself.output_path = output_pathself.level_black = level_blackself.level_white = level_whiteself.is_log = is_log""" 初始化 """@classmethoddef initialize(cls, config):input_path = config.input_pathoutput_path = config.output_pathlevel_black = config.level_blacklevel_white = config.level_whiteis_log = config.is_logreturn cls(input_path, output_path, level_black, level_white, is_log)
initialize() 函數和 __init__() 函數 是對象初始化和實例化,其中包括基本參數的賦值、最后返回用戶一個對象。這里作為一個類的基本操作,是屬于一個通用模板,在大多數項目中,都可以這么去寫。為了養成良好的編程習慣,大家可以把這個模板記下來,后續直接套用,修改部分參數就可以了。

第四步: 類的主流程函數
""" 主流程 """
def wipe_process(self,):if os.path.exists(self.input_path) and os.path.isdir(self.output_path):self.visit_dir_files(self.input_path, self.output_path, self.input_path)if self.is_log:print(u'完成!所有圖片已保存至路徑' + self.output_path)else:print(u'待處理的圖片存放的位置 %s, 如果沒有請新建目錄 %s' % (self.input_path, self.input_path))print(u'去除水印后的圖片存放位置 %s, 如果沒有請新建目錄 %s' % (self.output_path, self.output_path))
在寫代碼的時候,一定要抓住主線,就是代碼運行的主流程。因為一個完整可靠的項目,它是有很多細枝末節考慮,很多步驟是要分模塊來寫。主流程就是把主心干確定好,各個模塊的入口確定好。這樣開發的時候,思路會比較清晰,不會被細節吸引住。這里主心干只有個函數 visit_dir_files() 的調用,但是它的外圍都是一些邊界條件的判定,不重要,但是沒有它們程序會出現BUG。

第五步:圖像處理算法
""" 圖片處理 """
def img_deal(self, img_path, save_path):img = Image.open(img_path)img = self.levels_deal(img, self.level_black, self.level_white)img_res = Image.fromarray(img.astype('uint8'))if self.is_log:print(u'圖片[' + img_path + u']處理完畢')img_res.save(save_path)""" 圖像矩陣處理 """
def levels_deal(self, img, black, white):if white > 255:white = 255if black < 0:black = 0if black >= white:black = white - 2img_array = np.array(img, dtype=int)c_rate = -(white - black) / 255.0 * 0.05rgb_diff = np.maximum(img_array - black, 0)img_array = np.around(rgb_diff * c_rate, 0)img_array = img_array.astype(int)return img_array
在計算機看來,彩色圖片是三個二維數據分別是R通道、G通道、B通道,而灰度圖是一個二維數組。數值類型是uint8,簡單的說,就是每個像素點是0~255的數值。去除水印的算法,其實就是對每個像素點進行運算,為了加快運算速度和代碼的整潔度,使用了numpy包的矩陣運算。
這塊的細節理解起來是比較有難度的,它涉及了圖像處理的算法,這塊可以先跳過,知道它的功能是干嘛的就行。后續有時間,再來細細琢磨。

- 第六步: 遞歸訪問文件
""" 創建文件夾 """
def mkdir(self, path):path = path.strip().rstrip("")is_exists = os.path.exists(path)if not is_exists:os.makedirs(path)return Trueelse:return False""" 遞歸訪問文件/文件夾 """
def visit_dir_files(self, org_input_dir, org_output_dir, recursion_dir):single_file = Falseif os.path.isdir(recursion_dir):dir_list = os.listdir(recursion_dir)else:dir_list = [recursion_dir]single_file = Truefor i in range(0, len(dir_list)):path = os.path.join(recursion_dir, dir_list[i])if os.path.isdir(path):self.visit_dir_files(org_input_dir, org_output_dir, path)else:if imghdr.what(path):abs_output_dir = org_output_dir + recursion_dir[len(org_input_dir):]target_path = os.path.join(abs_output_dir, dir_list[i])if single_file:target_path = os.path.join(org_output_dir, os.path.basename(dir_list[i]))target_dir_name = os.path.dirname(target_path)if not os.path.exists(target_dir_name):self.mkdir(target_dir_name)self.img_deal(path, target_path)
這里也有一個難點,遞歸訪問文件/文件夾。遞歸,就是自己調用自己。可以把它當成“分治法”,打個比方,如果你想解決一個很大的難題,直接計算是非常困難的,可以把它拆解成多個小問題,一個一個來解決。而遞歸,就是起到一個“分治”的作用。它調用的過程,就是數據結構里面的“棧”(先進后出)。
我當時開始學習算法的時候,遞歸算法也是研究了一個星期才懂它的原理。所以大家學習的時候,不要著急,先在紙上模擬調用過程,慢慢就會懂了。

第七步: 主函數入口
if __name__ == '__main__':# 對象初始化doc_wipe = DocWipe.initialize(config=CONF)# 調用主流程doc_wipe.wipe_process()
至此,加上一個main函數去調用,所有程序的入口。我們終于完成了。

最后,測試一下
用我之前寫的《最近很火的文章自動生成器》,來生成隨機一篇文章,并加上水印。再轉成圖片,作為程序的輸入,運行結果:

左邊有水印,右邊是經過python去除了水印
注意: 僅對淺色的黑白/彩色水印有效,如WPS水印,課程水印等
最后,給一點點學習建議,不懂的時候,先弄明白它的功能以及會使用它,讓代碼先運行起來。等有時間就一個一個細節去攻破它,編程和寫文章一樣,需要慢慢積累,加油。
原文鏈接https://www.toutiao.com/a6810654859126112772/