การแปลงรูปภาพที่ติดแท็กทางภูมิศาสตร์ให้เป็นการวางซ้อนรูปภาพ KML

Mano Marks จากทีม Google Geo API
มกราคม 2009

วัตถุประสงค์

บทแนะนํานี้จะสอนวิธีใช้รูปภาพที่ติดแท็กทางภูมิศาสตร์เพื่อสร้าง KML PhotoOverlays แม้ว่าโค้ดตัวอย่างจะเขียนลงใน Python แต่ไลบรารีอื่นๆ ที่คล้ายกันก็มีให้บริการในภาษาโปรแกรมอื่นๆ ด้วย ดังนั้นจึงไม่น่าจะจําเป็นต้องแปลรหัสนี้เป็นภาษาอื่น โค้ดในบทความนี้ใช้ไลบรารี Python แบบโอเพนซอร์ส EXIF.py

บทนำ

กล้องดิจิทัลเป็นสิ่งที่ยอดเยี่ยมมาก ผู้ใช้หลายคนไม่รับรู้เรื่องนี้ แต่ทําได้มากกว่าแค่ถ่ายภาพและวิดีโอ พวกเขายังติดแท็กวิดีโอและรูปภาพเหล่านั้นด้วยข้อมูลเมตาเกี่ยวกับกล้องและการตั้งค่า ในช่วง 2 - 3 ปีที่ผ่านมา ผู้คนได้พบวิธีเพิ่มข้อมูลทางภูมิศาสตร์ลงในข้อมูลนั้นโดยฝังโดยผู้ผลิตกล้อง เช่น กล้องเปอร์โตริโกและ Nikon หรือผ่านอุปกรณ์อย่างเช่นตัวบันทึก GPS และ EyeFi Explore โทรศัพท์กล้องถ่ายรูป เช่น iPhone และโทรศัพท์ที่ใช้ระบบปฏิบัติการ Android เช่น G1 ของ T-Mobile จะฝังข้อมูลดังกล่าวโดยอัตโนมัติ เว็บไซต์อัปโหลดรูปภาพบางแห่ง เช่น Panoramio, Picasa Web Albums และ Flickr จะแยกวิเคราะห์ข้อมูล GPS โดยอัตโนมัติและใช้เพื่อติดป้ายสถานที่ให้กับรูปภาพ แล้วคุณจะได้รับข้อมูลนั้นคืนในฟีด แต่จะสนุกตรงไหน บทความนี้จะสํารวจวิธีเข้าถึงข้อมูลดังกล่าวด้วยตัวเอง

ส่วนหัว Exif

วิธีที่ใช้กันโดยทั่วไปเพื่อฝังข้อมูลไว้ในไฟล์ภาพคือการใช้รูปแบบไฟล์ภาพการแลกเปลี่ยนหรือ EXIF ข้อมูลจะจัดเก็บไว้ในรูปแบบไบนารีในส่วนหัว EXIF ด้วยวิธีมาตรฐาน หากทราบข้อมูลจําเพาะของส่วนหัว EXIF คุณก็แยกวิเคราะห์ด้วยตัวเองได้ โชคดีที่มีคนทํางานหนักและเขียนโมดูล Python ให้คุณแล้ว ไลบรารีแบบโอเพนซอร์ส EXIF.py เป็นเครื่องมือที่ยอดเยี่ยมสําหรับอ่านส่วนหัวของไฟล์ JPEG

รหัส

โค้ดตัวอย่างสําหรับบทความนี้อยู่ในไฟล์นี้: exif2 KML.py หากคุณต้องการข้ามไปยังการใช้งานโดยตรง ให้ดาวน์โหลดโมดูลนั้นรวมถึง EXIF.py แล้ววางลงในไดเรกทอรีเดียวกัน เรียกใช้ python exif2kml.py foo.jpg แทนที่ foo.jpg ด้วยเส้นทางไปยังรูปภาพที่ติดแท็กทางภูมิศาสตร์ ระบบจะสร้างไฟล์ชื่อ test.kml

การแยกวิเคราะห์ส่วนหัว Exif

EXIF.py มีอินเทอร์เฟซที่ใช้งานง่ายสําหรับดึงส่วนหัว Exif เพียงเรียกใช้ฟังก์ชัน process_file() และแสดงผลส่วนหัวเป็นออบเจ็กต์ dict

def GetHeaders(the_file):
  """Handles getting the Exif headers and returns them as a dict.

  Args:
    the_file: A file object

  Returns:
    a dict mapping keys corresponding to the Exif headers of a file.
  """

  data = EXIF.process_file(the_file, 'UNDEF', False, False, False)
  return data

เมื่อคุณมีส่วนหัว Exif แล้ว คุณจะต้องดึงพิกัด GPS EXIF.py จะถือว่าออบเจ็กต์เหล่านี้เป็น Ratio และออบเจ็กต์สําหรับจัดเก็บค่าตัวเลขและตัวหารของค่า ซึ่งจะกําหนดอัตราส่วนที่แน่นอนแทนการอาศัยจํานวนจุดลอยตัว แต่ KML ต้องการจํานวนทศนิยม ไม่ใช่อัตราส่วน คุณจึงดึงพิกัดแต่ละค่า และแปลงจํานวนและตัวหารเป็นเลขทศนิยมจุดเดียวสําหรับองศาทศนิยม

