φυβλαςのβλογ
บล็อกของ phyblas



[python] จัดการข้อมูล exif ในไฟล์รูปภาพด้วย PIL และ piexif
เขียนเมื่อ 2018/04/07 11:49
ปกติเวลาที่เราถ่ายภาพอะไรจากกล้องถ่ายรูปหรือมือถือ ข้อมูลที่อยู่ในรูปจะไม่ได้มีแค่ตัวรูปภาพ แต่จะยังประกอบไปด้วยข้อมูลที่บอกรายละเอียดเพิ่มเติมเกี่ยวกับภาพนั้น เช่นข้อมูล exif

exif (ย่อมาจาก Exchangeable image file format) คือข้อมูลเกี่ยวกับภาพถ่าย ซึ่งมักจะติดมากับไฟล์รูป .jpg ที่ถ่ายมา

ข้อมูลที่บรรจุก็เช่นว่าถ่ายจากกล้องอะไร รุ่นอะไร ความละเอียดเท่าไหร่ แล้วก็ข้อมูล GPS (ตำแหน่งสถานที่ที่ถ่าย) เป็นต้น

แต่ถ้านำภาพมาเปิดในโปรแกรมแต่งภาพบางชนิดแล้วก็เซฟใหม่ข้อมูลพวกนี้มักจะไม่ได้บันทึกไปด้วย กลายเป็น

ปกติในไพธอนมีมอดูลชื่อ PIL ซึ่งมักใช้จัดการรูปภาพและสามารถดึงข้อมูล exif ออกมาได้ด้วย

เพียงแต่หากใช้แค่ PIL จะยากที่จะแก้ข้อมูล exif ที่อยู่ภายในได้ ตรงจุดนี้หากใช้มอดูล piexif ช่วยก็จะสามารถแก้ข้อมูล exif ได้อย่างง่าย

ดังนั้นในบทความนี้จะพูดถึงวิธีการใช้ PIL และ piexif เพื่อที่จะแต่งภาพโดยสามารถรักษาข้อมูล exif ของรูปภาพไว้ รวมทั้งแก้ไขเปลี่ยนแปลงได้



เริ่มต้นใช้งาน
สามารถใช้ pip ติดตั้งได้โดยง่าย สำหรับ piexif ก็ใช้ชื่อตามนั้น ส่วน PIL นั้นใช้ชื่อว่า pillow
pip install pillow
pip install piexif

มีมอดูลที่ชื่อ pyexif อยู่ด้วย แม้ชื่อจะคล้ายกันชวนสับสน แต่ว่าเป็นคนละตัวกัน ที่จะใช้ในที่นี้คือ piexif ไม่ใช่ pyexif

ที่จริงได้ไปอ่านบล็อกส่วนตัวของคนที่ทำ piexif จึงรู้ว่าที่จริงเขาอยากตั้งชื่อเป็น pyexif แต่มีคนใช้ชื่อนี้ไปแล้วก็เลยต้องเปลี่ยนเป็น piexif เรื่องชื่อมอดูลนี่ใครสร้างเร็วกว่าก็ได้สิทธิ์จองชื่อก่อน

PIL นั้นที่จริงใช้ทำงานด้านแต่งภาพทำอะไรได้หลากหลาย แต่ในที่นี้จะเน้นแค่เรื่องการจัดการกับข้อมูล exif

ขอยกตัวอย่างโดยใช้ภาพที่เพิ่งถ่ายไปเมื่อวานนี้เลย



เพียงแต่ว่านี่เป็นภาพที่เอามาย่อขนาดแล้ว ของเดิมถ่ายจากกล้องมือถือ ตอนที่ถ่ายมาสามารถดูรายละเอียดส่วนหนึ่งได้จากในเครื่อง เช่นแบบนี้



อ่า... พอดีลองตั้งเป็นภาษาลาวไว้

เอาใหม่ ถ้าเปลี่ยนกลับเป็นภาษาไทยก็จะเป็นแบบนี้



ข้อมูลในนี้ส่วนใหญ่คือข้อมูล exif ส่วนขนาดหรือเวลาหรือชื่อไฟล์พวกนี้เป็นคุณสมบัติธรรมดาทั่วไปของไฟล์รูปอยู่แล้ว



เปิดอ่านและบันทึกภาพ
สามารถใช้ PIL เปิดอ่านภาพขึ้นมาแล้วบันทึกภาพใส่อีกไฟล์ได้ตามนี้
from PIL import Image
chuerup = 'miku20180406.jpg'
chuerupmai = 'miku20180406-.jpg'
rup = Image.open(chuerup)
rup.save(chuerupmai)

ให้ลองโหลดภาพตัวอย่างด้านบนนี่ไปแล้วลองไปรันโปรแกรมตามในเครื่องได้เลย

