返回文章列表

Python影像後設資料解析與處理技術

本文探討如何使用 Python 的 Pillow 函式庫解析和處理影像後設資料,包括 EXIF 資訊、GPS 座標以及影像縮放和裁剪技術。此外,文章還介紹瞭如何利用這些技術進行隱寫術,將秘密資訊嵌入到影像中。

影像處理 Python

現代數位影像處理中,解析和運用後設資料至關重要。本文將示範如何使用 Python 的 Pillow 函式庫提取影像的 EXIF 資訊,包含 GPS 座標解析,並講解影像縮放、裁剪等基本操作。更進一步,我們將探討如何運用這些技術實作隱寫術,將秘密訊息隱藏於影像之中,並討論不同編碼策略,例如修改顏色通道或最低有效位元。這些技術在數位版權保護、資訊隱藏等領域都有廣泛應用。

結合上述步驟的完整範例程式碼

img = Image.open(name)
print(name, img.format, img.mode, img.size)
for key in img.info:
    if key == 'exif':
        for k, v in img._getexif().items():
            if k == 34853: # GPSInfo
                print(" ", PIL.ExifTags.TAGS[k], v)
                for gk, gv in v.items():
                    print(" ", PIL.ExifTags.GPSTAGS[gk], gv)
            else:
                print(" ", PIL.ExifTags.TAGS[k], v)
    elif key == 'icc_profile':
        print(key) # Skip these details
    else:
        print(key, img.info[key])

內容解密:

  • 上述程式碼綜合了上述步驟,用於解析圖片的元資料和 Exif 資訊。
  • 對 GPSInfo 等巢狀結構進行了額外的解析。

輸出結果可能如下:

Common_face_of_one_euro_coin.jpg JPEG RGB (320, 312)
ExifOffset 26
ExifImageWidth 320
ExifImageHeight 312
jfif_version (1, 1)

影像處理與隱寫術:揭露後設資料的秘密

在數位影像處理的世界中,瞭解如何操控和解讀影像至關重要。除了基本的影像編輯外,後設資料(Metadata)提供了豐富的資訊,可以揭露影像的拍攝細節和背景。本章將探討影像後設資料的解讀、影像縮放和裁剪技術,以及如何利用這些技術進行隱寫術(Steganography)。

解讀影像後設資料

數位影像通常包含豐富的後設資料,其中最常見的是EXIF(Exchangeable Image File Format)資料。EXIF資料包含了相機的設定、拍攝時間和地點等資訊。例如,從一張照片的EXIF資料中,我們可以瞭解到相機的品牌和型號、光圈大小、快門速度,甚至是拍攝地點的GPS座標。

from PIL import Image
from PIL.ExifTags import TAGS

# 開啟影像檔案
img = Image.open("image.jpg")

# 提取EXIF資料
exif_data = img._getexif()

# 解析EXIF資料
for tag, value in exif_data.items():
    tag_name = TAGS.get(tag, tag)
    print(f"{tag_name}: {value}")

內容解密:

這段程式碼首先匯入了必要的模組,然後開啟一個影像檔案並提取其EXIF資料。透過遍歷EXIF資料中的每個標籤和值,我們可以列印預出每個標籤的名稱和對應的值。這樣,我們就可以瞭解影像的拍攝引數,例如拍攝時間、相機型號、光圈值等。

GPS座標的解析

現代相機,尤其是智慧手機相機,通常會在EXIF資料中包含GPS座標。這些座標以特定的格式儲存,需要經過解析才能轉換成可讀的經緯度。

def parse_gps(exif_data):
    gps_info = exif_data.get(34853)  # GPSInfo tag
    if gps_info:
        gps_latitude_ref = gps_info[1]
        gps_latitude = gps_info[2]
        gps_longitude_ref = gps_info[3]
        gps_longitude = gps_info[4]
        
        # 解析緯度和經度
        lat_deg, lat_min, lat_sec = gps_latitude
        lon_deg, lon_min, lon_sec = gps_longitude
        
        latitude = lat_deg + lat_min / 60 + lat_sec / 3600
        longitude = lon_deg + lon_min / 60 + lon_sec / 3600
        
        if gps_latitude_ref == 'S':
            latitude *= -1
        if gps_longitude_ref == 'W':
            longitude *= -1
        
        return latitude, longitude
    return None

