Xử lý khi đường dẫn có ký tự tiếng Nhật trong OpenCV (Python)
Với những người làm việc với Nhật như mình thì thỉnh thoảng gặp những vấn đề liên quan đến encoding là chuyện bình thường. Mới đây mình có gặp vấn đề về encode ngay trong đường dẫn, tức là còn chưa kịp đọc file 😂 khi dùng thư viện OpenCV để đọc và ghi file ảnh.
Khái quát vấn đề
Chuyện Shift_JIS ở Nhật
Người Nhật vẫn dùng Shift_JIS chứ không chuyển sang UTF-8 và với Windows 10 phiên bản tiếng Nhật thì mặc định vẫn là dùng Shift_JIS. Lý do người Nhật vẫn duy trì việc sử dụng Shift_JIS chắc là do truyền thống. Shift_JIS là chuẩn encoding được sử dụng rất sớm, trước khi mà UTF-8 trở lên phổ biến như ngày hôm nay. Ngoài ra còn một lý do nữa mình đoán là Shift_JIS chỉ cần dùng 2 byte mã hoá trong khi UTF-8 cần tới 3-4 byte cho việc encoding tiếng Nhật nói riêng và CJK nói chung 😅
Và nếu làm việc với họ thì việc để Shift_JIS cho đồng bộ là việc nên làm. Lý do là để tránh những vấn đề xảy ra do lệch encode (nhất là khi gửi và nhận file zip, tên file và thư mục bên trong sẽ lỗi hết). Cũng may là lần này để Shift_JIS cho đồng bộ nên nhờ đó mà phát hiện ra vấn đề luôn 🤣
Vấn đề với OpenCV
Quay trở lại với vấn đề mình muốn nói hôm nay. Riêng với ngôn ngữ Python, hiện tại việc xử lý dữ liệu Unicode đã rất dễ dàng và hầu như mình không gặp nhiều vấn đề với nó. Ngay cả những thư viện chuẩn để làm việc với file và thư mục cũng làm việc ngon lành khi mà OS sử dụng Shift_JIS còn mã nguồn thì dùng UTF-8 như bình thường mà không gặp vấn đề gì cả. Tức là việc chuyển đổi encoding đã được hỗ trợ mặc định rồi 😍 và nhiều thư viện cài thêm cũng như vậy.
Thế nhưng một thư viện rất quan trọng khi làm việc với hình ảnh là OpenCV thì lại chưa hỗ trợ việc này 🤐 (mình đoán là do thư viện này dùng nhiều code C++ nên việc hỗ trợ này tương đối khó). Khi sử dụng OpenCV với Python thì mình gặp 2 vấn đề như sau (có lẽ là do cùng nguyên nhân):
cv2.imread
không thể đọc được file nếu đường dẫn có chứa ký tự tiếng Nhậtcv2.imwrite
ghi file sai nếu đường dẫn có chứa ký tự tiếng Nhật
Lúc đầu mình gặp vấn đề với cv2.imwrite
, khi mà muốn ghi file với đường dẫn như sau:
output\テスト\foo.png
Thì kết quả là nhận được file ở đường dẫn thế này
output\繝・せ繝・foo.png
Nhìn vào kết quả này thì mình khá chắc vấn đề liên quan đến encoding. Thế nhưng điểm kỳ lạ là ở chỗ, chỗ ký tự không đọc được kia, một phần thì giống chữ テスト
encode bằng UTF-8 rồi decode sai bằng Shift_JIS, một phần thì lại không giống. Với lại, nếu encode sai thì đúng ra từ chỗ \foo.png
trở đi vẫn giữ nguyên, thì kết quả và mình phải nhận được foo.png
trong một thư mục lạ hoắc chứ không phải một file viết liền thế kia.
Sau đó thì mình kiểm tra với cv2.imread
thì cũng gặp vấn đề tương tự. Do đường dẫn bị lệch encode nên không thể đọc được file mình mong muốn.
Giải quyết
Đổi encode
Mình thử đổi OS sang sử dụng encode UTF-8 thì kết quả là vấn đề cũng tự nhiên biến mất 🤗. Như vậy là OpenCV có hỗ trợ UTF-8 và chỉ hỗ trợ UTF-8 mà thôi. Với những OS sử dụng encode khác thì sẽ bị lỗi decode sai như ở trên. Nếu thế thì khả năng lỗi này chỉ gặp khi dùng Windows, vì mình biết MacOS hay GNU/Linux đều sử dụng UTF-8 hết rồi.
Thế nhưng đến lúc này vấn đề chưa được giải quyết, vì đổi encode ở máy mình thì dễ rồi, nhưng code mình viết cần phải chạy trên máy khách hàng, mà khi đó không thể bắt họ đổi encode giống như mình được 😪. Đúng là mệt mỏi, tại sao dự án này khách lại dùng Windows cơ chứ 😅
Sau khi tìm hiểu một hồi thì mình thấy một issue của OpenCV liên quan đến chuyện này, và có vẻ còn nhiều issue khác liên quan. Tất cả đều đã closed nhưng không được giải quyết. Họ có để lại comment như sau:
There are 2 alternatives to use
wchar_t
strings with OpenCV:
- Convert
wchar_t
strings to UTF-8 and pass UTF-8 string ascv::imread
andcv::imwrite
parameter. UTF-8 string is handled by systemfopen
call and it’s behavior depends on OS support and locale. Seembstowcs
in C++ standard for more details.- OpenCV provides
cv::imdecode
andcv::imencode
functions that allow to decode and encode image using memory buffer as input and output. The solution decouples file IO and image decoding and allows to manage path strings, locales, etc in user code. See code snippet forcv::imencode
bellow.fopen
can be replaced with_wfopen
for wide strings support. See Microsoft reference manual for details: https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/fopen-wfopen?view=vs-2019
Với phương án đầu tiên, đổi encode của OS thì OK rồi nhưng còn vấn đề với khách hàng. Mình lại thử code mã nguồn của Python thành Shift_JIS mà vẫn không thành công. Thử truyền tên file vào hàm bằng cách encode Shift_JIS như sau nhưng cũng không thành công.
"output\テスト\foo.png".decode("Shift_JIS").encode("unicode-escape")
Vậy là chỉ còn phương án 2. Mình thì không thành thạo mấy hàm imencode
với imdecode
lắm nên hơi mất thời gian tìm hiểu một chút. Sau khi mày mò đủ nguồn trên mạng thì cuối cùng, mình cũng có thể viết lại hai hàm dựng sẵn của OpenCV là imread
và imwrite
(thực ra không phải tự viết mà đi “tham khảo” nhiều nơi 🤣)
Viết lại cv2.imread
Thay vì dùng trực tiếp cv2.imread
có sẵn, thì mình chuyển sang dùng np.fromfile
(may sao thư viện numpy không bị lỗi tương tự như OpenCV 🤩) kết hợp với cv2.imdecode
như sau. Mình lấy luôn tên hàm giống với cv2.imread
để có gì “Find & Replace” cho nhanh 😊
import numpy as np
import cv2
def imread(filename, flags=cv2.IMREAD_COLOR, dtype=np.uint8):
try:
n = np.fromfile(filename, dtype)
img = cv2.imdecode(n, flags)
return img
except Exception as e:
print(e)
return None
Viết lại cv2.imwrite
Tương tự như trên, thay vì dùng cv2.imwrite
thì mình chuyển sang sử dụng cv2.imencode
và np.ndarray.tofile
như sau:
import numpy as np
import cv2
import os
def imwrite(filename, img, params=None):
try:
ext = os.path.splitext(filename)[1]
result, n = cv2.imencode(ext, img, params)
if result:
with open(filename, mode='w+b') as f:
n.tofile(f)
return True
else:
return False
except Exception as e:
print(e)
return False
Sau khi viết được 2 hàm như trên, và mình chỉ cần thay hết các hàm của OpenCV thành các hàm này (tìm rồi xoá prefix cv2.
thôi) là chương trình lại chạy như ngựa 😊
Kết luận
Encoding vẫn là vấn đề muốn thuở với các bạn làm việc với khách hàng Nhật như mình. Mình nghĩ trong tương lai một vài năm tới người Nhật vẫn chưa chuyển sang dùng UTF-8 được đâu, Shift_JIS vẫn là một phần tất yếu của cuộc sống. Hy vọng bài viết giúp ích cho các bạn trong công việc và giúp giải quyết phần nào khó khăn mà các bạn có thể cũng gặp phải như mình 😀
Welcome
Đây là thế giới của manhhomienbienthuy (naa). Chào mừng đến với thế giới của tôi!
Bài viết liên quan
Bài viết mới
Chuyên mục
Lưu trữ theo năm
Thông tin liên hệ
Cảm ơn bạn đã quan tâm blog của tôi. Nếu có bất điều gì muốn nói, bạn có thể liên hệ với tôi qua các mạng xã hội, tạo discussion hoặc report issue trên Github.