使用Python批量處理圖片


最近處理一些規格不一的照片,需要修改成指定尺寸便於打印。為便於分類排列,還需要能夠在照片上顯示日期信息。如果只是單獨幾張照片,完全可以使用Photoshop解決。然而面對成百上千張的照片,照片的日期信息也不盡相同,Photoshop便顯得難以勝任了。

考慮到修改圖片尺寸也好,添加日期文本到圖片上也好,這些對圖片的修改任務都是程序式的,完成一次和完成一百次在步驟上並沒有什么變化,因此編寫腳本程序進行處理無疑是一勞永逸。而Python的標准庫以及圖像處理庫Pillow恰能完美完成本項任務。

這里選擇十張大小不一的圖片,使用Python+Pillow對整個圖片批處理過程進行模擬。主要流程是編寫兩個方法,一個用於修改尺寸,一個用於為圖片添加日期信息,最后在每張圖片上都分別應用這兩個方法,達到批處理的目的。

1.素材准備

因為要處理大量圖片,首要任務是收集待處理圖片。對於相機拍攝的照片,可以直接使用原圖,這樣做有兩個優點:一是保證圖片在尺寸修改之前不會有畫質損失;二是原始圖片中包含很多有用的信息,比如有規則的文件名,拍攝時間等,這在本文的為圖片添加文本信息部分會詳細介紹。

用於本文模擬的圖片素材下載自網絡,並不包含類似於照片的拍攝信息。不過因為對照片處理的任務只需要使用照片的拍攝日期信息,而恰好日期信息在照片文件名中有所體現,所以完全可以把素材圖片文件名修改為和待處理照片相似的格式。待處理照片的文件名格式為“IMG_(datetime)xxxxx”,有用的部信息為“_”之后的拍攝日期。將模擬素材照稍加處理之后,得如下圖所示的一些文件:

修改文件名后的圖片素材及文件信息

這些素材經微信轉發一次,文件名會被修改成固定的格式,再將日期信息稍加修改即得到我們期望的素材。此外,我們也能看到圖片的分辨率信息,這些圖片的長寬比都不一致,一般來說相機拍攝照片的長寬比更為固定,不過使用這些尺寸變化較大的素材,更能體現出使用代碼進行圖片處理在應對不同尺寸素材時的靈活性。現在已經得到了模擬處理過程所需的全部素材,接下來便可以逐步對素材進行處理。

2.修改圖片尺寸

任何一個圖像處理庫都會包含一些最基本圖像處理操作,其中最常用的為仿射變換,包括旋轉,平移,縮放,剪切等。修改一張圖片至目標尺寸,縮放與剪切正是我們所需要的。為保持圖片的原始比例,並不能夠直接將圖片變換到目標尺寸,需要在等比例縮放后對圖片進行一些修剪。所以我們只需要在圖像處理庫中找到縮放和修剪圖片相關的方法,再進一步代碼實現縮放和修剪過程即可。

Pillow是Python中最為流行的圖像處理庫,支持一系列的圖形處理功能。我們使用Pillow完成圖片尺寸修剪的需求。查閱Pillow庫文檔,可以在Image模組中找到名為resize的方法,使用這個方法可以調整一張圖片的尺寸,最巧妙的是可以在調整尺寸后輸出指定區域,因此在參數設置合理的情況下,只需要這一個方法就能夠完成我們的全部需求。下面用一張圖描繪resize方法重定義圖像尺寸的等效的過程:

重定義圖片尺寸過程可視化模擬圖

圖中所有非紅色蒙版區域尺寸均等於目標尺寸。resize方法對圖片尺寸調整的效果相當於流程(1)→(3)→(4)。(1)中目標尺寸大於素材尺寸,所以將素材等比例放大至與目標尺寸等高,再與目標尺寸中心對齊后修建去目標尺寸之外的像素,便得到我們所需要圖片。(2)中描繪素材縮放后與目標尺寸左對齊的效果,如果在此基礎上修剪冗余部分,圖像信息損失會集中在右側,為使修改后圖片相對均衡,本次批處理使用中心對齊並修剪的方案。

需要注意,縮放過程中素材與目標尺寸等高或者等寬,可以保證圖片在縮放和剪切后損失最少的信息。至於縮放至等長還是等寬的選擇取決於素材尺寸與目標尺寸的相對長寬比,例如目標尺寸相對素材尺寸較寬時--上圖對resize方法效果的模擬情況下--需要縮放至等寬,如果縮放至等長,顯然目標尺寸中會有部分區域未被填充,我們並不希望獲得一張包含過多空白區域的圖片。

