Chuyển đổi ảnh OpenCV sang Pillow và ngược lại
OpenCV - Tut 1.2: OpenCV <-> Pillow
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:
- Chuyển kênh màu từ BGR sang RGB ta dùng hàm cv2.cvtColor
- 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].
- 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
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:
- 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
- Load data của Pillow Image thành numpy array np.array()
- Đả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:
- https://pillow.readthedocs.io/en/stable/reference/Image.html
- https://stackoverflow.com/questions/43232813/convert-opencv-image-format-to-pil-image-format#43234001
- https://stackoverflow.com/questions/14134892/convert-image-from-pil-to-opencv-format#14140796
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é!
- Minh: https://www.facebook.com/minhng.info
- Khám phá xử lý ảnh - GVGroup: https://www.facebook.com/groups/ip.gvgroup
Khám phá xử lý ảnh - GVGroup
Danh sách bài viết series OpenCV:
- Hashtag #OpenCV
- Tut 1: Xử lý ảnh - OpenCV đọc ghi hình ảnh (code Python và C++)
- Tut 1.1: Xử lý ảnh - Cấu trúc dữ liệu ảnh trong OpenCV. Pixel là gì?
- Tut 1.2: Xử lý ảnh - Chuyển đổi ảnh OpenCV sang Pillow và ngược lại
- Tut 2: Xử lý ảnh - OpenCV resize, crop và padding hình ảnh (code Python và C++)
- Tut 3: Xử lý ảnh - OpenCV biến đổi mức sáng hình ảnh (code Python)
- Tut 4: Xử lý ảnh - OpenCV vùng quan tâm (ROI) là gì? (code Python)
- Tut 4.1: Xử lý ảnh - OpenCV: vẽ văn bản, đường thẳng, mũi tên, hình chữ nhật, hình tròn, ellipse, đa giác
- Tut 4.2: Xử lý ảnh - Pha trộn ảnh trong OpenCV (blending)
- Tut 5: Xử lý ảnh - OpenCV ảnh nhị phân
- Tut 6: Xử lý ảnh - OpenCV cân bằng sáng (histogram equalization)
- Tut 7: Xử lý ảnh - OpenCV kỹ thuật cửa sổ trượt (sliding window)
- Tut 8: Xử lý ảnh - Convolution là gì?
- Tut 9: Xử lý ảnh - Làm mờ ảnh (blur)
- Tut 10: Xử lý ảnh - Gradient của ảnh là gì?
- Tut 11: Xử lý ảnh - Phát hiện cạnh Canny (Canny Edge Detection)
- Tut 12: Xử lý ảnh - Phát hiện đường thẳng bằng Hough Transform (Hough Line)
- Tut 13: Xử lý ảnh - Hiện thực phát hiện đoạn thẳng dùng Hough Transform (Hough Line)
- Tut 14: Xử lý ảnh - Giải thuật phân vùng Region Growing trên ảnh màu
- Tut 15: Xử lý ảnh - Giải thuật Background Subtraction trên ảnh màu
- Tut 16: Xử lý ảnh - Frame Subtraction để phát hiện chuyển động trong video
- Tut 17: Xử lý ảnh - HOG - Histograms of Oriented Gradients
- Tut 18: Xử lý ảnh - HOG - Huấn luyện mô hình phân loại người
- Tut 19: Xử lý ảnh - HOG - Phát hiện người
- Tut 20: Xử lý ảnh - Tổng hợp kinh nghiệm xử lý ảnh (End)
- Tut 21: Xử lý ảnh - Hiện thực trích đặc trưng Local Binary Patterns (LBP)
- Tut 22: Xử lý ảnh - Trích đặc trưng Gabor filters