ใครเขียนโปรแกรมภาษาไพธอนเพื่อจัดการภาพแล้วเคยเจอปัญหาว่าภาพที่อ่านเข้ามามันถูกหมุนหรือพลิกกลับด้าน ไม่เหมือนกับที่ปรากฏในเว็บเบราว์เซอร์หรือโปรแกรมอ่านภาพทั่วๆไปบ้างหรือเปล่า?
บทความนี้จะอธิบายสาเหตุของปัญหาที่เกิดขึ้นนี้และวิธีการแก้ปัญหา
เนื้อหานี้ได้เขียนเป็นภาษาญี่ปุ่นลงในเว็บ qiita ด้วย
→
読み込んだ画像が回転したり反射したりする原因と対策 ปัญหาที่เจอ ขอยกตัวอย่างโดยใช้ภาพนี้
irelcp.jpg
ลองโหลดไปลองเปิดดูกันได้ เช่นลองเปิดแล้วก็แสดงด้วย matplotlib แบบนี้
import matplotlib.pyplot as plt
rup = plt.imread('irelcp.jpg')
plt.imshow(rup)
plt.show()
ก็จะได้ภาพที่ถูกหมุนทวนเข็มนาฬิกาไป ๙๐ องศาแบบนี้
ทำไมถึงเป็นแบบนี้? เกิดอะไรขึ้น? ภาพนี้มีอะไรบางอย่างผิดพลาดหรือ? หรือว่าผีหลอก!?
ใครที่เจอแบบนี้ก็คงตั้งข้อสงสัยไปต่างๆมากมาย แต่ที่จริงแล้วนี่ไม่ใช่ความผิดพลาดของตัวโปรแกรมหรอก ลองเปิดอ่านดูด้วยวิธีอื่นเช่น skimage, imageio หรือ PIL (pillow) ดูก็ได้ผลเหมือนกัน
skimage
from skimage import io
rup = io.imread('irelcp.jpg')
print(rup.shape) # (600, 450, 3)
(shape ในที่นี้แสดงขนาดของอาเรย์ภาพ ซึ่งแสดงเป็น (ความสูง, ความกว้าง, สี))
imageio
import imageio
rup = imageio.imread('irelcp.jpg')
print(rup.shape) # (600, 450, 3)
PIL
from PIL import Image
rup = Image.open('irelcp.jpg')
print(rup.height,rup.width) # 600 450
ไพธอนนั้นมีวิธีการที่หลากหลายในการเปิดอ่านไฟล์ แต่ว่าวิธีการอ่านส่วนใหญ่ที่นิยมใช้กันนั้นต่างก็อ่านภาพนี้ออกมาได้แบบนี้กันหมดเลย
ดังนั้นเพื่อที่จะได้อ่านให้ถูกต้องเราจำเป็นต้องรู้สาเหตุและทางแก้ไข
สาเหตุของปัญหา ที่จริงแล้วปัญหานี้เกิดกับภาพบางส่วนที่มีข้อมูล exif
exif คืออะไรนั้นได้เคยอธิบายไปในบทความก่อนหน้านี้แล้ว ในที่นี้ขอไม่อธิบายรายละเอียด หากสนใจสามารถอ่านได้ใน
→
จัดการข้อมูล exif ในไฟล์รูปภาพด้วย PIL และ piexif ภายในข้อมูล exif นั้นมีข้อมูลส่วนที่เรียกว่า orientation คือค่าที่บอกว่าจะมีการปรับเปลี่ยนการจัดวางรูปภาพอย่างไร
อย่างเช่นในกรณีภาพที่มีปัญหานี้ เกิดจากการที่ข้อมูล orientation ได้ระบุว่าให้ "หมุนภาพตามเข็ม ๙๐ องศา"
ดังนั้นที่จริงแล้วก็คือภาพที่เห็นว่าหมุนทวนเข็มไป ๙๐ องศาที่เห็นแสดงเมื่ออ่านด้วยไพธอนนี้คือภาพเดิมจริงๆที่ยังไม่โดนปรับแก้การจัดวางตามข้อมูลที่ระบุใน exif
เวลาที่แสดงผลตามเบราว์เซอร์หรือโปรแกรมดูรูปภาพทั่วไปนั้นมักจะมีการนำข้อมูล exif นี้มาคิดแล้วปรับการจัดวางให้ตามที่ควรจะเป็น แต่ว่าเวลาอ่านด้วยฟังก์ชันอ่านภาพในไพธอนมันไม่ได้อ่านตรงนี้ให้ เราจึงจำเป็นต้องเขียนโค้ดเพิ่มเติมเพื่อจัดการตรงนี้เอง
ข้อมูล exif นี้สามารถดูได้โดยการปิดภาพด้วยมอดูล PIL แล้วใช้เมธอด
._getexif()
from PIL import Image
rup = Image.open('irelcp.jpg')
print(rup._getexif()) # {274: 6}
ค่า 274 เป็นรหัสของข้อมูลส่วน orientation ส่วน 6 เป็นรหัสแสดงการจัดเรียง มีความหมายดังนี้
1 |
ไม่เปลี่ยนแปลง |
2 |
กลับซ้ายขวา |
3 |
กลับซ้ายขวาหน้าหลัง (หมุน ๑๘๐ องศา) |
4 |
กลับบนล่าง |
5 |
กลับระหว่างซ้ายล่างกับขวาบน |
6 |
หมุนตามเข็ม ๙๐ องศา |
7 |
กลับระหว่างซ้ายบนกับขวาล่าง |
8 |
หมุนทวนเข็ม ๙๐ องศา |
ภาพโดยส่วนใหญ่จะเป็น 1 คือไม่จำเป็นต้องไปทำอะไร แต่ว่าถ้าเจอเลขอื่นก็จำเป็นต้องนำภาพมาแก้ก่อนจึงจะใช้งานได้อย่างถูกต้อง
ภาพที่เป็นแบบนี้ส่วนใหญ่จะเป็นภาพถ่ายที่ถ่ายด้วยแอปพลิเคชันบางอย่าง หรือถูกปรับแก้ด้วยโปรแกรมบางอย่าง ซึ่งไปพลิกภาพโดยการระบุข้อมูล orientation ใน exif แทนที่จะพลิกภาพจริงๆ
โอกาสเจอภาพแบบนี้อาจมีไม่มากนัก บางคนอาจไม่เคยเจอก็ได้ แต่ถ้าใครเขียนโปรแกรมจัดการรูปภาพละก็ควรจะคำนึงถึงตรงนี้ด้วย ไม่งั้นเกิดไปเจอภาพที่เป็นแบบนี้เข้าก็จะทำให้โปรแกรมทำงานผิดพลาดโดยไม่รู้ตัว เป็นสาเหตุของปัญหาได้
สำหรับวิธีการแก้นั้นต่อไปนี้ขอแนะนำ ๒ วิธีด้วยกัน
การแก้โดยใช้ opencv มอดูลที่กล่าวมาข้างต้นส่วนใหญ่ของไพธอนนั้นเมื่อเปิดอ่านภาพจะไม่มีการปรับแก้ภาพตามข้อมูล exif ให้อัตโนมัติ แต่ว่าก็มีบางมอดูลที่ปรับแก้ให้เลยเหมือนกัน นั่นก็คือ opencv (cv2 นั่นเอง ดังนั้นถ้าใครเปิดอ่านด้วย opencv อยู่แล้วก็ไม่จำเป็นต้องมากังวลเรื่องนี้เลย
เกี่ยวกับเรื่องการใช้ opencv ในไพธอนถ้าใครสนใจสามารถอ่านได้ในบทความนี้
→
opencv-python เบื้องต้น ลองเปิดอ่านดูโดยใช้ opencv กันเลย
import cv2
import matplotlib.pyplot as plt
rup = cv2.imread('irelcp.jpg')
print(rup.shape) # (450, 600, 3)
plt.imshow(rup)
plt.show()
จะเห็นว่าภาพถูกแสดงหันถูกทิศ ไม่หมุนเพี้ยนไป แต่อ้าว ทำไมสีมันดูแปลกๆ?
ที่จริงแล้วภาพที่อ่านด้วย opencv นั้นมีปัญหาอยู่อย่างก็คือสีจะแสดงเป็นระบบ BGR ซึ่งตรงกันข้ามกับระบบ RGB ที่ใช้กันทั่วไป ดังนั้นพอเอามาอ่านใน matplotlib ทันทีแบบนี้ก็จะแสดงผลแปลกๆอย่างที่เห็นเพราะสีน้ำเงินกับแดงถูกสลับกัน
ดังนั้นถ้าใครคิดแค่ว่าจะอุตส่าห์ใช้ opencv เพื่อเปิดอ่านรูปภาพละก็อาจจะไม่ค่อยสะดวกนัก ต้องมาแปลงสีกลับก่อน เช่นทำแบบนี้
rup = rup[:,:,::-1]
นอกจากนี้แล้วยังมีข้อเสียอีกอย่างคือ opencv อ่านไฟล์ที่ชื่อเป็นภาษาไทยหรือภาษาอื่นๆไม่ได้ จึงต้องมาหาทางรับมือกับตรงนี้อีกที
ดังนั้นแล้ว ขอแนะนำอีกวิธีมากกว่า นั่นคือการใช้ PIL
การแก้โดยใช้ PIL PIL นั้นมีฟังก์ชัน ImageOps.exif_transpose อยู่ แค่ใช้ฟังก์ชันนี้ภาพก็จะถูกปรับพลิกหรือหมุนกลับตามข้อมูล orientation ของ exif
เราอาจลองเขียนฟังก์ชันสำหรับอ่านไฟล์ไว้ให้ตรวจดูว่าภาพนี้มี orientation เป็น 2 ขึ้นไปหรือเปล่า ถ้าเป็นก็ใช้ฟังก์ชันนี้ทำการปรับแก้ซะ
import numpy as np
from PIL import Image,ImageOps
def imread(f):
rup = Image.open(f)
exif = rup._getexif()
if(exif and 274 in exif and exif[274]!=1):
rup = ImageOps.exif_transpose(rup)
return np.array(rup)
แล้วก็ลองเอาฟังก์ช้นนี้มาใช้งานดู
rup = imread('irelcp.jpg')
plt.imshow(rup)
plt.show()
เท่านี้ก็น่าจะได้ภาพที่แสดงผลอย่างถูกต้องแล้ว
หรืออาจเขียนให้แก้แล้วก็บันทึกทับไฟล์เดิมไปเลย แบบนี้ครั้งต่อไปก็ไม่ต้องมาห่วงเรื่อง exif แล้ว
f = 'irelcp.jpg'
rup = Image.open(f)
exif = rup._getexif()
if(exif and 274 in exif and exif[274]!=1):
rup = ImageOps.exif_transpose(rup)
rup.save(f)