我从事摄影师工作已有数十年,因此,我拥有超过 100,000 个各种格式的图像文件,其中许多都包含位置信息。我认为用这些位置数据做实验会很有趣。这个任务比我预想的要困难一些,但这是一个很好的机会来了解Anthropic Claude 的调试能力。随着我修复错误并扩大范围,我最终得到了 11 个版本的代码。为了保持这篇文章的简洁性,我将只分享最终代码,而不分享中间版本。但是,我还将包括我与 Claude 的所有交流,以便您可以看到这个过程在现实场景中的工作方式。我从未接触过 Claude 生成的代码,否则,我会在具有与 Claude 适当集成的 IDE 中运行它(请参阅本文中的一些示例)。
我从简单开始,以确保我可以从测试图像中提取位置数据,并要求 Claude(3.5 Sonnet)为我编写一些代码:
我的提示:
编写一个 Python 脚本,以图像文件列表作为参数。对于每个图像文件,列出文件名、创建日期和位置信息。图像格式将包括 jpg、nef 和 heic(Apple iphone 图像)。
根据要求,Claude 为我创建了一些 Python 代码,并要求我安装以下 Pillow 和 piexif 库:
pip install Pillow piexif
我尝试了生成的代码,出现了错误,并告诉了 Claude:
使用 jpg 进行测试时出现以下错误:
回溯(最近一次调用最后一次):文件“/Users/gregwilson/Downloads/imageplay/ image-metadata-script.py ”,第 87 行,在 <module> main(sys.argv[1:]) 中文件“/Users/gregwilson/Downloads/imageplay/ image-metadata-script.py ”,第 74 行,在 main 中 info = process_image(image_file) ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 文件“/Users/gregwilson/Downloads/imageplay/ image-metadata-script.py ”,第 63 行,在 process_image 中 location = get_gps_info(exif_data) ^^^^^^^^^^^^^^^^^^^^^^^^^ 文件“/Users/gregwilson/Downloads/imageplay/ image-metadata-script.py ",第 44 行,在 get_gps_info 中返回 f"{ lat:.6f }, { lon:.6f }" ^^^^^^^^^^^^^^^^^^^^^^^^^^^ TypeError: 传递给 Fraction.format 的格式字符串不受支持
克劳德回答道:
I apologize for the oversight. The error you're encountering is due to the GPS coordinates being stored as Fraction objects in some EXIF data, which can't be formatted directly with the f-string. Let's modify the get_gps_info function to handle this case.
These changes should resolve the error you were seeing. The script now handles GPS coordinates stored as tuples (degrees, minutes, seconds) or as Fraction objects, which are common formats for GPS data in EXIF.
This updated version should work with JPG files that store GPS coordinates in various formats. It still maintains compatibility with NEF a
nd HEIC 文件也是如此。
一切都适用于 jpg 文件,但我的 heic 文件不适用,因此我告诉 Claude:
所有 heic 文件的创建日期和位置均未知
克劳德回答道:
I apologize for the oversight with HEIC files. You're right that the current implementation isn't correctly extracting the date and location information from HEIC files. Let's modify the script to properly handle HEIC metadata. We'll use the pyheif library in addition to piexif to better process HEIC files.
Claude 要求我安装 pyheif - pip install pyheif - 但是我在尝试安装时遇到错误,因此我告诉了 Claude:
pyheif 无法安装
克劳德回答道:
I apologize for the difficulty you're experiencing with installing pyheif. You're right that it can sometimes be challenging to install due to its dependencies. Let's try an alternative approach using pillow-heif, which is generally easier to install and use. Here's an updated version of the script that uses pillow-heif instead of pyheif:
我按照说明安装了 pillow-heif,并尝试了新生成的代码,但出现了另一个错误,因此我告诉了 Claude:
我收到以下错误:
回溯(最近一次调用最后一次):文件“/Users/gregwilson/Downloads/imageplay/ image-metadata-script.py ”,第 82 行,在 <module> main(sys.argv[1:]) 中文件“/Users/gregwilson/Downloads/imageplay/ image-metadata-script.py ”,第 69 行,在 main 中 info = process_image(image_file) ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 文件“/Users/gregwilson/Downloads/imageplay/ image-metadata-script.py ”,第 58 行,在 process_image 中 location = get_gps_info(exif_data) ^^^^^^^^^^^^^^^^^^^^^^^^^ 文件“/Users/gregwilson/Downloads/imageplay/ image-metadata-script.py ”,第 38 行,在 get_gps_info lat = gps_info.get(2) ^^^^^^^^^^^^^ AttributeError: 'int' 对象没有属性 'get'
克劳德回答道:
I apologize for the oversight. The error you're encountering suggests that the GPS information is not being stored as expected in the EXIF data. Let's modify the get_gps_info function to handle different possible structures of GPS data.
这对 jpg 文件有效,所以现在我们取得了一些进展!但是,对于 heic 文件,它不起作用,所以我告诉 Claude:
所有 heic 文件中的位置均未知
克劳德回答道:
I apologize for the continued difficulty with HEIC files. It seems that the GPS data in HEIC files might be stored differently or not easily accessible through the current method. Let's try a different approach using the exif library, which is specifically designed to handle EXIF data and should work better with HEIC files.
我尝试了新的代码,出现了新的错误,并告诉了Claude:
在 heic 文件上出现以下错误:
读取 IMG_1837.heic 的 EXIF 数据时出错:+--------+------------+-------+----------+------------------------+ | 偏移量 | 访问 | 值 | 字节 | 格式 | +--------+------------+-------+------------------------+ | | | | | TiffHeader (结构) | | 0 | byte_order | 11565 | 2d 2d | tiff_byte_order | +--------+------------+-------+----------+------------------------+
克劳德回答道:
I apologize for the continued issues. It seems that the exif library is having trouble reading the HEIC files directly. Let's try a different approach using pillow_heif to open the HEIC files and then extract the EXIF data.
我下载了新代码,它成功了!下面是输出:
python3 image-metadata-script.py *heic *jpg
Filename: IMG_1837.heic
Date Created: 2023-12-26 21:27:59
Location: 37.659542, -122.294072
----------------------------------------
Filename: IMG_3724.heic
Date Created: 2023-08-08 19:59:54
Location: 37.659483, -122.294006
----------------------------------------
Filename: IMG_3725.heic
Date Created: 2023-08-08 20:01:46
Location: 37.659525, -122.293936
----------------------------------------
Filename: DSC_7148.jpg
Date Created: 2015-08-27 09:36:46
Location: 48.862175, 2.323205
----------------------------------------
Filename: PANO0001 Panorama-106.jpg
Date Created: 2023-09-07 18:17:21
Location: 37.659570, -122.293875
----------------------------------------
Filename: PANO0001 Panorama.jpg
Date Created: 2023-09-07 18:15:53
Location: 37.6759570, -122.293872
----------------------------------------
显然,我不得不和克劳德搏斗一番才能让这个作品成功,但无论如何,这仍然令人印象深刻。
我决定更进一步,让 Claude 添加一些代码,将纬度/经度转换为城市、州和国家。我问 Claude:
成功了。现在添加每个图片位置的城市、州、国家
克劳德回答道:
Great to hear that the script is now working for your HEIC files! Let's enhance the script to add city, state, and country information for each image location. We'll use the geopy library to perform reverse geocoding based on the GPS coordinates.
我安装了所需的库,下载了新的代码,尝试了一下,出现了错误,然后告诉了 Claude:
获取错误:
获取位置详细信息时出错:HTTPSConnectionPool(host=' nominatim.openstreetmap.org ',port=443):url 超出最大重试次数:/reverse?lat=37.759541666666664&lon=-122.39407222222223&format=json&addressdetails=1(由 SSLError(SSLCertVerificationError(1,'[SSL:CERTIFICATE_VERIFY_FAILED] 证书验证失败:无法获取本地颁发者证书(_ssl.c:1006)')))
克劳德回答道:
I apologize for the SSL certificate verification error you're encountering. This issue is likely related to your Python installation not having the proper SSL certificates. Let's modify our script to work around this issue while still maintaining security. We'll add an option to disable SSL verification, but we'll make sure to warn the user about the potential security implications.
Claude 还提供了如何安装和运行 SSL 证书的说明,但我不想在这个小练习中这样做,所以我下载了新代码,尝试了一下,它成功了!以下是新的输出:
----------------------------------------
Filename: DSC_7148.jpg
Date Created: 2015-08-27 09:36:46
Location: 48.862175, 2.323205
Location Details: Paris, Île-de-France, France
----------------------------------------
我决定再尝试一件事...我问克劳德:
效果很好。现在确定所有图像之间的距离,并确定距离最大的两幅图像。
克劳德回答道:
Great to hear it's working perfectly! Let's enhance the script to calculate the distances between all images and find the two images that are farthest apart. We'll use the geopy.distance module to calculate the distances between coordinates.
我下载了新代码,它运行得很好。我给它输入了一些图像,它输出:
The two images farthest apart are:
1. DSC_7148.jpg (Paris, Île-de-France, France)
2. IMG_3724.heic (San Francisco, California, United States)
Distance between them: 8974.51 km
最终代码:www.cqzlsb.com
import os
import sys
from datetime import datetime
from PIL import Image
from pillow_heif import register_heif_opener, HeifImagePlugin
from exif import Image as ExifImage
import io
from geopy.geocoders import Nominatim
from geopy.distance import geodesic
from geopy.exc import GeocoderTimedOut, GeocoderUnavailable
import time
import ssl
import certifi
import argparse
from itertools import combinations
# Register HEIF opener with Pillow
register_heif_opener()
def create_ssl_context(verify_ssl):
if verify_ssl:
return ssl.create_default_context(cafile=certifi.where())
else:
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
return ctx
def initialize_geolocator(verify_ssl):
ssl_context = create_ssl_context(verify_ssl)
return Nominatim(user_agent="image_metadata_script", scheme='https', ssl_context=ssl_context)
def get_exif_data(image_path):
try:
if image_path.lower().endswith('.heic'):
with Image.open(image_path) as img:
exif_data = img.getexif()
exif_bytes = io.BytesIO()
img.save(exif_bytes, format='JPEG', exif=exif_data)
exif_bytes.seek(0)
return ExifImage(exif_bytes)
else:
with open(image_path, 'rb') as image_file:
return ExifImage(image_file)
except Exception as e:
print(f"Error reading EXIF data for {image_path}: {str(e)}")
return None
def get_creation_date(exif_data):
try:
if hasattr(exif_data, 'datetime_original'):
return datetime.strptime(str(exif_data.datetime_original), '%Y:%m:%d %H:%M:%S')
elif hasattr(exif_data, 'datetime'):
return datetime.strptime(str(exif_data.datetime), '%Y:%m:%d %H:%M:%S')
except Exception as e:
print(f"Error parsing creation date: {str(e)}")
return None
def convert_to_degrees(dms, ref):
degrees = dms[0]
minutes = dms[1] / 60.0
seconds = dms[2] / 3600.0
if ref in ['S', 'W']:
return -(degrees + minutes + seconds)
else:
return degrees + minutes + seconds
def get_gps_info(exif_data):
try:
if hasattr(exif_data, 'gps_latitude') and hasattr(exif_data, 'gps_longitude'):
lat = convert_to_degrees(exif_data.gps_latitude, exif_data.gps_latitude_ref)
lon = convert_to_degrees(exif_data.gps_longitude, exif_data.gps_longitude_ref)
return lat, lon
except Exception as e:
print(f"Error processing GPS info: {str(e)}")
return None
def get_location_details(geolocator, lat, lon):
max_retries = 3
for attempt in range(max_retries):
try:
location = geolocator.reverse(f"{lat}, {lon}")
if location:
address = location.raw['address']
city = address.get('city') or address.get('town') or address.get('village') or 'Unknown'
state = address.get('state') or 'Unknown'
country = address.get('country') or 'Unknown'
return f"{city}, {state}, {country}"
return "Unknown location"
except (GeocoderTimedOut, GeocoderUnavailable) as e:
if attempt < max_retries - 1:
time.sleep(1)
else:
print(f"Error getting location details: {str(e)}")
return "Unknown location"
def process_image(image_path, geolocator):
filename = os.path.basename(image_path)
exif_data = get_exif_data(image_path)
creation_date = get_creation_date(exif_data) if exif_data else None
gps_info = get_gps_info(exif_data) if exif_data else None
location = "Unknown"
location_details = "Unknown location"
if gps_info:
lat, lon = gps_info
location = f"{lat:.6f}, {lon:.6f}"
location_details = get_location_details(geolocator, lat, lon)
return {
"filename": filename,
"creation_date": creation_date.strftime('%Y-%m-%d %H:%M:%S') if creation_date else "Unknown",
"location": location,
"location_details": location_details,
"gps_coords": gps_info
}
def calculate_distances(image_infos):
distances = []
for (img1, info1), (img2, info2) in combinations(image_infos.items(), 2):
if info1['gps_coords'] and info2['gps_coords']:
distance = geodesic(info1['gps_coords'], info2['gps_coords']).miles
distances.append((img1, img2, distance))
return distances
def find_farthest_images(distances):
if not distances:
return None, None, 0
return max(distances, key=lambda x: x[2])
def main(image_files, verify_ssl):
geolocator = initialize_geolocator(verify_ssl)
image_infos = {}
for image_file in image_files:
if os.path.exists(image_file):
info = process_image(image_file, geolocator)
image_infos[image_file] = info
print(f"Filename: {info['filename']}")
print(f"Date Created: {info['creation_date']}")
print(f"Location: {info['location']}")
print(f"Location Details: {info['location_details']}")
print("-" * 40)
else:
print(f"File not found: {image_file}")
print("-" * 40)
distances = calculate_distances(image_infos)
farthest_img1, farthest_img2, max_distance = find_farthest_images(distances)
if farthest_img1 and farthest_img2:
print(f"\nThe two images farthest apart are:")
print(f"1. {image_infos[farthest_img1]['filename']} ({image_infos[farthest_img1]['location_details']})")
print(f"2. {image_infos[farthest_img2]['filename']} ({image_infos[farthest_img2]['location_details']})")
print(f"Distance between them: {max_distance:.2f} miles")
else:
print("\nUnable to calculate distances. Ensure at least two images have valid GPS coordinates.")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Process image metadata, location information, and calculate distances in miles.")
parser.add_argument("image_files", nargs="+", help="Image files to process")
parser.add_argument("--no-verify-ssl", action="store_true", help="Disable SSL certificate verification (use with caution)")
args = parser.parse_args()
if args.no_verify_ssl:
print("WARNING: SSL certificate verification is disabled. This is not recommended for production use.")
print("To fix SSL issues properly, try updating your SSL certificates:")
print("pip install --upgrade certifi")
print("If issues persist, consult your system administrator or Python installation documentation.")
print()
main(args.image_files, not args.no_verify_ssl)
通过这个练习,我更好地了解了 Claude 和其他 LLM 能够进行的编码类型以及调试如何进行。
对于我的下一个项目,我可能会选取某一年的所有图像,提取位置,然后创建一个 KML 文件,这样我就可以在 Google Earth 中可视化所有图像。我坚信我能快速完成这项工作。