ผลที่ได้ก็จะพบว่าได้ภาพหน้าตาเหมือนเดิมออกมา เพียงแต่หากไปดูที่ข้อมูลราย
ละเอียดต่างๆของไฟล์ภาพจะพบว่าหายไปแล้ว อีกทั้งขนาดไฟล์ก็เล็กลงด้วย

วิธีการดูข้อมูลของภาพจากในเครื่องคอมก็แล้วแต่ว่าใช้โปรแกรมไหนเปิดดูเช่นถ้าใช้ mac ก็ใช้ finder เปิดดูข้อมูลได้โดยคลิกขวาที่ไฟล์แล้วเลือก "ขอดูรายละเอียด" ลองดูไฟล์เก่ากับใหม่จะเห็นความแตกต่างแบบนี้



นั่นแสดงว่าแค่เปิดภาพมาแล้วเซฟทันทีแบบนี้ก็จะมีแค่ตัวรูปภาพเท่านั้นที่ถูกเซฟมา

อีกทั้งรูปไหนที่ผ่านการพลิกหมุนหรือกลับด้านโดยใช้โปรแกรมบางอย่าง ข้อมูลที่บอกว่าต้องพลิกตัวยังไงนั้นก็ถูกเก็บไว้ใน exif นี้ด้วย การที่ข้อมูล exif หายไปนั่นหมายความว่าภาพจะโดนพลิกกลับมาเหมือนเดิม



หากต้องการให้ข้อมูลรายละเอียดต่างๆที่เก็บไว้ในตัวรูปถูกเซฟมาลงในไฟล์ใหม่ด้วยจะต้องแก้เพิ่มเข้าไปเป็นแบบนี้
rup.save(chuerupmai,exif=rup.info['exif'])

ข้อมูล exif จะถูกเก็บอยู่ในแอตทริบิวต์ .info['exif'] เราแค่เพิ่มมันลงไปโดยใส่ลงในคีย์เวิร์ด exif ตอนเซฟเท่านั้น

คราวนี้ลองเปิดไฟล์ที่ได้ออกมา จะเห็นว่าข้อมูลตามมาด้วยไม่ได้หายไปไหนแล้ว

เพียงแต่ว่าข้อมูล exif ที่ได้จากตรงนี้เป็นข้อมูลแบบไบนารี หากลองเอามา print ดูก็จะพบว่าเป็นรหัสซึ่งคนไม่สามารถอ่านรู้เรื่องได้

ลักษณะแบบนี้แม้ว่าสามารถจะดึงข้อมูลมาแล้วเซฟได้ทันที แต่ก็ไม่สามารถไปแก้อะไรได้ตามที่ต้องการ ดังนั้นจึงต้องมีตัวช่วยอื่น นั่นก็คือ piexif



เปิดดูข้อมูลด้วย piexif
หากใช้คำสั่ง load ของ piexif ก็จะสามารถอ่านข้อมูลของรูปเข้ามาอยู่ในรูปของดิกได้ทันที
import piexif
exif_dict = piexif.load(chuerup)
print(type(exif_dict)) # ได้ <class 'dict'>
print(exif_dict.keys()) # ได้ dict_keys(['0th', 'Exif', 'GPS', 'Interop', '1st', 'thumbnail'])

จะเห็นว่าได้ข้อมูล exif มาในรูปของดิก ซึ่งมีข้อมูลที่แบ่งออกเป็น ๖ ส่วน ในจำนวนนั้นมี ๕ อันที่เป็นข้อความรายละเอียดต่างๆ ส่วน thumbnail เป็นรูปภาพขนาดย่อ

สามารถลองเอาภาพจาก thumbnail มาเซฟลงไฟล์ได้
with open(chuerup.replace('.jpg','x.jpg'),'wb') as f:
    f.write(exif_dict['thumbnail'])

แบบนี้ก็จะได้ภาพเล็กๆขนาดกว้าง 320 ของภาพนี้ออกมา

ส่วนค่าต่างๆส่วนที่เหลืออาจลองให้ไล่ print ออกมาดูได้โดยทำแบบนี้
for ifd in exif_dict:
    if(ifd=='thumbnail'):
        continue
    print('\n---'+ifd+'---')
    for tag in exif_dict[ifd]:
        print('%s:: %s'%(piexif.TAGS[ifd][tag]['name'],exif_dict[ifd][tag]))

ก็จะได้
---0th---
Make:: b'vivo'
Model:: b'vivo 1718'
XResolution:: (72, 1)
YResolution:: (72, 1)
ResolutionUnit:: 2
Software:: b'msm8953_64-user 7.1.2 N2G47H eng.compil.20180202.222336 release-keys'
DateTime:: b'2018:04:06 19:57:31'
YCbCrPositioning:: 1
ExifTag:: 256
GPSTag:: 740