# 使用該函式解析GPS座標
latitude, longitude = parse_gps(exif_data)
print(f"Latitude: {latitude}, Longitude: {longitude}")

內容解密:

這段程式碼定義了一個函式parse_gps,用於從EXIF資料中提取GPS資訊並解析成可讀的經緯度。首先,它檢查EXIF資料中是否存在GPS資訊(標籤34853)。然後,它提取GPS緯度和經度的參考值以及座標值,並將它們轉換成十進位制格式。如果參考值為’S’或’W’,則對應的緯度或經度取負值。最終傳回解析後的經緯度。

縮放和裁剪影像

Pillow函式庫提供了多種影像處理功能,包括縮放和裁剪。縮放可以幫助我們建立縮圖,而裁剪則允許我們提取影像中的特定區域。

from PIL import Image

# 開啟影像檔案
img = Image.open("image.jpg")

# 縮放影像
img.thumbnail((128, 128))  # 最大尺寸為128x128
img.save("thumbnail.jpg")

# 裁剪影像
img = Image.open("image.jpg")
img_cropped = img.crop((100, 100, 300, 300))  # 裁剪區域為(100, 100)到(300, 300)
img_cropped.save("cropped.jpg")

內容解密:

這段程式碼展示瞭如何使用Pillow函式庫進行影像縮放和裁剪。首先,開啟一個影像檔案並使用thumbnail方法將其縮放到最大尺寸為128x128,儲存為縮圖。然後,開啟同一個影像檔案,使用crop方法裁剪出指定區域(左上角(100, 100)到右下角(300, 300)),並儲存為新的影像檔案。

利用隱寫術編碼秘密訊息

影像裁剪與處理

在處理影像時,我們經常需要裁剪影像以提取感興趣的區域。Python 的 PIL(Pillow)函式庫提供了一個強大的 Image 模組,可以輕鬆實作影像裁剪。

使用 crop() 方法裁剪影像

from PIL import Image

# 開啟影像檔案
ship = Image.open("LHD_warship.jpg")

# 取得影像大小
print(ship.size)  # 輸出:(2592, 1936)