def DmsToDecimal(degree_num, degree_den, minute_num, minute_den,
                 second_num, second_den):
  """Converts the Degree/Minute/Second formatted GPS data to decimal degrees.

  Args:
    degree_num: The numerator of the degree object.
    degree_den: The denominator of the degree object.
    minute_num: The numerator of the minute object.
    minute_den: The denominator of the minute object.
    second_num: The numerator of the second object.
    second_den: The denominator of the second object.

  Returns:
    A deciminal degree.
  """

  degree = float(degree_num)/float(degree_den)
  minute = float(minute_num)/float(minute_den)/60
  second = float(second_num)/float(second_den)/3600
  return degree + minute + second


def GetGps(data):
  """Parses out the GPS coordinates from the file.

  Args:
    data: A dict object representing the Exif headers of the photo.

  Returns:
    A tuple representing the latitude, longitude, and altitude of the photo.
  """

  lat_dms = data['GPS GPSLatitude'].values
  long_dms = data['GPS GPSLongitude'].values
  latitude = DmsToDecimal(lat_dms[0].num, lat_dms[0].den,
                          lat_dms[1].num, lat_dms[1].den,
                          lat_dms[2].num, lat_dms[2].den)
  longitude = DmsToDecimal(long_dms[0].num, long_dms[0].den,
                           long_dms[1].num, long_dms[1].den,
                           long_dms[2].num, long_dms[2].den)
  if data['GPS GPSLatitudeRef'].printable == 'S': latitude *= -1
  if data['GPS GPSLongitudeRef'].printable == 'W': longitude *= -1
  altitude = None

  try:
    alt = data['GPS GPSAltitude'].values[0]
    altitude = alt.num/alt.den
    if data['GPS GPSAltitudeRef'] == 1: altitude *= -1

  except KeyError:
    altitude = 0

  return latitude, longitude, altitude

เมื่อคุณมีพิกัดแล้ว คุณจะสามารถสร้าง PhotoOverlay แบบง่ายสําหรับแต่ละรูปภาพได้อย่างง่ายดาย:

def CreatePhotoOverlay(kml_doc, file_name, the_file, file_iterator):
  """Creates a PhotoOverlay element in the kml_doc element.

  Args:
    kml_doc: An XML document object.
    file_name: The name of the file.
    the_file: The file object.
    file_iterator: The file iterator, used to create the id.

  Returns:
    An XML element representing the PhotoOverlay.
  """

  photo_id = 'photo%s' % file_iterator
  data = GetHeaders(the_file)
  coords = GetGps(data)

  po = kml_doc.createElement('PhotoOverlay')
  po.setAttribute('id', photo_id)
  name = kml_doc.createElement('name')
  name.appendChild(kml_doc.createTextNode(file_name))
  description = kml_doc.createElement('description')
  description.appendChild(kml_doc.createCDATASection('<a href="#%s">'
                                                     'Click here to fly into '
                                                     'photo</a>' % photo_id))
  po.appendChild(name)
  po.appendChild(description)

  icon = kml_doc.createElement('icon')
  href = kml_doc.createElement('href')
  href.appendChild(kml_doc.createTextNode(file_name))

  camera = kml_doc.createElement('Camera')
  longitude = kml_doc.createElement('longitude')
  latitude = kml_doc.createElement('latitude')
  altitude = kml_doc.createElement('altitude')
  tilt = kml_doc.createElement('tilt')

  # Determines the proportions of the image and uses them to set FOV.
  width = float(data['EXIF ExifImageWidth'].printable)
  length = float(data['EXIF ExifImageLength'].printable)
  lf = str(width/length * -20.0)
  rf = str(width/length * 20.0)

  longitude.appendChild(kml_doc.createTextNode(str(coords[1])))
  latitude.appendChild(kml_doc.createTextNode(str(coords[0])))
  altitude.appendChild(kml_doc.createTextNode('10'))
  tilt.appendChild(kml_doc.createTextNode('90'))
  camera.appendChild(longitude)
  camera.appendChild(latitude)
  camera.appendChild(altitude)
  camera.appendChild(tilt)

  icon.appendChild(href)

  viewvolume = kml_doc.createElement('ViewVolume')
  leftfov = kml_doc.createElement('leftFov')
  rightfov = kml_doc.createElement('rightFov')
  bottomfov = kml_doc.createElement('bottomFov')
  topfov = kml_doc.createElement('topFov')
  near = kml_doc.createElement('near')
  leftfov.appendChild(kml_doc.createTextNode(lf))
  rightfov.appendChild(kml_doc.createTextNode(rf))
  bottomfov.appendChild(kml_doc.createTextNode('-20'))
  topfov.appendChild(kml_doc.createTextNode('20'))
  near.appendChild(kml_doc.createTextNode('10'))
  viewvolume.appendChild(leftfov)
  viewvolume.appendChild(rightfov)
  viewvolume.appendChild(bottomfov)
  viewvolume.appendChild(topfov)
  viewvolume.appendChild(near)

  po.appendChild(camera)
  po.appendChild(icon)
  po.appendChild(viewvolume)
  point = kml_doc.createElement('point')
  coordinates = kml_doc.createElement('coordinates')
  coordinates.appendChild(kml_doc.createTextNode('%s,%s,%s' %(coords[1],
                                                              coords[0],
                                                              coords[2])))
  point.appendChild(coordinates)

  po.appendChild(point)

  document = kml_doc.getElementsByTagName('Document')[0]
  document.appendChild(po)