---Exif---
ExposureTime:: (10, 333)
FNumber:: (200, 100)
ExposureProgram:: 0
ISOSpeedRatings:: 422
ExifVersion:: b'0220'
DateTimeOriginal:: b'2018:04:06 19:57:31'
DateTimeDigitized:: b'2018:04:06 19:57:31'
ComponentsConfiguration:: b'\x01\x02\x03\x00'
ShutterSpeedValue:: (5058, 1000)
ApertureValue:: (200, 100)
BrightnessValue:: (26, 100)
MeteringMode:: 2
Flash:: 0
FocalLength:: (355, 100)
SubSecTime:: b'604322'
SubSecTimeOriginal:: b'604322'
SubSecTimeDigitized:: b'604322'
FlashpixVersion:: b'0100'
ColorSpace:: 1
PixelXDimension:: 4608
PixelYDimension:: 3456
InteroperabilityTag:: 709
SensingMethod:: 2
SceneType:: b'\x01'
ExposureMode:: 0
WhiteBalance:: 0
FocalLengthIn35mmFilm:: 4
SceneCaptureType:: 0

---GPS---
GPSAltitudeRef:: 0
GPSLatitudeRef:: b'N'
GPSLatitude:: ((24, 1), (47, 1), (247869, 10000))
GPSLongitudeRef:: b'E'
GPSLongitude:: ((120, 1), (59, 1), (376872, 10000))
GPSAltitude:: (0, 1000)
GPSTimeStamp:: ((11, 1), (57, 1), (30, 1))
GPSProcessingMethod:: b'ASCII\x00\x00\x00NETWORK'
GPSDateStamp:: b'2018:04:06'

---Interop---
InteroperabilityIndex:: b'R98'

---1st---
Compression:: 6
XResolution:: (72, 1)
YResolution:: (72, 1)
ResolutionUnit:: 2
JPEGInterchangeFormat:: 1076
JPEGInterchangeFormatLength:: 17280

จะเห็นว่าข้อมูลมีรายละเอียดเยอะแยะมากมาย ในที่นี้ขอไม่พูดถึงรายละเอียด



แก้ข้อมูลแล้วบันทึกใหม่
ข้อมูลที่ได้มาอยู่ในรูปดิกนี้สามารถแก้ไขได้ตามที่ต้องการ แต่ว่าพอจะนำมาเซฟจะต้องทำการแปลงเป็นไบนารีก่อน

ตัวอย่าง สมมุติว่าต้องการแก้ข้อมูลในส่วนของยี่ห้อและรุ่นอุปกรณ์ก็ไปดูที่ค่า Make และ Model
print(exif_dict['0th'][piexif.ImageIFD.Make]) # ได้ vivo
print(exif_dict['0th'][piexif.ImageIFD.Model]) # ได้ vivo 1718

ส่วนที่เขียนว่า piexif.ImageIFD.Make นั้นคือค่าตัวเลขที่จะชี้ไปยังแท็ก Make ที่อยู่ใน 0th
print(exif_dict['0th']) # ได้ {271: 'vivo', 272: 'vivo 1718', 282: (72, 1), 283: (72, 1), 296: 2, 305: b'msm8953_64-user 7.1.2 N2G47H eng.compil.20180202.222336 release-keys', 306: b'2018:04:06 19:57:31', 531: 1, 34665: 256, 34853: 740}
print(piexif.ImageIFD.Make) # ได้ 271

สำหรับแท็กที่อยู่ในส่วนของ 0th กับ 1st จะอยู่ใน ImageIFD

ส่วนข้อมูลGPS จะอยู่ใน GPSIFD ส่วน Exif จะอยู่ใน ExifIFD และ Interop จะอยู่ใน InteropIFD

ดูแล้วมีความยุ่งยากเล็กน้อย เขาน่าจะทำให้ง่ายกว่านี้สักหน่อย แต่แค่นี้ก็สะดวกพออยู่ สามารถใช้งานได้ดี

ต่อไปลองทำการแก้แล้วก็บันทึก เช่นถ้าอยากหลอกว่าภาพนี้ไม่ได้ถ่ายด้วยวีโวแต่ถ่ายด้วยเสียวหมี่ก็อาจทำแบบนี้
rup = Image.open(chuerup)
exif_dict = piexif.load(chuerup)
exif_dict['0th'][piexif.ImageIFD.Make] = 'xiaomi'
exif_dict['0th'][piexif.ImageIFD.Model] = 'xiaomi a1'
exif = piexif.dump(exif_dict)
rup.save(chuerupmai,exif=exif)

ในที่นี้คำสั่งที่ใช้เปลี่ยนจากดิกเป็นไบนารีก็คือ dumb พอเปลี่ยนเสร็จแล้วก็เอาไปใช้ตอนเซฟภาพได้เลย

