Pin-Jiun / ComputerVision

0 stars 0 forks source link

22-Photoshop ver2.0 #22

Open Pin-Jiun opened 1 year ago

Pin-Jiun commented 1 year ago

功能列表

# ------  My photoshop Release notes ------ #
# ver2.0 (2020.10.11)
1. 新增視窗 `My histogram`,可以隨時查看現在圖片的 RGB直方圖
2. 新增視窗 `My color panel`,可以自己選顏色、自己畫點
# ver1.0 (2020.10.10)
1. 新增儲存檔案功能
2. 離開應用程式的穩定優化
3. 新增關閉程式的文字提示
4. 新增調整光線、對比度功能
5. 新增調整明度、飽和度功能
6. 新增旋轉圖片功能
7. 新增可增加圖片噪點的功能,相當於增加圖片顆粒感

1.新增視窗 My histogram,可以隨時查看現在圖片的 RGB直方圖

1-1. 建立新視窗 “My histogram”

# 直方圖視窗
cv2.namedWindow("My histogram", window_flags) # cv2.WINDOW_NORMAL)

1-2. 使用之前教學的函數,畫出圖片 RGB直方圖

plot_histogram = show_histogram(img_copy)    
# 顯示調整後的效果
cv2.imshow("My histogram", plot_histogram)

1-3. 最大難題:如何把 plot 變成 圖片? 這問題才是這次更新功能最大的難關啊, 我們之前絕大部分的plot都是直接顯示在 jupyter notebook 中, 現在要把它變成圖片移出來,該怎麼辦呢?

1-3-1. (failed) 嘗試使用 plt.show() plt.show() 我記得可以直接另外開啟視窗產生出圖片繪製結果, 但在 jupyter notebook 中,似乎沒辦法這樣做。

1-3-2. (passed?) 儲存 plt 為 jpg 檔後,再用 OpenCV 讀取 看標題就知道一定會成功了, 總之這也是很直覺的方法,直接儲存圖片後再讀取圖片, 但這會有一個很大的問題,我們的硬碟無時無刻都會一直在讀寫…

長久下來絕對不是一個好的方案 (除非想更快換硬碟)

1-3-3. (最佳化) 儲存 plt 進 memory buffer 後,再讀取出來 這個方法的精隨在於,他並沒有實際上做儲存檔案的動作, 而只是把這些資訊暫時放在記憶體的某個位置上, 而我們能用如同 1-3-2. 的方法把圖片讀取出來。

def show_histogram(img):    
    fig, ax = plt.subplots()
    # 畫出 RGB 三種顏色的分佈圖
    color = ('b','g','r')
    plt.style.use('dark_background')
    for idx, color in enumerate(color):
        histogram = cv2.calcHist([img],[idx],None,[256],[0, 256])
        ax.plot(histogram, color = color)

    s, (width, height) = fig.canvas.print_to_buffer()
    plot_histogram = np.frombuffer(s, np.uint8).reshape((height, width, 4))
    return plot_histogram

2. 新增視窗 My color panel,可以自己選色並作畫

這個不論是 photoshop 或 小畫家 應該都有的功能吧! 我們先新增一個能夠顯示顏色的視窗:

2-1. 建立新視窗 “My color panel”

# 控制顏色
cv2.namedWindow("My color panel", window_flags) # cv2.WINDOW_NORMAL)

2-2. 建立 RGB滑動條,並顯示對應顏色

# create trackbars for color change
cv2.createTrackbar('R','My color panel', 0, 255, nothing)
cv2.createTrackbar('G','My color panel', 0, 255, nothing)
cv2.createTrackbar('B','My color panel', 0, 255, nothing)

def color_panel(img_copy):      
    data = {}
    data['r']  = cv2.getTrackbarPos('R','My color panel')
    data['g']  = cv2.getTrackbarPos('G','My color panel')
    data['b']  = cv2.getTrackbarPos('B','My color panel')
    color_rgb = np.zeros((300,400,3), np.uint8)
    color_rgb[:] = [data['b'], data['g'], data['r']]

    # 改變顯示 window 的內容
    cv2.imshow('My color panel', color_rgb)
    cv2.resizeWindow("My color panel", 400, 300)

    return data['img']

使用 cv2.getTrackbarPos 得到顏色的回傳值,並使用 color_rgb[:] = [data['b'], data['g'], data['r']],組合顏色並顯示顏色

2-3. 建立打點用的筆,並對圖片作畫

這邊我特別說明我們需要製作一個開關,來偵測現在是否開啟打點模式。

製作開關:

cv2.createTrackbar('Points','My color panel', 0, 1, nothing)

偵測開關

data['points']  = cv2.getTrackbarPos('Points','My color panel')

如果開關打開,才是開啟打點模式:

if data['points'] == 1:            
    # 標記點位置
    cv2.circle(data['img'], (x,y), 3, (data['b'], data['g'], data['r']), 5, 16) 

    # 顯示修改的 (x,y) 位置 
    print("change points: (x, y) = ({}, {})".format(x, y))