คุณจะเห็นว่าเราใช้เมธอด W3C DOM แบบมาตรฐานเท่านั้น เพราะเป็นวิธีที่ใช้ได้ในภาษาโปรแกรมส่วนใหญ่ หากต้องการดูว่าโค้ดทั้งหมดทํางานร่วมกันอย่างไร ให้ดาวน์โหลดโค้ดจากที่นี่

ตัวอย่างนี้ไม่ได้ใช้ประโยชน์จากศักยภาพสูงสุดของ PhotoOverlays ซึ่งจะช่วยให้คุณสร้างการสํารวจรูปภาพความละเอียดสูงได้ แต่จะสาธิตวิธีแขวนรูปภาพในรูปแบบบิลบอร์ดผ่าน Google Earth ต่อไปนี้คือตัวอย่างของไฟล์ KML ที่สร้างขึ้นโดยใช้โค้ดนี้

<?xml version="1.0" encoding="utf-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
  <Document>
    <PhotoOverlay id="photo0">
      <name>
        1228258523134.jpg
      </name>
      <description>
<![CDATA[<a href="#photo0">Click here to fly into photo</a>]]>      </description>
      <Camera>
      	<longitude>
          -122.3902159196034
        </longitude>
        <latitude>
           37.78961266330473
        </latitude>
        <altitude>
          10
        </altitude>
        <tilt>
          90
        </tilt>
      </Camera>
      <Icon>
        <href>
          1228258523134.jpg
        </href>
      </Icon>
      <ViewVolume>
        <leftFov>
          -26.6666666667
        </leftFov>
        <rightFov>
          26.6666666667
        </rightFov>
        <bottomFov>
          -20
        </bottomFov>
        <topFov>
          20
        </topFov>
        <near>
          10
        </near>
      </ViewVolume>
      <Point>
        <coordinates>
          -122.3902159196034,37.78961266330473,0
        </coordinates>
      </Point>
    </PhotoOverlay>
  </Document>
</kml>

และมีลักษณะดังนี้ใน Google Earth


ข้อควรระวัง

การติดป้ายสถานที่ให้กับรูปภาพยังอยู่ระหว่างการสร้าง

ข้อควรทราบมีดังนี้

  • อุปกรณ์ GPS ไม่ได้ทํางานถูกต้อง 100% เสมอไป โดยเฉพาะอุปกรณ์ที่มาพร้อมกับกล้อง ดังนั้น คุณควรตรวจสอบตําแหน่งของรูปภาพ
  • อุปกรณ์หลายเครื่องไม่ได้ติดตามระดับความสูง จึงตั้งค่าให้เป็น 0 แทน หากระดับความสูงเป็นสิ่งสําคัญสําหรับคุณ คุณควรหาวิธีอื่นในการเก็บข้อมูลนั้น
  • ตําแหน่ง GPS คือตําแหน่งของกล้อง ไม่ใช่วัตถุในรูปภาพ ด้วยเหตุนี้ ตัวอย่างนี้จึงวางตําแหน่งองค์ประกอบกล้องไว้ในตําแหน่ง GPS และรูปภาพจริงอยู่ห่างจากตําแหน่งนั้น
  • Exif ไม่ได้บันทึกเกี่ยวกับทิศทางที่กล้องชี้ไป คุณจึงต้องปรับ PhotoOverlays ด้วยเหตุผลดังกล่าว ข่าวดีก็คืออุปกรณ์บางอย่าง เช่น โทรศัพท์ที่สร้างขึ้นในระบบปฏิบัติการ Android ช่วยให้คุณบันทึกข้อมูล เช่น ทิศทางของเข็มทิศและเอียงได้โดยตรง ไม่ใช่แค่ในส่วนหัว Exif

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

ขั้นตอนถัดไป

ตอนนี้คุณได้เริ่มใช้ส่วนหัว EXIF แล้ว คุณอาจสํารวจข้อกําหนด EXIF มีข้อมูลอื่นมากมายที่จัดเก็บไว้ในนั้น และคุณอาจสนใจบันทึกส่วนหัวเพื่อนําไปใส่ไว้ในบอลลูนคําอธิบาย คุณอาจลองสร้าง PhotoOverlays ที่สมบูรณ์ยิ่งขึ้นโดยใช้ ImagePyramids บทความคู่มือสําหรับนักพัฒนาซอฟต์แวร์เกี่ยวกับ PhotoOverlays มีภาพรวมที่ดีในการใช้งาน