พอมาดูข้อมูลรูปอีกทีก็จะพบว่าถูกเปลี่ยนเป็นไปตามนั้นเรียบร้อย



ข้อมูลลองจิจูดและละติจูดเป็นตัวบอกตำแหน่งทำให้คนรู้ว่าเราถ่ายภาพจากที่ไหน ถ้ากังวลเรื่องความเป็นส่วนตัวไม่อยากให้คนรู้ก็อาจลบหรือแก้ไขได้ด้วยวิธีเดียวกันนี้



การลบข้อมูล exif จากไฟล์รูป
ถ้าแค่ต้องการจะลบข้อมูล exif ทั้งหมดออกไปจากรูปไม่จำเป็นต้องยุ่งยากเปิดไฟล์ขึ้นมา สามารถทำได้ง่ายๆโดยใช้คำสั่ง remove
piexif.remove('miku20180406-.jpg')

เพียงเท่านี้ข้อมูล exif ก็อันตรธานหายไปจากภาพนั้นทันที



การใส่ข้อมูล exif ให้ไฟล์รูป
ถ้าต้องการนำข้อมูล exif ไปใส่ให้อีกไฟล์นึงที่มีอยู่แล้วก็ทำได้โดยคำสั่ง insert เช่น
exif = Image.open('miku20180406.jpg').info['exif']
piexif.insert(exif,'miku20180406-.jpg')

หรือถ้าแค่จะเอาข้อมูล exif จากไฟล์นึงไปไว้อีกไฟล์ก็อาจใช้คำสั่ง transplant
piexif.transplant('miku20180406.jpg','miku20180406-.jpg')

เท่านี้ข้อมูล exif จากไฟล์ทางซ้ายก็ถูกลอกไปใส่ในไฟล์ทางขวา



สรุป
piexif หลักๆมีอยู่แค่ ๕ คำสั่ง ได้แก่
load โหลดข้อมูล exif มาในรูปดิก
dump แปลงข้อมูล exif ในรูปดิกให้เป็นไบนารีเพื่อจะใช้เซฟได้
remove ลบข้อมูล exif ออกจากไฟล์
insert ใส่ข้อมูล exif ให้กับไฟล์
transplant คัดลอกข้อมูล exif จากไฟล์นึงไปอีกไฟล์




อ้างอิง


-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> python

ไม่อนุญาตให้นำเนื้อหาของบทความไปลงที่อื่นโดยไม่ได้ขออนุญาตโดยเด็ดขาด หากต้องการนำบางส่วนไปลงสามารถทำได้โดยต้องไม่ใช่การก๊อปแปะแต่ให้เปลี่ยนคำพูดเป็นของตัวเอง หรือไม่ก็เขียนในลักษณะการยกข้อความอ้างอิง และไม่ว่ากรณีไหนก็ตาม ต้องให้เครดิตพร้อมใส่ลิงก์ของทุกบทความที่มีการใช้เนื้อหาเสมอ

สารบัญ

รวมคำแปลวลีเด็ดจากญี่ปุ่น
python
-- numpy
-- matplotlib

-- pandas
-- pytorch
maya
การเรียนรู้ของเครื่อง
-- โครงข่าย
     ประสาทเทียม
บันทึกในญี่ปุ่น
บันทึกในจีน
-- บันทึกในปักกิ่ง
บันทึกในไต้หวัน
บันทึกในยุโรปเหนือ
บันทึกในประเทศอื่นๆ
เรียนภาษาจีน
qiita
บทความอื่นๆ

บทความแบ่งตามหมวด



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  ค้นหาบทความ

  บทความแนะนำ

หลักการเขียนทับศัพท์ภาษาจีนกวางตุ้ง
การใช้ unix shell เบื้องต้น ใน linux และ mac
หลักการเขียนทับศัพท์ภาษาจีนกลาง
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
บันทึกการเที่ยวสวีเดน 1-12 พ.ค. 2014
แนะนำองค์การวิจัยและพัฒนาการสำรวจอวกาศญี่ปุ่น (JAXA)
เล่าประสบการณ์ค่ายอบรมวิชาการทางดาราศาสตร์โดยโซวเคนได 10 - 16 พ.ย. 2013
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
บันทึกการเที่ยวญี่ปุ่นครั้งแรกในชีวิต - ทุกอย่างเริ่มต้นที่สนามบินนานาชาติคันไซ
หลักการเขียนคำทับศัพท์ภาษาญี่ปุ่น
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ
ทำไมถึงอยากมาเรียนต่อนอก
เหตุผลอะไรที่ต้องใช้ภาษาวิบัติ?

บทความแต่ละเดือน

2019年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2018年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2017年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2016年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2015年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

ค้นบทความเก่ากว่านั้น

ไทย

日本語

中文