2-4. 最後,注意 imshow 圖片更新邏輯 我們需要注意一件事情, 昨天我們的內容大多數都是「從原圖」出發,依據對應值開始調整畫面,

但這次的打點內容,是需要被保存在圖片上的。 因此我們必須要修改這部分的程式邏輯,

img = color_panel(img)
img_copy = np.copy(img)

我們每次進行打點時,更新原來的圖片, 而做其他變化時,不更新原來的圖片,從原圖片出發開始計算。

code如下

import cv2
import glob
import time
import math
import io
import PIL

from IPython.display import clear_output
import matplotlib
import matplotlib.pyplot as plt
import numpy as np

def resize_img(img, scale_percent):
    # percent of original size
    width = int(img.shape[1] * scale_percent / 100)
    height = int(img.shape[0] * scale_percent / 100)
    dim = (width, height)  
    resize_img = cv2.resize(img, dim, interpolation = cv2.INTER_AREA)  

    return resize_img
def rotate_img(img, rotation=0):
    (h, w, d) = img.shape
    center = (w // 2, h // 2)

    # 第一個參數旋轉中心,第二個參數旋轉角度(-順時針/+逆時針),第三個參數縮放比例
    M = cv2.getRotationMatrix2D(center, rotation, 1.0)

    # 第三個參數變化後的圖片大小
    rotate_img = cv2.warpAffine(img, M, (w, h))

    return rotate_img
def gaussian_noise(img, mean=0, sigma=0.1):
    # int -> float (標準化)
    img = img / 255.0
    # 隨機生成高斯 noise (float + float)
    noise = np.random.normal(mean, sigma, img.shape)
    # noise + 原圖
    gaussian_out = img + noise
    # 所有值必須介於 0~1 之間,超過1 = 1,小於0 = 0
    gaussian_out = np.clip(gaussian_out, 0, 1)

    # 原圖: float -> int (0~1 -> 0~255)
    gaussian_out = np.uint8(gaussian_out*255)
    # noise: float -> int (0~1 -> 0~255)
    noise = np.uint8(noise*255)

    return gaussian_out 
def modify_contrast_and_brightness(img, brightness=0 , contrast=0):   

    B = brightness / 255.0
    c = contrast / 255.0 
    k = math.tan((45 + 44 * c) / 180 * math.pi)

    img = (img - 127.5 * (1 - B)) * k + 127.5 * (1 + B)

    # 所有值必須介於 0~255 之間,超過255 = 255,小於 0 = 0
    img = np.clip(img, 0, 255).astype(np.uint8)

    return img
def modify_lightness_saturation(img, lightness = 0, saturation = 0):
    # lightness 調整為  "1 +/- 幾 %"
    # saturation 調整為 "1 +/- 幾 %"
    origin_img = img

    # 圖像歸一化,且轉換為浮點型
    fImg = img.astype(np.float32)
    fImg = fImg / 255.0

    # 顏色空間轉換 BGR -> HLS
    hlsImg = cv2.cvtColor(fImg, cv2.COLOR_BGR2HLS)
    hlsCopy = np.copy(hlsImg)

    # 亮度調整
    hlsCopy[:, :, 1] = (1 + lightness / 100.0) * hlsCopy[:, :, 1]
    hlsCopy[:, :, 1][hlsCopy[:, :, 1] > 1] = 1  # 應該要介於 0~1,計算出來超過1 = 1

    # 飽和度調整
    hlsCopy[:, :, 2] = (1 + saturation / 100.0) * hlsCopy[:, :, 2]
    hlsCopy[:, :, 2][hlsCopy[:, :, 2] > 1] = 1  # 應該要介於 0~1,計算出來超過1 = 1

    # 顏色空間反轉換 HLS -> BGR 
    result_img = cv2.cvtColor(hlsCopy, cv2.COLOR_HLS2BGR)
    result_img = ((result_img * 255).astype(np.uint8))

    return result_img

def show_histogram(img):    
    fig, ax = plt.subplots()
    # 畫出 RGB 三種顏色的分佈圖
    color = ('b','g','r')
#     plt.ion()
    plt.style.use('dark_background')
    for idx, color in enumerate(color):
        histogram = cv2.calcHist([img],[idx],None,[256],[0, 256])
        ax.plot(histogram, color = color)
#         ax.xlim([0, 256])

#     fig.canvas.tostring_rgb()

#     plt.savefig("plot_histogram.jpg")
#     plot_histogram = cv2.imread('plot_histogram.jpg')

    s, (width, height) = fig.canvas.print_to_buffer()

    # Option 2a: Convert to a NumPy array.
    plot_histogram = np.frombuffer(s, np.uint8).reshape((height, width, 4))

#     print(plot_histogram.shape)

    return plot_histogram
def color_panel(img_copy):
    def mouse_handler(event, x, y, flags, data):                  
        if event == cv2.EVENT_LBUTTONDOWN:
            if data['points'] == 1:            
                # 標記點位置
                cv2.circle(data['img'], (x,y), 3, (data['b'], data['g'], data['r']), 5, 16) 

                # 顯示修改的 (x,y) 位置 
                print("change points: (x, y) = ({}, {})".format(x, y))
            else:
                pass # do nothing

    data = {}
    data['img'] = img_copy.copy()
    data['r']  = cv2.getTrackbarPos('R','My color panel')
    data['g']  = cv2.getTrackbarPos('G','My color panel')
    data['b']  = cv2.getTrackbarPos('B','My color panel')
    data['points']  = cv2.getTrackbarPos('Points','My color panel')
    color_rgb = np.zeros((300,400,3), np.uint8)
    color_rgb[:] = [data['b'], data['g'], data['r']]

    # 利用滑鼠回傳值,資料皆保存於 data dict中
    cv2.setMouseCallback("My photoshop", mouse_handler, data)

    # 改變顯示 window 的內容
#     cv2.imshow("My photoshop", data['img'])
    cv2.imshow('My color panel', color_rgb)
    cv2.resizeWindow("My color panel", 400, 300)

    return data['img']
# 讀取彩色圖像
img = cv2.imread('./testdata/cat.jpg', cv2.IMREAD_COLOR)

h, w, dim = img.shape

# control analysis size
if h>=1000 or w>=1000:
    img = resize_img(img, scale_percent=30)

half_MAX_VALUE = 100
half_BIG_MAX_VALUE = 255

window_flags = cv2.WINDOW_AUTOSIZE| cv2.WINDOW_KEEPRATIO

# 修圖主視窗
cv2.namedWindow("My photoshop", window_flags) # cv2.WINDOW_NORMAL)

# 直方圖視窗
cv2.namedWindow("My histogram", window_flags) # cv2.WINDOW_NORMAL)

# 控制顏色
cv2.namedWindow("My color panel", window_flags) # cv2.WINDOW_NORMAL)

def nothing(*arg):
    pass

# create trackbars for color change
cv2.createTrackbar('R','My color panel', 0, 255, nothing)
cv2.createTrackbar('G','My color panel', 0, 255, nothing)
cv2.createTrackbar('B','My color panel', 0, 255, nothing)
cv2.createTrackbar('Points','My color panel', 0, 1, nothing)

# 滑動塊
cv2.createTrackbar("rotation", "My photoshop", 180, 2*180, nothing)
cv2.createTrackbar("brightness", "My photoshop", half_MAX_VALUE, 2*half_MAX_VALUE, nothing) # 亮度
cv2.createTrackbar("contrast", "My photoshop", half_BIG_MAX_VALUE, 2*half_BIG_MAX_VALUE, nothing) # 對比
cv2.createTrackbar("lightness", "My photoshop", half_MAX_VALUE, 2*half_MAX_VALUE, nothing) # 亮度
cv2.createTrackbar("saturation", "My photoshop", half_BIG_MAX_VALUE, 2*half_BIG_MAX_VALUE, nothing) # 飽和度
# 陰影
# 色溫
# 銳化

cv2.createTrackbar("noise", "My photoshop", 0, half_MAX_VALUE, nothing) # 顆粒點

# 調整飽和度和亮度
while True:
    time.sleep(0.1) # for better memory use

    # My color panel (some will change and no reverse)
    img = color_panel(img)
    img_copy = np.copy(img)

    rotation = cv2.getTrackbarPos('rotation', 'My photoshop') - 180
    lightness = cv2.getTrackbarPos('lightness', 'My photoshop') - half_MAX_VALUE 
    saturation = cv2.getTrackbarPos('saturation', 'My photoshop') - half_BIG_MAX_VALUE 
    brightness = cv2.getTrackbarPos('brightness', 'My photoshop') - half_MAX_VALUE
    contrast = cv2.getTrackbarPos('contrast', 'My photoshop') - half_BIG_MAX_VALUE 
    noise = cv2.getTrackbarPos('noise', 'My photoshop')

    img_copy = rotate_img(img_copy, rotation)
    img_copy = modify_lightness_saturation(img_copy, lightness, saturation)
    img_copy = modify_contrast_and_brightness(img_copy, brightness , contrast)   

    img_copy = gaussian_noise(img_copy, mean=0, sigma=noise/100)

    plot_histogram = show_histogram(img_copy)

    # 顯示調整後的效果
    cv2.imshow("My histogram", plot_histogram)

    msg = "Press ESC to exit, or press S to save and exit."
    img_copy = cv2.putText(img_copy, msg, (10, h-10), cv2.FONT_HERSHEY_COMPLEX, 0.5, (255, 255, 255), 1, cv2.LINE_AA)

    # 顯示調整後的效果
    cv2.imshow("My photoshop", img_copy)

    ch = cv2.waitKey(5)
    if ch == 27:
        # 按 ESC 鍵退出
        break
    elif ch == ord('s'):
        # 按 s 鍵保存結果並退出
        cv2.imwrite("result.jpg", img_copy)
        break

# 關閉所有的窗口
cv2.destroyAllWindows()

https://www.wongwonggoods.com/all-posts/python/python_opencv/opencv-my-photoshop-v2/