# 將影像分成九個區域並顯示
w, h = ship.size
ship.crop(box=(w//3, 0, 2*w//3, h//3)).show()
ship.crop(box=(w//3, h//3, 2*w//3, 2*h//3)).show()

影像裁剪的邊界框

crop() 方法需要一個四元組(four-tuple)來指定裁剪區域的左、上、右、下邊界。這些值必須是整數。

簡化邊界框的計算

為了簡化邊界框的計算,我們可以使用分數來定義影像的分割。

from fractions import Fraction

# 定義影像分割的數量
slices = 6

# 計算邊界框的分數
box = [Fraction(i, slices) for i in range(slices+1)]

# 使用巢狀迴圈生成所有邊界框
for i in range(slices):
    for j in range(slices):
        bounds = int(w*box[i]), int(h*box[j]), int(w*box[i+1]), int(h*box[j+1])
        print(bounds)
        ship.crop(bounds).show()

使用 map() 函式最佳化邊界框計算

bounds = tuple(map(int, (w*box[i], h*box[j], w*box[i+1], h*box[j+1])))

裁剪並儲存感興趣的區域

slices = 12
box = [Fraction(i, slices) for i in range(slices+1)]
bounds = tuple(map(int, (w*box[3], h*box[6], w*box[5], h*box[7])))
logo = ship.crop(bounds)
logo.show()
logo.save("LHD_number.jpg")

影像縮放

我們可以使用 resize() 方法來縮放影像。

w, h = logo.size
logo.resize((w*3, h*3))

內容解密:

  1. ship = Image.open("LHD_warship.jpg"):開啟名為 “LHD_warship.jpg” 的影像檔案。
  2. ship.size:取得影像的大小,傳回一個二元組(寬度,高度)。
  3. ship.crop(box=(w//3, 0, 2*w//3, h//3)):裁剪影像,提取指定區域。
    • box 引數是一個四元組,定義了裁剪區域的左、上、右、下邊界。
  4. from fractions import Fraction:匯入 Fraction 類別,用於精確表示分數。
  5. box = [Fraction(i, slices) for i in range(slices+1)]:計算影像分割的分數列表。
  6. bounds = int(w*box[i]), int(h*box[j]), int(w*box[i+1]), int(h*box[j+1]):計算每個裁剪區域的邊界框。
  7. logo = ship.crop(bounds):裁剪原始影像,提取感興趣的區域。
  8. logo.save("LHD_number.jpg"):儲存裁剪後的影像到檔案 “LHD_number.jpg”。
  9. logo.resize((w*3, h*3)):將裁剪後的影像縮放到原始大小的三倍。

此圖示說明瞭如何使用 PIL 函式庫進行影像裁剪和縮放。

此技術可以用於提取影像中的特定區域,或將影像縮放到不同的大小。

影像處理 - 增強圖片細節

原始圖片相當模糊,我們希望能夠增強找到的切片細節。Pillow 函式庫提供了多種過濾器來修改圖片。與流行的電視劇和電影不同,沒有所謂的「增強」功能可以神奇地讓糟糕的圖片變得驚人。

我們可以修改圖片,有時會使其更具可用性;我們也可以修改圖片,但結果可能並不比原圖更好。第三種選擇——通常對特工來說不可行——是使結果比原始圖片更具藝術性。

Pillow 套件中有三個模組包含類別似過濾器的處理:

  • ImageEnhance
  • ImageFilter
  • ImageOps

使用 ImageEnhance 模組增強圖片

ImageEnhance 模組包含增強類別的定義。我們透過繫結增強器和圖片來建立增強器物件。然後,我們使用該繫結物件來建立增強版本的圖片。增強器允許我們對圖片進行多次增量更改。可以把它們想像成簡單的旋鈕,用於調整圖片。

from PIL import ImageEnhance

# 建立 Contrast 類別的增強器物件
e = ImageEnhance.Contrast(logo)
# 使用不同的引數值進行增強並顯示結果
e.enhance(2.0).show()
e.enhance(4.0).show()
e.enhance(8.0).show()

內容解密:

  1. ImageEnhance.Contrast(logo):建立一個 Contrast 類別的增強器物件,傳入 logo 圖片。
  2. e.enhance(2.0):使用 e 增強器物件對 logo 圖片進行對比度增強,引數 2.0 表示增強的程度。
  3. .show():顯示增強後的圖片。

使用 ImageFilter 模組過濾圖片

ImageFilter 模組定義了 18 種不同的過濾器。當我們使用過濾器時,會將過濾器物件提供給 Image 的 filter() 方法。

from PIL import ImageFilter

# 使用 EDGE_ENHANCE 過濾器
logo.filter(ImageFilter.EDGE_ENHANCE).show()

內容解密:

  1. ImageFilter.EDGE_ENHANCE:定義了一個邊緣增強過濾器,用於突出圖片中的邊緣。
  2. logo.filter():對 logo 圖片應用指定的過濾器。
  3. .show():顯示過濾後的圖片。

結合多種操作增強圖片

我們可以結合多種操作來建立新的圖片。

# 先進行對比度增強,再應用 EDGE_ENHANCE 過濾器
e.enhance(8.0).filter(ImageFilter.EDGE_ENHANCE).save("LHD_Number_2.jpg")

內容解密:

  1. e.enhance(8.0):對 logo 圖片進行對比度增強,增強程度為 8.0
  2. .filter(ImageFilter.EDGE_ENHANCE):對增強後的圖片應用邊緣增強過濾器。
  3. .save("LHD_Number_2.jpg"):將處理後的圖片儲存為 LHD_Number_2.jpg

使用 ImageOps 模組進行圖片轉換

ImageOps 模組提供了 13 種額外的轉換功能,可以用來改善圖片。

from PIL import ImageOps

# 使用 autocontrast 功能調整圖片對比度
ImageOps.autocontrast(logo).show()
logo.show()

內容解密:

  1. ImageOps.autocontrast(logo):自動調整 logo 圖片的對比度,使其亮度範圍填充整個 8 位元空間(0 到 255)。
  2. .show():顯示調整對比度後的圖片和原始圖片。

組合多種技術進一步增強圖片

# 先進行 autocontrast 處理,再進行對比度增強
ac = ImageEnhance.Contrast(ImageOps.autocontrast(logo))
ac.enhance(2.5).save("LHD_Number_3.jpg")

內容解密:

  1. ImageOps.autocontrast(logo):自動調整 logo 圖片的對比度。
  2. ImageEnhance.Contrast():建立一個對比度增強器物件,傳入經過 autocontrast 處理的圖片。
  3. ac.enhance(2.5):對經過 autocontrast 處理的圖片進行對比度增強,增強程度為 2.5
  4. .save("LHD_Number_3.jpg"):將最終處理後的圖片儲存為 LHD_Number_3.jpg

使用隱寫術編碼秘密訊息

隱寫術是一種在影像檔案中隱藏訊息的技術。由於影像檔案通常很大、很複雜且相對嘈雜,因此在其中新增一些額外的資料位元不會對影像或檔案造成明顯的改變。

有兩種常見的方法可以在影像中隱藏訊息:

  • 使用顏色通道:如果我們在某個顏色通道中覆寫一些位元組,我們將改變該區域中幾個畫素的顏色的一部分。這只會影響數百萬畫素中的幾個,並且只會影響三種(或四種)顏色中的一種。如果我們將修改限制在邊緣,那麼就不太容易被察覺。
  • 使用每個位元組的最低有效位元(LSB):如果我們在一系列位元組中覆寫最低有效位元,我們將對影像進行極微小的改變。我們必須限制訊息的大小,因為每個畫素只能編碼一個位元組。一張小的432 * 161 = 69,552畫素的圖片可以編碼8,694位元組的資料。如果我們使用UTF-8編碼我們的字元,我們應該能夠在該影像中塞入一個8K的訊息。如果我們使用UTF-16,我們只能得到一個4K的訊息。這種技術甚至適用於只有一個通道的灰階影像。

編碼秘密訊息的策略

我們的策略如下:

  1. 從影像的畫素中取得位元組。
  2. 將我們的秘密訊息從Unicode字串轉換為一系列位元。
  3. 對於我們的秘密訊息的每個位元,修改原始影像的一個位元組。

取得紅色通道資料

讓我們來看看如何使用紅色通道LSB編碼將我們的訊息編碼到影像中。為什麼是紅色?為什麼不呢?有些男性可能具有某種程度的紅綠色盲;如果他們不太可能察覺到這個通道中的變化,那麼我們就進一步隱藏了我們的影像,不讓一些窺探的眼睛發現。

y = 0
for x in range(64):
    print(ship.getpixel((x, y)))

上述程式碼顯示瞭如何使用PIL的getpixel()方法取得影像中個別畫素的值。

從Unicode字元中提取位元組

為了將我們的秘密訊息編碼到影像的位元組中,我們需要將我們的Unicode訊息轉換為位元組。一旦我們有了一些位元組,我們就可以進行另一次轉換,以獲得一系列位元。

# 將Unicode字串轉換為位元組
message_bytes = message.encode('utf-8')

# 將位元組轉換為一系列位元
message_bits = ''.join(format(byte, '08b') for byte in message_bytes)

將秘密訊息位元嵌入影像位元組中

對於我們的秘密訊息的每個位元,我們將修改原始影像的一個位元組。如果我們要嵌入一個0位元,我們將使影像畫素值成為偶數;如果我們要嵌入一個1位元,我們將使影像畫素值成為奇數。

# 取得紅色通道的位元組
red_channel_bytes = [pixel[0] for pixel in ship.getdata()]

# 將秘密訊息位元嵌入紅色通道位元組中
encoded_bytes = []
for i, byte in enumerate(red_channel_bytes):
    if i < len(message_bits):
        # 修改最低有效位元以嵌入秘密訊息位元
        encoded_byte = (byte & ~1) | int(message_bits[i])
        encoded_bytes.append(encoded_byte)
    else:
        encoded_bytes.append(byte)

內容解密:

上述程式碼展示瞭如何將秘密訊息編碼到影像的紅色通道中。首先,我們取得紅色通道的位元組。然後,我們將秘密訊息轉換為一系列位元。接著,我們遍歷紅色通道的每個位元組,並修改最低有效位元以嵌入秘密訊息的對應位元。最後,我們得到了一系列嵌入了秘密訊息的位元組。