了解到resize方法實現的效果后,只需要為resize方法提供合適的參數,就可以將一張圖片的尺寸靈活地調整到目標尺寸。這些參數的具體計算過程以及對resize方法的使用封裝在文末程序代碼的img_resize方法中,我們只需要提供修改圖片的目標尺寸。

3.為圖片添加文本信息

為圖片添加文本信息的過程比較簡單,就是一個將大象裝進冰箱的問題:①首先獲取欲添加的文本信息 ②把文本添加到待處理圖片上。

這里的待處理圖片指的是尺寸調整之后達到需求尺寸的圖片,只有在圖片尺寸確定之后才能夠在正確的位置添加文本信息。

素材准備部分中將素材文件名修改為規則的帶有日期信息的格式正是為了方便地獲取①步驟中欲添加的日期文本,在實際的批處理中照片文件名也是這種帶有日期信息的規則格式。觀察文件名,可以發現日期信息從“_”之后開始,由8位數字組成,使用正則表達式可以直接提取這個字符串,再將年、月、日拆分,轉換到常用日期格式,便得到待添加的文本。

把文本添加到圖片上的操作在常用的辦公軟件上相當簡單,可以直接添加文本框。不過在代碼層面上這個操作要復雜一些,在獲取文本后還需要將其與待處理圖片混合,輸出一張完整的圖片。在數字圖像中,可以將圖像分為四個通道:RGBA。RGB三個通道包含圖片基本的顏色信息,而A(α)通道攜帶的則是圖片的透明度信息,決定一張圖片的透明程度。在圖片上添加文本的原理類似於將帶有文字的“貼紙”貼在待處理圖片上,這張“貼紙”實際上就是一張圖片,圖片中除文本部分外α通道為全透明,在設置“貼紙”上的文字與文字位置信息后,將“貼紙”與素材圖片的α通道混合得到一張新的圖片,就實現了圖片上文本信息的添加。

使用Pillow庫進行代碼實現流程與上述基本一致:①創建一張與待處理圖片尺寸一致的空圖片 ②在這張空圖片合適的位置上繪制文本,使用ImageDraw.text方法,該方法可以直接得到一張“貼紙”形式的圖片 ③使用 Image.alpha_composite方法混合“貼紙”與待處理圖片的α通道,得到我們所需要的圖片。

到這里我們已經獲得了所需要的一張圖片,下一步的批處理步驟則不斷重復修改圖片尺寸和為圖片添加文本信息這兩個過程,大大提高了處理圖片的效率。

4.批量處理

使用python標准庫的os模塊,可以方便地獲取文件夾中的文件路徑,這是我們得以對圖片進行批處理的基礎:在獲取素材圖片路徑中圖片的文件名后,一方面可以對路徑下的每張圖片進行尺寸修改,另一方面在通過文件名解析日期信息,便可以為圖片添加文本。

既是批處理過程,首先需要得知的是需要處理那些素材,我們可以將遍歷圖片路徑得到的文件名儲存到一個列表中,再對這些圖片進行統一的處理。在具體的代碼實現中,獲取文件名的過程可以和解析日期的過程同時進行,我們將獲得兩組信息,一組是文件名,另一組是文件名中包含的日期信息,這兩組信息是一一對應的,可以創建一個字典保存這些信息。代碼中通過get_pathtime_dict這個方法實現。

余下的操作就比較清晰了,只需要將上述步驟整合起來,便可完成我們的圖片批處理過程:獲取待處理的文件名及日期信息字典,遍歷這個字典,通過文件名找到圖片並修改尺寸,而后將日期信息添加到重定尺寸后的圖片上,最后把處理后的圖片導出。批量處理后的單張圖片效果與批處理后的新圖片的文件信息如下:

單張素材原圖(左)&處理后的效果圖(右)
素材批處理后的文件信息

從兩張圖中可以看出,日期信息被成功地添加到素材圖片當中;而經過批處理后的圖片在尺寸上已經完全一致,並且保持了素材圖片的矩形較長邊的方向。處理前后圖片的文件格式由jpg轉換為了png,這是由於在添加文本的過程中涉及到α混合,圖片保存有α通道信息,可以直接儲存為png格式,jpg格式則不包含α通道信息,所以欲保存為jpg格式還需要進一步轉換。因為兩種圖片格式都非常常用,能夠適用於各種場景,也就沒有將png格式再刻意地轉換回jpg格式。

