ageitgey

Stars and mentions

Last 30 days, Oct 4 – Nov 2

GitHub stars95
mentions1
02468Oct 4Oct 18Nov 2
UTC days · repositories ageitgey owns4 repositories

Package downloads

No package download history available yet.

Top repositories

ageitgey’s repositories ranked by new stars, as on trending.

Loading

Contributes to

Repositories outside ageitgey’s account where ageitgey opened or reviewed pull requests in the past 365 days.

Who noticed ageitgey

Everyone who starred or forked ageitgey’s repositories since the archive began in 2011.

Notable accounts

Ordered by the stars their own repositories have received.

Biggest fans

Accounts that starred the most of these repositories.

Breakout weeks

Each repository’s best week of new stars, with the release published around it.

Mentions

1post
Nov 30 posts
  1. Group Photos by Face in Python: One Album per Person --- title: "Group Photos by Face in Python: One Album per Person" published: true tags: python, api, computervision, machinelearning canonical_url: ai-engine.net cover_image: ai-engine.net --- Phone galleries, family archives, a photographer's backlog: where are all the photos of this person? Here is how to group photos by face in Python: detect every face, match it in a face repository, and sort the library into one album per person. No model to download, no dlib to compile. Every number below comes from a live run on 24 photos from two outings, a camping trip and a beach picnic. > Want to sort your own library? Get a key for the Face Analyzer API. Step 1: create a face repository import io import os import shutil import requests from concurrent.futures import ThreadPoolExecutor from PIL import Image, ImageOps BASE = "https://faceanalyzer-ai.p.rapidapi.com" HEADERS = { "x-rapidapi-host": "faceanalyzer-ai.p.rapidapi.com", "x-rapidapi-key": "YOUR_API_KEY", } LIBRARY = "my-photo-library" MIN_SIMILARITY = 90 r = requests.post( f"{BASE}/create-facial-repository", headers=HEADERS, data={"repository_id": LIBRARY}, ) print(r.json()) Step 2: detect and crop every face def detect_faces(path): """Return the photo path and the bounding box of every face in it.""" with open(path, "rb") as f: r = requests.post(f"{BASE}/faceanalysis", headers=HEADERS, files={"image": f}) return path, [face["boundingBox"] for face in r.json()["body"]["faces"]] def crop_face(path, box, margin=0.4): """Cut one face out of the photo, with a margin, as JPEG bytes.""" im = ImageOps.exif_transpose(Image.open(path)).convert("RGB") w, h = im.size x1, y1 = box["topLeft"]["x"] * w, box["topLeft"]["y"] * h x2, y2 = box["bottomRight"]["x"] * w, box["bottomRight"]["y"] * h mx, my = (x2 - x1) * margin, (y2 - y1) * margin face = im.crop(( int(max(0, x1 - mx)), int(max(0, y1 - my)), int(min(w, x2 + mx)), int(min(h, y2 + my)), )) buf = io.BytesIO() face.save(buf, "JPEG", quality=92) return buf.getvalue() The repository search looks at the main face of the image it receives, so each face is sent as its own crop. Step 3: match each face or start a new person def who_is(face_jpg): """Return the label of the closest known person, or None for a new face.""" r = requests.post( f"{BASE}/search-face-in-repository", headers=HEADERS, files={"image": ("face.jpg", face_jpg)}, data={"repository_id": LIBRARY}, ) matches = [ m for m in r.json()["body"].get("FaceMatches", []) if m["Similarity"] >= MIN_SIMILARITY ] if not matches: return None return max(matches, key=lambda m: m["Similarity"])["ExternalImageId"] def remember(face_jpg, person): """Save the face under the person's label, so later photos match it too.""" requests.post( f"{BASE}/save-face-in-repository", headers=HEADERS, files={"image": ("face.jpg", face_jpg)}, data={"repository_id": LIBRARY, "external_id": person, "max_faces": "1"}, ) people = {} # person label -> names of the photos they appear in def sort_photos(folder): """Detect faces in parallel, then match each face to a person in order.""" paths = sorted(os.path.join(folder, n) for n in os.listdir(folder)) with ThreadPoolExecutor(max_workers=4) as pool: detected = list(pool.map(detect_faces, paths)) for path, boxes in detected: for box in boxes: face_jpg = crop_face(path, box) person = who_is(face_jpg) if person is None: person = f"person_{len(people) + 1}" people[person] = set() remember(face_jpg, person) people[person].add(path) return sum(len(boxes) for _, boxes in detected) faces = sort_photos("photos") print(faces, "faces,", len(people), "face groups") # 54 faces, 16 face groups Step 4: one album per person MIN_PHOTOS = 3 def build_albums(out_dir="albums"): """Copy each photo into the album of every person in it.""" shutil.rmtree(out_dir, ignore_errors=True) for person, paths in people.items(): album = person if len(paths) >= MIN_PHOTOS else "unsorted" os.makedirs(os.path.join(out_dir, album), exist_ok=True) for path in paths: shutil.copy(path, os.path.join(out_dir, album, os.path.basename(path))) for album in sorted(os.listdir(out_dir)): print(album, len(os.listdir(os.path.join(out_dir, album))), "photos") build_albums() person_10 6 photos person_12 4 photos person_13 4 photos person_14 5 photos person_2 6 photos person_3 6 photos person_5 7 photos person_6 5 photos person_9 3 photos unsorted 7 photos Nine albums for the nine people: the two campers and the seven picnic friends. Every album contains a single person, checked by eye. Faces seen once or twice (a head turned away, a passer-by out of focus) go to unsorted. !Nine person albums built by a face recognition API from a 24-photo library Step 5: new photos join the right album before = {person: set(paths) for person, paths in people.items()} sort_photos("new_photos") for person, paths in people.items(): for path in sorted(paths - before.get(person, set())): print(os.path.basename(path), "->", person) build_albums() IMG_5064932.jpg -> person_3 IMG_8021122.jpg -> person_9 Under 3 seconds, no re-clustering. Why not face_recognition + DBSCAN? | | face_recognition + DBSCAN | Face Analyzer API | |---|---|---| | Install | dlib source build (CMake, C++) | requests and Pillow | | Last release | 1.3.0, February 2020 | hosted | | New photos | re-cluster, labels can change | one search per face, stable labels | | Tuning | eps and min_samples | one similarity threshold | | Children | README warns it mixes them up at 0.6 | same code for every face | Production tips - One repository per user, deleted when they turn the feature off - Face grouping is biometric processing under GDPR Article 9: make it opt-in - Let users merge and rename albums - Run the first pass as a background job Full guide: group photos by face on ai-engine.net. Sources: face_recognition, dlib on PyPI, GDPR Article 9, test photos from Pexels (Kamaji Ogino, Thirdman). 👉 Grab a key from the Face Analyzer API page and sort your own photos.

Trending awards, every completed day and week
Gold
21st place finishes
Silver
02nd place finishes
Bronze
03rd place finishes
Awarded repositories
2Owned by ageitgey now

Repositories

Ordered by gold, then silver, then bronze.

  1. amplifyBest: 1st by stars, 859 stars100
  2. node-unfluffBest: 1st by stars, 400 stars100

Every award

Top-three finishes of these repositories in completed trending days and weeks, newest first.

  1. #1 by starsDaily trending
    859 stars
    amplify
  2. #1 by starsDaily trending
    400 stars
    node-unfluff

A repository earns an award for finishing first, second or third in a completed trending day or week, ranked by new stars, new forks or pull request authors. Awards follow a repository through renames and transfers and count toward its current owner. Weeks run Monday to Sunday, UTC.