Chuyển đổi ảnh OpenCV sang Pillow và ngược lại

  Jul 23, 2021      2m      0   
 

OpenCV - Tut 1.2: OpenCV <-> Pillow

Chuyển đổi ảnh OpenCV sang Pillow và ngược lại

Tut 1.2: Chuyển đổi ảnh qua lại giữa hai thư viện OpenCV và Pillow

Trong quá trình làm về Xử Lý Ảnh trong môi trường thực tế, đôi khi một thư viện ảnh là chưa đủ để giải quyết các thử thách mà ta cần phải vượt qua. Mỗi thư viện ảnh đều có cộng đồng phát triển và điểm mạnh riêng. Tại thời điểm viết bài (23/07/2021), số sao dành cho hai thư viện Xử Lý Ảnh này là:

Muốn sử dụng hai thư viện xử lý ảnh cùng lúc, ta cần phải chuyển đổi cấu trúc dữ liệu ảnh qua lại giữa thư viện OpenCV và Pillow để có thể dùng đan xen.

Điểm mạnh của thư viện OpenCV là thư viện rất đồ sộ, cộng đồng lớn, lịch sử phát triển lâu đời, có phiên bản C++ (tối ưu hơn phiên bản Python), có thể tích hợp mobile, tài liệu API và Tutorial khá chi tiết, …

Điểm mạnh của thư viện Pillow là dễ cài đặt, API cấp cao dễ sử dụng, tiếp cận dễ dàng, … mình chưa thực sự dùng hết các tính năng mà Pillow cung cấp, nhưng một trong những tính năng rất giá trị của Pillow là dễ dàng trong việc vẽ lên ảnh (hình học, văn bản có thể load font, blending ảnh với overlay, …) đôi khi nếu mình chỉ thao tác xử lý ảnh sơ sơ thì dùng Pillow cho nhanh vì tính gọn nhẹ dễ cài đặt. Vác anh OpenCV sáu múi thì đao to búa lớn quá hahah.

Chuyển đổi ảnh từ OpenCV sang Pillow

OpenCV lưu trữ ảnh với kiểu dữ liệu là mảng Numpy (array) ba chiều H x W x C (height x width x channel). Đối với ảnh màu (channel=3) thì trật tự sắp xếp các kênh màu là BGR. Tham khảo bài viết trước của Minh Tut 1.1: Xử lý ảnh - Cấu trúc dữ liệu ảnh trong OpenCV. Pixel là gì?

Pillow lưu trữ ảnh với kiểu dữ liệu cũng là mảng Numpy ba chiều H x W x C, nhưng trật tự ba kênh màu là RGB chính nhờ điểm khác biệt này nên trước khi chuyển đổi ta cần sắp xếp lại trật tự các kênh màu trong numpy array.

Quá trình chuyển đổi từ OpenCV sang Pillow như sau:

  1. Chuyển kênh màu từ BGR sang RGB ta dùng hàm cv2.cvtColor
  2. Chuyển đổi kiểu dữ liệu float32 sang uint8 - bước này là tùy chọn vì OpenCV hỗ trợ ảnh có giá trị pixel là số thực (float) nằm trong đoạn [0, 1].
  3. Tạo Pillow Image object bằng cách load data từ numpy dùng hàm Image.fromarray của Pillow cung cấp https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.fromarray

Lưu ý: Pillow có một class Image riêng để bọc dữ liệu ảnh lại (data có cấu trúc là numpy array) ta có thể dùng Pillow Image object này để gọi các phương thức trong class (vd: save(), resize(), show(), …) cách tiếp cận và sử dụng bên Pillow hơi khác OpenCV bạn nhỉ :). Chính vì điểm này nên khi ta có data theo cấu trúc numpy đúng định dạng ảnh rồi, vẫn phải dùng hàm fromarray để tạo đối tượng Image trong Pillow.

Phần hấp dẫn mà các ông đang mong chờ đến rồi, đó là ảnh mẫu và full source code không che copy paste chạy được ngay ^^~. Đuổi theo trend Minh quyết định lụm ảnh của cô giáo Vật Lý mà các ông bố ham học nha hahah.

co_giao_minh_thu.jpg

co_giao_minh_thu.jpg

Ghi chú: do ảnh này mình tìm kiếm và lấy trên mạng, nên không biết chính chủ để credit vào; ảnh này được sử dụng vào mục đích học tập / nghiên cứu (giáo dục), và mang tính chất phi thương mại!

Nguồn lụm ảnh: https://tiin.vn/chuyen-muc/360-do-fan/co-giao-trieu-view-minh-thu-gay-sot-cong-dong-mang-khi-duoc-hoc-tro-day-choi-pubg-mobile.html

opencv2pil.py

import numpy as np
import cv2
from PIL import Image # pip3 install Pillow

IMAGE_OPENCV_PATH = "co_giao_minh_thu.jpg"
IMAGE_PIL_PATH = "co_giao_pil.jpg"

def npf32u8(np_arr):
    # intensity conversion
    if str(np_arr.dtype) != 'uint8':
        np_arr = np_arr.astype(np.float32)
        np_arr -= np.min(np_arr)
        np_arr /= np.max(np_arr) # normalize the data to 0 - 1
        np_arr = 255 * np_arr # Now scale by 255
        np_arr = np_arr.astype(np.uint8)
    return np_arr

def opencv2pil(opencv_image):
    opencv_image_rgb = cv2.cvtColor(opencv_image, cv2.COLOR_BGR2RGB) # convert BGR to RGB
    opencv_image_rgb = npf32u8(opencv_image_rgb) # convert numpy array type float32 to uint8
    pil_image = Image.fromarray(opencv_image_rgb) # convert numpy array to Pillow Image Object
    return pil_image