5.總結

使用上述方法對圖片進行批量處理,修改一張圖片只需要不到一秒鍾的時間,這種效率是使用Photoshop等軟件無法企及的。此外使用代碼進行圖片批處理還極具靈活性,如果需要修改圖片至不同的尺寸、添加外的信息等,只需簡單地修改幾處參數。

篇幅原因本文沒有介紹一些python使用細節,代碼中方法內部的涉及的小算法也沒有詳細的解析。本文最主要的目的是分析圖片批處理過程的整體思路,python是解決這個問題的一個工具,使用其它編程語言同樣可以完美完成,不過可能不會有python來得方便。

對於圖片的批處理,也可以發揮更多的想象力,完成更高級的任務,比如封裝成應用程序的形式、不通過文件名獲取文件的生成日期信息、識別圖片中的關鍵點並以此為中心進行縮放等,這些任務都非常有趣,不過既然是追求效率,那就貫徹到底,又快又好的完成我們的需求就足夠了。

完整代碼:

from PIL import Image, ImageDraw, ImageFont
import os
import re
font = ImageFont.truetype(r'C:\Windows\Fonts\msyh.ttc', size=80)


def get_pathtime_dict(path_root):
    raw_paths = os.listdir(path_root)
    path_time_dict = {}
    pattern = re.compile(r'_(\d{8})\d*')
    for path in raw_paths:
        if path[-4:] != '.jpg':
            continue
        timeinfo = pattern.findall(path)
        if timeinfo:
            label = timeinfo[0][:4]+'/' + \
                timeinfo[0][4:6] + '/' + timeinfo[0][6:]
            path_time_dict[path] = label

    return path_time_dict


def text2img(img, text, font=font):
    rgba_img = img.convert('RGBA')

    # Create an overlay for the texts
    text_overlay = Image.new('RGBA', rgba_img.size, (0, 255, 0, 0))
    image_draw = ImageDraw.Draw(text_overlay)
    # Set the properties of the texts
    text_wdth, text_hgt = image_draw.textsize(text, font=font)
    text_sz = (rgba_img.size[0] - text_wdth - 50,
               rgba_img.size[1] - text_hgt - 50)
    image_draw.text(text_sz, text, font=font, fill=(255, 0, 0, 180))

    img_with_text = Image.alpha_composite(rgba_img, text_overlay)
    return img_with_text


def img_resize(img, trgtsize):
    '''
    Zoom an image based on the center of the image to fit the target size, with the redundant section trimmed

    :param trgtsize: tatget size for the img normalization
    '''
    trgtsize = tuple(trgtsize)

    img_wdth, img_hgt = img.size
    if img_wdth > img_hgt:
        trgtsize = (max(trgtsize), min(trgtsize))
    else:
        trgtsize = (min(trgtsize), max(trgtsize))
    target_wdth, target_ght = trgtsize

    # Fit the source image to the target size with the redundant part trimmed
    center_px = (img_wdth / 2, img_hgt / 2)
    zoom_fct = min(img_wdth / target_wdth, img_hgt / target_ght)
    box_wdth, box_hgt = target_wdth*zoom_fct, target_ght*zoom_fct
    box_start_pt = (int(center_px[0]-box_wdth/2), int(center_px[1]-box_hgt/2))
    box_end_pt = (int(center_px[0]+box_wdth/2), int(center_px[1]+box_hgt/2))
    resized_img = img.resize(trgtsize, box=(
        box_start_pt[0], box_start_pt[1], box_end_pt[0], box_end_pt[1]))

    return resized_img


def main():
    path_root = r'C:\Users\Windy\Desktop\Python照片批處理\images_we'
    path_time_dict = get_pathtime_dict(path_root)
    nrml_sz = (1051, 1500)
    for path, text in path_time_dict.items():
        img_pre = Image.open(
            r'C:\Users\Windy\Desktop\Python照片批處理\images_we\{}'.format(path))
        img_nrml = img_resize(img_pre, nrml_sz)
        img_pos = text2img(img_nrml, text)
        img_pos.save(  # remove the '.jpg' suffix from an image file's path
            r'C:\Users\Windy\Desktop\Python照片批處理\images_we\new\_{}.png'.format(path[:-4]))
        print('image {} done!'.format(path))
        img_pre.close()


main()
print('OK~')


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM