728x90
반응형

 

PyQt5 의 기본 사용방법은 앞 포스팅에서 살펴보았습니다. 이번에는 응용하여 프로그램을 하나 만들어 보겠습니다. 

만들 프로그램은 사진의 GPS 정보를 이용하여 사진과 사진 사이의 거리를 측정하는 프로그램 입니다.


필요한 라이브러리

  1. PyQt5: GUI 애플리케이션 개발.
  2. Pillow (PIL): 사진 파일에서 EXIF 데이터 추출.
  3. geopy: GPS 좌표를 사용한 거리 계산.

설치 명령어

아래 명령어를 사용해 필요한 라이브러리를 설치하세요:

pip install pyqt5 pillow geopy

실행코드

import sys
import os
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QFileDialog, QListWidget, QVBoxLayout, QPushButton, QLabel, QWidget
)
from PIL import Image
from PIL.ExifTags import TAGS, GPSTAGS
from geopy.distance import geodesic


class GPSDistanceApp(QMainWindow):

    def __init__(self):
        super().__init__()
        self.initUI()
        self.photos = []  # 선택한 사진 경로 리스트
        self.gps_data = []  # 사진의 GPS 정보 리스트

    def initUI(self):
        # 위젯 설정
        self.setWindowTitle("GPS Distance Calculator")
        self.setGeometry(300, 300, 600, 400)

        self.centralWidget = QWidget()
        self.setCentralWidget(self.centralWidget)

        layout = QVBoxLayout()

        self.photoList = QListWidget()
        self.resultLabel = QLabel("Distances will appear here.")

        self.selectButton = QPushButton("Select Photos")
        self.selectButton.clicked.connect(self.openFileDialog)

        self.calculateButton = QPushButton("Calculate Distances")
        self.calculateButton.clicked.connect(self.calculateDistances)

        layout.addWidget(self.photoList)
        layout.addWidget(self.selectButton)
        layout.addWidget(self.calculateButton)
        layout.addWidget(self.resultLabel)

        self.centralWidget.setLayout(layout)

    def openFileDialog(self):
        # 여러 사진 선택
        filePaths, _ = QFileDialog.getOpenFileNames(self, "Select Photos", "", "Images (*.jpg *.jpeg *.png)")
        if filePaths:
            self.photos = filePaths
            self.photoList.clear()
            self.photoList.addItems(filePaths)

    def extractGPS(self, photoPath):
        try:
            img = Image.open(photoPath)
            exif_data = img._getexif()
            if not exif_data:
                return None
            gps_info = {}
            for tag, value in exif_data.items():
                tag_name = TAGS.get(tag, tag)
                if tag_name == "GPSInfo":
                    for gps_tag in value.keys():
                        gps_name = GPSTAGS.get(gps_tag, gps_tag)
                        gps_info[gps_name] = value[gps_tag]
            if gps_info:
                return self.convertGPS(gps_info)
            return None
        except Exception as e:
            print(f"Error reading GPS data: {e}")
            return None

    def convertGPS(self, gps_info):
        """Convert GPS EXIF data to latitude and longitude in decimal format."""
        def to_decimal(coord, ref):
            degrees, minutes, seconds = coord
            decimal = degrees + (minutes / 60.0) + (seconds / 3600.0)
            if ref in ['S', 'W']:
                return -decimal
            return decimal

        try:
            lat = to_decimal(gps_info["GPSLatitude"], gps_info["GPSLatitudeRef"])
            lon = to_decimal(gps_info["GPSLongitude"], gps_info["GPSLongitudeRef"])
            return lat, lon
        except KeyError:
            return None

    def calculateDistances(self):
        # GPS 정보 추출
        self.gps_data = [self.extractGPS(photo) for photo in self.photos]

        # 유효하지 않은 GPS 정보 확인
        if None in self.gps_data:
            self.resultLabel.setText("Some photos do not have valid GPS data.")
            return

        # 거리 계산
        distances = []
        for i in range(len(self.gps_data) - 1):
            dist = geodesic(self.gps_data[i], self.gps_data[i + 1]).meters
            distances.append(dist)

        # 결과 출력
        result_text = "Distances between photos:\n"
        for i, dist in enumerate(distances):
            result_text += f"{os.path.basename(self.photos[i])} -> {os.path.basename(self.photos[i + 1])}: {dist:.2f} m\n"
        self.resultLabel.setText(result_text)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = GPSDistanceApp()
    ex.show()
    sys.exit(app.exec_())

 


코드 설명

  1. GUI 구성
    • QListWidget: 선택한 사진 경로를 리스트 형식으로 표시.
    • QPushButton: 파일 선택 및 거리 계산 기능을 트리거.
    • QLabel: 거리 계산 결과를 표시.
  2. 사진 선택
    • QFileDialog.getOpenFileNames: 여러 장의 사진 선택.
    • 선택된 파일 경로를 self.photos 리스트에 저장하고 화면에 표시.
  3. GPS 정보 추출
    • PIL.Image의 _getexif 메서드를 사용해 EXIF 데이터 추출.
    • GPSInfo를 읽어 GPS 좌표를 추출하고, 이를 소수점 형식의 경위도(latitude, longitude)로 변환.
  4. 거리 계산
    • geopy.distance.geodesic: 두 GPS 좌표 간의 직선 거리를 계산.
    • 결과는 미터 단위로 표시.
  5. 결과 출력
    • 사진 간의 거리와 파일 이름을 화면에 표시.

728x90

실행결과

 

 


참고 포스팅

https://tylee82.tistory.com/418

 

PyQt5: QGridLayout으로 깔끔한 레이아웃 만들기

QGridLayout은 PyQt5에서 제공하는 레이아웃 관리 도구 중 하나로, 위젯들을 행(row)과 열(column)로 구성된 그리드 형태로 배치할 수 있습니다.이번 포스팅에서는 QGridLayout의 사용법과 예제 코드를 자세

tylee82.tistory.com

https://tylee82.tistory.com/420

 

QFileDialog를 활용한 파일 열기와 저장하기

QFileDialog는 PyQt5에서 파일 또는 디렉터리를 선택하는 대화 상자를 제공하는 클래스입니다. 파일 열기와 저장을 간단히 구현할 수 있어 응용 프로그램 개발에 자주 사용됩니다. 이번 포스팅에서

tylee82.tistory.com

https://tylee82.tistory.com/413

 

파이썬으로 이미지의 GPS 정보 읽기

사진 속에는 우리가 눈으로 볼 수 없는 다양한 메타데이터가 포함되어 있습니다. 특히 스마트폰이나 GPS 기능이 있는 카메라로 촬영한 사진에는 GPS 정보가 포함될 수 있습니다. 이번 포스팅에서

tylee82.tistory.com

 

728x90
반응형
728x90
반응형

사진 속에는 우리가 눈으로 볼 수 없는 다양한 메타데이터가 포함되어 있습니다. 특히 스마트폰이나 GPS 기능이 있는 카메라로 촬영한 사진에는 GPS 정보가 포함될 수 있습니다. 이번 포스팅에서는 파이썬을 사용해 이미지에서 GPS 정보를 추출하는 방법을 소개합니다.


준비물

이 작업을 위해 두 가지 라이브러리가 필요합니다: PillowExifRead. 아래 명령어를 사용하여 설치할 수 있습니다.


Pillow

Pillow는 Python의 강력한 이미지 처리 라이브러리로, 이미지 파일을 열고, 읽고, 저장하며 다양한 변환 작업(예: 크기 조정, 포맷 변환 등)을 수행할 수 있습니다.

이 포스팅에서는 이미지를 열고 EXIF 메타데이터를 추출하기 위해 사용됩니다. 직관적이고 간단한 API 덕분에 이미지 처리에 널리 활용됩니다.


ExifRead

ExifRead는 이미지의 EXIF 메타데이터를 분석하는 데 특화된 라이브러리입니다. GPS 정보와 같은 세부 데이터를 효율적으로 읽고, 해석하는 기능을 제공합니다.

Pillow만으로도 EXIF 데이터를 읽을 수 있지만, ExifRead는 특히 GPS 데이터를 더 세밀하고 직관적으로 다룰 수 있도록 설계되어 있습니다.

 

pip install Pillow ExifRead

코드 구현

다음은 파이썬으로 이미지의 GPS 정보를 추출하는 코드입니다.

1. 이미지의 EXIF 데이터 읽기

from PIL import Image
from PIL.ExifTags import TAGS, GPSTAGS

def get_exif_data(image_path):
    """이미지에서 Exif 데이터를 추출합니다."""
    try:
        image = Image.open(image_path)
        exif_data = image._getexif()
        
        if not exif_data:
            return None
        
        exif = {}
        for tag, value in exif_data.items():
            tag_name = TAGS.get(tag, tag)  # 태그를 사람이 읽을 수 있는 이름으로 변환
            exif[tag_name] = value
        
        return exif
    except Exception as e:
        print(f"Error reading EXIF data: {e}")
        return None
  • Image.open(image_path): Pillow를 사용해 이미지를 엽니다.
  • image._getexif(): 이미지의 EXIF 데이터를 추출합니다.
  • TAGS.get(tag, tag): EXIF 태그 번호를 사람이 읽을 수 있는 이름으로 변환합니다.
728x90

2. GPS 정보 추출

