圖像處理: jpg格式 存儲-讀寫 時 像素值 微小變化 探究

起因

遇到問題

在做項目的過程中,想比較 同一幅圖像二值化處理結果人工標註的ground_truth圖 之間的差異。

因爲這兩幅用來比較的圖在生成的時候都是 二值圖像(即像素值非 0 即 255),所以用來求差異圖的代碼段,我想當然地這麼寫:

    for i in range(h):
        for j in range(w):
            if thresh_pic[i, j, 0] == 0 and label_pic[i, j, 0] == 0:
                diff_pic[i, j, :] = 0
            elif thresh_pic[i, j, 0] == 255 and label_pic[i, j, 0] == 255:
                diff_pic[i, j, :] = 255
            elif thresh_pic[i, j, 0] == 0 and label_pic[i, j, 0] == 255:
                diff_pic[i, j, 2] = 255
            elif thresh_pic[i, j, 0] == 255 and label_pic[i, j, 0] == 0:
                diff_pic[i, j, 1] = 255
            else:
                logging.error('({},{}): thresh={}, label={}'.format(i, j, thresh_pic[i, j, 0], label_pic[i, j, 0]))

在差異比對效果圖出來之後卻看到了很詭異的現象,圖片中出現了很多黑色麻花,如下圖:

logging打印出來的結果也顯示有很多 既非 0 也非 255 的像素點:

...
ERROR:root:(279,535): thresh=254, label=0
ERROR:root:(279,576): thresh=253, label=0
ERROR:root:(279,581): thresh=253, label=0
...

修改代碼

經過觀察,我發現某些像素點在 存儲爲圖片 之前 像素值 還是 255 或 0,存爲圖片 以後,就會變成了245~255或0~10範圍內的隨機數(在不懂原理的我看來感覺那就是隨機偏移,真實情況其實應該是按照某個算法進行了對應的偏移)了。初步猜測是在 存儲爲圖片時從圖片讀取出來時部分像素點 發生了 像素值 的 少許偏移

於是我修改了代碼,考慮到了一個 閾值爲10像素值偏移區間

    for i in range(h):
        for j in range(w):
            if thresh_pic[i, j, 0] < 10 and label_pic[i, j, 0] < 10:
                diff_pic[i, j, :] = 0
            elif thresh_pic[i, j, 0] > 245 and label_pic[i, j, 0] > 245:
                diff_pic[i, j, :] = 255
            elif thresh_pic[i, j, 0] < 10 and label_pic[i, j, 0] > 245:
                diff_pic[i, j, 2] = 255
            elif thresh_pic[i, j, 0] > 245 and label_pic[i, j, 0] < 10:
                diff_pic[i, j, 1] = 255
            else:
                logging.error('({},{}): thresh={}, label={}'.format(i, j, thresh_pic[i, j, 0], label_pic[i, j, 0]))

這次黑色麻花沒有了,logging也沒有打印error信息了:

查找資料

經過上網查找,我發現原來一些細心的前輩們也發現這個問題了,並給出了解答

Soga,原來是因爲:

  • .jpg 是 有損壓縮格式,保存時會 壓縮失真 ( .png 是 無損壓縮格式) 。

那麼好奇心大發作的我又想拿我最愛的妹子圖來進一步探究一下。

實驗

實驗思路

  1. 將原圖像 複製多份 ,分別 進行 不同輪次 的 循環存儲-讀寫,經過 多輪次 的 循環 後,在 肉眼層面 查看 新圖像 是否明顯較 原圖像 有失真
  2. 比較 每一輪 循環存儲-讀寫 後,圖片上 各像素點 的 像素值 發生了哪些 變化(置色方案參見下表)。

像素點的像素值變化

置色方案

不變

黑色

增加

綠色

減少

紅色

實驗效果

圖像:

100輪 存-讀 之後的圖像:

原圖像第1輪 存-讀 後的 圖像差異

第1輪 存-讀 後的圖像 與 第2輪 存-讀 後的 圖像差異

第2輪 存-讀 後的圖像 與 第3輪 存-讀 後的 圖像差異