def main():
    opencv_image = cv2.imread(IMAGE_OPENCV_PATH)
    pil_image = opencv2pil(opencv_image) # Pillow Image object
    # ref: https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.save
    pil_image.save(IMAGE_PIL_PATH) # call method "save" from Pillow Image object
    print("- Image width x height: %d x %d" % (pil_image.width, pil_image.height))
    print("- Image mode: %s" % pil_image.mode)
    print("PILLOW Image saved @ %s" % IMAGE_PIL_PATH)
    print("Done!")
    pass

if __name__ == "__main__":
    main()
    print('-------')
    print('* Follow me @ ' + "\x1b[1;%dm" % (34) + ' https://www.facebook.com/kyznano/' + "\x1b[0m")
    print('* Minh fanpage @ ' + "\x1b[1;%dm" % (34) + ' https://www.facebook.com/minhng.info/' + "\x1b[0m")    
    print('* Join GVGroup @ ' + "\x1b[1;%dm" % (34) + 'https://www.facebook.com/groups/ip.gvgroup/' + "\x1b[0m")    
    print('* Thank you ^^~')    

Thực thi code trên bạn sẽ thấy kết quả in ra như sau:

root@e7eba89aeeaf:/workspace/OPENCV/opencv-pillow# python3 opencv2pil.py
- Image width x height: 960 x 960- Image mode: RGB
PILLOW Image saved @ co_giao_pil.jpgDone!
-------
* Follow me @  https://www.facebook.com/kyznano/
* Minh fanpage @  https://www.facebook.com/minhng.info/
* Join GVGroup @ https://www.facebook.com/groups/ip.gvgroup/
* Thank you ^^~

Ảnh ghi xuống đĩa sau khi convert Opencv qua Pillow ta thấy rằng nó giống hệt ảnh gốc về kích thước, màu sắc thể hiện đã chuyển đổi đối tượng ảnh thành công giữa hai thư viện.

Ghi chú: ta có thể dùng kĩ thuật chuyển đổi ảnh này để trộn lại, giúp có thể dùng hai thư viện OpenCV và Pillow trong cùng một dự án.

Phần tiếp theo mình đi convert theo chiều ngược lại từ Pillow qua OpenCV nha.

Chuyển đổi ảnh Pillow sang OpenCV

Quá trình chuyển đổi ảnh Pillow sang OpenCV ta sẽ làm như sau:

  1. Convert ảnh Pillow về mode RGB, Pillow hỗ trợ nhiều chế độ kênh màu. Danh sách modes: https://pillow.readthedocs.io/en/stable/handbook/concepts.html#concept-modes
  2. Load data của Pillow Image thành numpy array np.array()
  3. Đảo thứ tự các kênh màu từ RGB thành BGR. Như đã nói, BGR là thứ tự các kênh màu mà OpenCV xử lý ta được numpy array cấu trúc dữ liệu phù hợp với các hàm trong thư viện OpenCV :).

Mời ae tham khảo code bên dưới để hiểu rõ thêm nhé:

pil2opencv.py

import numpy as np
import cv2
from PIL import Image # pip3 install Pillow

IMAGE_PIL_PATH = "co_giao_minh_thu.jpg"
IMAGE_OPENCV_PATH = "co_giao_minh_opencv.jpg"

def pil2opencv(pil_image):
    # print("pil_image attributes:", str(dir(pil_image))) # ['_Image__transformer', '__array_interface__', '__class__', '__copy__', ...]
    opencv_image = np.array(pil_image.convert("RGB"))
    # Convert RGB to BGR
    opencv_image = opencv_image[:, :, ::-1].copy()
    return opencv_image

def main():
    pil_image = Image.open(IMAGE_PIL_PATH)
    opencv_image = pil2opencv(pil_image)
    cv2.imwrite(IMAGE_OPENCV_PATH, opencv_image)
    print("- Image width x height: %d x %d" % (opencv_image.shape[1], opencv_image.shape[0]))
    print("PILLOW Image saved @ %s" % IMAGE_OPENCV_PATH)
    print("Done!")
    pass

if __name__ == "__main__":
    main()
    print('-------')
    print('* Follow me @ ' + "\x1b[1;%dm" % (34) + ' https://www.facebook.com/kyznano/' + "\x1b[0m")
    print('* Minh fanpage @ ' + "\x1b[1;%dm" % (34) + ' https://www.facebook.com/minhng.info/' + "\x1b[0m")    
    print('* Join GVGroup @ ' + "\x1b[1;%dm" % (34) + 'https://www.facebook.com/groups/ip.gvgroup/' + "\x1b[0m")    
    print('* Thank you ^^~')    

Thực thi code trên bạn sẽ thấy kết quả in ra như sau:

- Image width x height: 960 x 960
PILLOW Image saved @ co_giao_minh_opencv.jpg
Done!
-------
* Follow me @  https://www.facebook.com/kyznano/
* Minh fanpage @  https://www.facebook.com/minhng.info/
* Join GVGroup @ https://www.facebook.com/groups/ip.gvgroup/
* Thank you ^^~

Như vậy, khi có thể chuyển đối tượng ảnh qua lại giữa OpenCV và Pillow, ta có thể tận dụng các điểm mạnh của mỗi thư viện để nhanh chóng xử lý công việc.

Bài viết tham khảo:

Bài viết tiếp theo Tut 2: Xử lý ảnh - OpenCV resize, crop và padding hình ảnh (code Python và C++)


Cảm ơn bạn đã theo dõi bài viết. Hãy kết nối với tớ nhé!

Khám phá xử lý ảnh - GVGroup


Danh sách bài viết series OpenCV:



Khám phá xử lý ảnh - GVGroup




-->