def get_geotagging(exif_data):
    """Exif 데이터에서 GPS 정보를 추출합니다."""
    if not exif_data or "GPSInfo" not in exif_data:
        return None
    
    gps_info = exif_data["GPSInfo"]
    geotags = {}
    
    for key, val in gps_info.items():
        tag_name = GPSTAGS.get(key, key)  # GPS 태그 이름 변환
        geotags[tag_name] = val
    
    return geotags
  • exif_data["GPSInfo"]: EXIF 데이터에서 GPS 정보를 포함하는 부분만 추출합니다.
  • GPSTAGS.get(key, key): GPS 태그 번호를 사람이 읽을 수 있는 이름으로 변환합니다.

3. GPS 데이터를 도(degree)로 변환

def convert_to_degrees(value):
    """GPS 좌표 값을 도(degree)로 변환합니다."""
    d, m, s = value  # degree, minute, second 형식
    return d + (m / 60.0) + (s / 3600.0)

EXIF 데이터에서 GPS 좌표는 (degree, minute, second) 형식으로 저장됩니다. 이를 십진수 형식으로 변환하는 함수입니다.


4. 위도와 경도 계산

def get_coordinates(geotags):
    """GPS 정보에서 위도와 경도를 추출합니다."""
    if not geotags:
        return None
    
    lat = geotags.get("GPSLatitude")
    lat_ref = geotags.get("GPSLatitudeRef")
    lon = geotags.get("GPSLongitude")
    lon_ref = geotags.get("GPSLongitudeRef")
    
    if not lat or not lon or not lat_ref or not lon_ref:
        return None
    
    lat = convert_to_degrees(lat)
    if lat_ref != "N":
        lat = -lat
    
    lon = convert_to_degrees(lon)
    if lon_ref != "E":
        lon = -lon
    
    return lat, lon

 

  • GPSLatitude****, ********GPSLongitude: 위도와 경도 정보를 가져옵니다.
  • GPSLatitudeRef****, ********GPSLongitudeRef: 위도/경도의 북(N)/남(S), 동(E)/서(W) 방향을 확인합니다. 방향이 남(S) 또는 서(W)일 경우 음수로 변환합니다.

전체 코드

위의 함수들을 조합하면, 이미지에서 GPS 정보를 추출하는 전체 코드는 다음과 같습니다:

from PIL import Image
from PIL.ExifTags import TAGS, GPSTAGS

def get_exif_data(image_path):
    try:
        image = Image.open(image_path)
        exif_data = image._getexif()
        if not exif_data:
            return None
        exif = {TAGS.get(tag, tag): value for tag, value in exif_data.items()}
        return exif
    except Exception as e:
        print(f"Error reading EXIF data: {e}")
        return None

def get_geotagging(exif_data):
    if not exif_data or "GPSInfo" not in exif_data:
        return None
    gps_info = exif_data["GPSInfo"]
    geotags = {GPSTAGS.get(key, key): val for key, val in gps_info.items()}
    return geotags

def convert_to_degrees(value):
    d, m, s = value
    return d + (m / 60.0) + (s / 3600.0)

def get_coordinates(geotags):
    if not geotags:
        return None
    lat = geotags.get("GPSLatitude")
    lat_ref = geotags.get("GPSLatitudeRef")
    lon = geotags.get("GPSLongitude")
    lon_ref = geotags.get("GPSLongitudeRef")
    if not lat or not lon or not lat_ref or not lon_ref:
        return None
    lat = convert_to_degrees(lat)
    if lat_ref != "N":
        lat = -lat
    lon = convert_to_degrees(lon)
    if lon_ref != "E":
        lon = -lon
    return lat, lon

# 사용 예제
image_path = "example.jpg"  # 이미지 경로
exif_data = get_exif_data(image_path)
geotags = get_geotagging(exif_data)

if geotags:
    coordinates = get_coordinates(geotags)
    if coordinates:
        print(f"GPS Coordinates: {coordinates}")
    else:
        print("GPS 정보가 없습니다.")
else:
    print("Exif에 GPS 정보가 없습니다.")

결과

사진에 GPS 정보가 포함되어 있다면, 위 코드를 실행하면 다음과 같은 출력 결과를 얻을 수 있습니다:

GPS Coordinates: (37.7749, -122.4194)

 

이 결과는 위도(latitude)와 경도(longitude)를 나타냅니다. 예를 들어, 37.7749, -122.4194는 샌프란시스코의 좌표입니다.


주의사항

  1. GPS 정보 유무: 모든 이미지에 GPS 정보가 포함되어 있지는 않습니다. 스마트폰이나 GPS 기능이 있는 카메라로 촬영된 사진만 GPS 정보를 포함할 가능성이 높습니다.
  2. EXIF 제거 여부: 일부 소셜 미디어나 편집 프로그램은 이미지를 저장할 때 EXIF 데이터를 제거할 수 있습니다.
  3. 라이브러리 버전: 최신 버전의 Pillow를 사용하는 것을 권장합니다.
728x90
반응형

+ Recent posts