| 步驟 | 目標 | 工具/接口 |
|---|---|---|
| ① 預處理 | 提取 EXIF、調尺寸、轉 JPG | Pillow、piexif |
| ② 地理增強 | 反向地理編碼 + 附近地標 | Google Maps API |
| ③ 視覺描述 | 圖像→標題 | Vertex AI ImageText 模型 |
| ④ 故事成文 | 標題+地理+時間→博客 | Vertex AI PaLM + 提示工程 |
懶人包:直接跑 Colab,改 3 行配置即可生成你的旅行故事。
from PIL import Image
import piexif, os, pathlib
SRC_DIR = "photos"
DST_DIR = "photos_converted"
MAX_EDGE = 800 # 最長邊≤800px,減少流量
JPG_QUAL = 85
pathlib.Path(DST_DIR).mkdir(exist_ok=True)
for img_path in pathlib.Path(SRC_DIR).iterdir():
img = Image.open(img_path)
img.thumbnail((MAX_EDGE, MAX_EDGE), Image.LANCZOS)
# 保存為JPG,復制EXIF
new_path = pathlib.Path(DST_DIR) / f"{img_path.stem}.jpg"
exif_dict = piexif.load(img.info.get("exif", b""))
exif_bytes = piexif.dump(exif_dict)
img.save(new_path, format="JPEG", quality=JPG_QUAL, exif=exif_bytes)
提取關鍵字段:
from PIL import Image
from PIL.ExifTags import TAGS, GPSTAGS
import datetime, json
def exif_to_decimal(degrees, minutes, seconds, direction):
deg = degrees[0] / degrees[1]
min = minutes[0] / minutes[1]
sec = seconds[0] / seconds[1]
decimal = deg + min/60 + sec/3600
return -decimal if direction in ['S', 'W'] else decimal
def get_lat_lng(exif):
gps = exif.get("GPSInfo")
if not gps: return None, None
lat = exif_to_decimal(*gps[2], gps[1])
lng = exif_to_decimal(*gps[4], gps[3])
return lat, lng
def extract_meta(img_path):
img = Image.open(img_path)
exif = {TAGS.get(k, k): v for k, v in img._getexif().items()}
lat, lng = get_lat_lng(exif)
date_str = exif.get("DateTime", "")
return {"lat": lat, "lng": lng, "date": date_str}
import googlemaps, os
MAPS_API_KEY = os.getenv("MAPS_API_KEY")
gmaps = googlemaps.Client(key=MAPS_API_KEY)
def enrich_location(lat, lng, radius=1000):
if lat is None or lng is None:
return {"address": None, "nearby": []}
address = gmaps.reverse_geocode((lat, lng), language="en")[0]["formatted_address"]
nearby = gmaps.places_nearby(location=(lat, lng), radius=radius, language="en")["results"][:3]
return {"address": address, "nearby": [{"name": p["name"], "type": p.get("types", [])] for p in nearby]}
import vertexai
from vertexai.vision_models import ImageTextModel, Image
PROJECT_ID = "your-gcp-project"
vertexai.init(project=PROJECT_ID)
model = ImageTextModel.from_pretrained("imagetext")
def caption_image(img_path):
img = Image.load_from_file(img_path)
captions = model.get_captions(
image=img,
number_of_results=1,
language="en"
)
return captions[0] if captions else "A memorable moment"
album_context = """I flew to Los Angeles for a short trip,
and the album contains the photos from the day I arrived there.
The man in those photos is myself."""
few_shot_example = """
Photo 1: Sunset at Santa Monica Pier
Photo 2: Street food on Venice Beach
Blog:
?? Santa Monica, CA — Day 1
I landed in LA just in time for golden hour. The pier stretched into the Pacific, carnival lights flickering...
(insert Photo 1 here)
After sunset, I wandered to Venice Beach for the legendary tacos...
(insert Photo 2 here)
"""
prompt_template = f"""
{album_context}
{few_shot_example}
Now generate a blog post in the same style for the following photos:
{{photos_info}}
"""
from vertexai.language_models import TextGenerationModel
palm = TextGenerationModel.from_pretrained("text-bison@001")
response = palm.predict(prompt=prompt_template, max_output_tokens=1024)
blog_md = response.text
# 1?? 批量處理相冊
meta_list = [extract_meta(p) for p in Path("photos_converted").glob("*.jpg")]
# 2?? 增強地理信息
for m in meta_list:
m.update(enrich_location(m["lat"], m["lng"]))
# 3?? 生成標題
for m in meta_list:
m["caption"] = caption_image(m["path"])
# 4?? 組裝 Few-Shot Prompt
photos_info = "\n".join([f"Photo {i+1}: {m['caption']} ({m['address']})"
for i, m in enumerate(meta_list)])
final_prompt = prompt_template.format(photos_info=photos_info)
# 5?? 生成博客
blog_md = palm.predict(final_prompt, max_output_tokens=2048).text
| 技術 | 作用 | 示例 |
|---|---|---|
| Few-Shot | 減少“跑題” | 提供 1-2 段輸出樣板 |
| 角色扮演 | 統一口吻 | “你是一名旅行博主” |
| 占位符 | 方便后期替換 | (Photo 1 here) → 正則替換為  |
想系統優化 Prompt?用 代碼審查助手 掃描模板,AI 會提示“缺少輸出長度限制”或“示例與真實字段不一致”等潛在坑。??
緩存策略寫不好?對 代碼優化 說“為 Maps 反向地理編碼加 LRU 緩存”,秒出帶 TTL 的 Python 裝飾器。?
監控指標想量化?用 開發任務管理系統 KPI 輸入“平均生成耗時 ≤ 20 秒,緩存命中率 ≥ 60%”,AI 自動拆成每日可追蹤的北極星指標。??
通過“EXIF 提取 → Maps 增強 → 視覺 caption → Few-Shot 提示工程”四連擊,我們把一堆靜態照片變成了可讀、可分享、可 SEO 的博客故事。
省流版:
生成式 AI 不只是聊天,它也能成為你最會講故事的“旅行伙伴”。??