第3輪 存-讀 後的圖像 與 第4輪 存-讀 後的 圖像差異

第4輪 存-讀 後的圖像 與 第5輪 存-讀 後的 圖像差異

實驗代碼

# coding=utf-8

first_path = './data/first.jpg'
second_path = './data/second.jpg'
third_path = './data/third.jpg'
forth_path = './data/forth.jpg'
fifth_path = './data/fifth.jpg'

one_hundred_path = './data/one_hundred.jpg'

import cv2
import numpy as np
import logging

# 生成並保存 倆圖像 的 差異圖
def compare(pic1, pic2, dst_path):
    h, w, c = pic1.shape
    diff_pic = np.zeros_like(pic1, dtype=np.uint8)
    for i in range(h):
        for j in range(w):
            if pic1[i, j, 0] == pic2[i, j, 0]:  # 像素值 不變,該點 置 黑色
                diff_pic[i, j, :] = 0
            elif pic1[i, j, 0] < pic2[i, j, 0]:  # 像素值 增加,該點 置 綠色
                diff_pic[i, j, 1] = 255
            elif pic1[i, j, 0] > pic2[i, j, 0]:  # 像素值 減少,該點 置 紅色
                diff_pic[i, j, 2] = 255
            else:
                logging.error('({},{}): pic1={}, pic2={}'.format(i, j, pic1[i, j, 0], pic2[i, j, 0]))

    cv2.imwrite(dst_path, diff_pic)
    return diff_pic

# 指定輪數 進行 循環 存儲和讀取
def save_and_read_cycle(pic, path, num):
    for _ in xrange(num):
        cv2.imwrite(path, pic)
        pic = cv2.imread(path)
    return pic

# 原圖像
origin_pic = cv2.imread('./data/girl.jpg')

# 需要 循環 1次 的圖像
first_pic = origin_pic.copy()
first_pic = save_and_read_cycle(first_pic, first_path, 1)

# 需要 循環 2次 的圖像
second_pic = origin_pic.copy()
second_pic = save_and_read_cycle(second_pic, second_path, 2)

# 需要 循環 3次 的圖像
third_pic = origin_pic.copy()
third_pic = save_and_read_cycle(third_pic, third_path, 3)

# 需要 循環 4次 的圖像
forth_pic = origin_pic.copy()
forth_pic = save_and_read_cycle(forth_pic, forth_path, 4)

# 需要 循環 5次 的圖像
fifth_pic = origin_pic.copy()
fifth_pic = save_and_read_cycle(fifth_pic, fifth_path, 5)

# 需要 循環 100次 的圖像
one_hundred_pic = origin_pic.copy()
one_hundred_pic = save_and_read_cycle(one_hundred_pic, one_hundred_path, 100)

# 求 循環 存儲和讀取 後 該圖片 與 原圖像 差異
ori_fir = compare(origin_pic, first_pic, 'data/ori_fir.jpg')
fir_sec = compare(first_pic, second_pic, 'data/fir_sec.jpg')
sec_thi = compare(second_pic, third_pic, 'data/sec_thi.jpg')
thi_for = compare(third_pic, forth_pic, 'data/thi_for.jpg')
for_fif = compare(forth_pic, fifth_pic, 'data/for_fif.jpg')
ori_hundred = compare(origin_pic, one_hundred_pic, 'data/ori_hundred.jpg')

實驗結論

  1. 經過 多輪次 的 循環 後,在 肉眼層面 , 新圖像 較 原圖像 沒有明顯的失真
  2. 每一輪 循環存儲-讀寫 後,圖片上 各像素點 的 像素值 發生的 變化 會越來越少;
  3. .jpg 是有損壓縮格式。

實驗不足與展望

不足之處

沒有進一步探究壓縮算法的原理

沒有實驗出像素值的偏移區間範圍

沒有探究循環讀寫的失真率變化原因

沒有製作循環讀寫的失真率變化曲線圖

缺少其他圖片進行對比試驗,驗證實驗結論的泛化性

沒有在單通道灰度圖像上做實驗



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章