|
| 1 | +<!--Copyright 2025 The HuggingFace Team. All rights reserved. |
| 2 | +
|
| 3 | +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
| 4 | +the License. You may obtain a copy of the License at |
| 5 | +
|
| 6 | +http://www.apache.org/licenses/LICENSE-2.0 |
| 7 | +
|
| 8 | +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
| 9 | +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
| 10 | +specific language governing permissions and limitations under the License. |
| 11 | +
|
| 12 | +⚠️ Note that this file is in Markdown but contain specific syntax for our doc-builder (similar to MDX) that may not be |
| 13 | +rendered properly in your Markdown viewer. |
| 14 | +
|
| 15 | +--> |
| 16 | + |
| 17 | +# Keypoint matching |
| 18 | + |
| 19 | +Keypoint matching matches different points of interests that belong to same object appearing in two different images. Most modern keypoint matchers take images as input and output the following: |
| 20 | + |
| 21 | +- **Keypoint coordinates (x,y):** one-to-one mapping of pixel coordinates between the first and the second image using two lists. Each keypoint at a given index in the first list is matched to the keypoint at the same index in the second list. |
| 22 | +- **Matching scores:** Scores assigned to the keypoint matches. |
| 23 | + |
| 24 | +In this tutorial, you will extract keypoint matches with the [`EfficientLoFTR`] model trained with the [MatchAnything framework](https://huggingface.co/zju-community/matchanything_eloftr), and refine the matches. This model is only 16M parameters and can be run on a CPU. You will use the [`AutoModelForKeypointMatching`] class. |
| 25 | + |
| 26 | +```python |
| 27 | +from transformers import AutoImageProcessor, AutoModelForKeypointMatching |
| 28 | +import torch |
| 29 | + |
| 30 | +processor = AutoImageProcessor.from_pretrained("zju-community/matchanything_eloftr") |
| 31 | +model = AutoModelForKeypointMatching.from_pretrained("zju-community/matchanything_eloftr")) |
| 32 | +``` |
| 33 | + |
| 34 | +Load two images that have the same object of interest. The second photo is taken a second apart, it's colors are edited, and it is further cropped and rotated. |
| 35 | + |
| 36 | +<div style="display: flex; align-items: center;"> |
| 37 | + <img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/bee.jpg" |
| 38 | + alt="Bee" |
| 39 | + style="height: 200px; object-fit: contain; margin-right: 10px;"> |
| 40 | + <img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/bee_edited.jpg" |
| 41 | + alt="Bee edited" |
| 42 | + style="height: 200px; object-fit: contain;"> |
| 43 | +</div> |
| 44 | + |
| 45 | +```python |
| 46 | +from transformers.image_utils import load_image |
| 47 | +image1 = load_image("https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/bee.jpg") |
| 48 | +image2 = load_image("https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/bee_edited.jpg") |
| 49 | + |
| 50 | +images = [image1, image2] |
| 51 | +``` |
| 52 | + |
| 53 | +We can pass the images to the processor and infer. |
| 54 | + |
| 55 | +```python |
| 56 | +inputs = processor(images, return_tensors="pt") |
| 57 | +with torch.no_grad(): |
| 58 | + outputs = model(**inputs) |
| 59 | +``` |
| 60 | + |
| 61 | +We can postprocess the outputs. The threshold parameter is used to refine noise (lower confidence thresholds) in the output matches. |
| 62 | + |
| 63 | +```python |
| 64 | +image_sizes = [[(image.height, image.width) for image in images]] |
| 65 | + |
| 66 | +outputs = processor.post_process_keypoint_matching(outputs, image_sizes, threshold=0.2) |
| 67 | +print(outputs) |
| 68 | +``` |
| 69 | + |
| 70 | +Here's the outputs. |
| 71 | + |
| 72 | +``` |
| 73 | +[{'keypoints0': tensor([[4514, 550], |
| 74 | + [4813, 683], |
| 75 | + [1972, 1547], |
| 76 | + ... |
| 77 | + [3916, 3408]], dtype=torch.int32), |
| 78 | + 'keypoints1': tensor([[2280, 463], |
| 79 | + [2378, 613], |
| 80 | + [2231, 887], |
| 81 | + ... |
| 82 | + [1521, 2560]], dtype=torch.int32), |
| 83 | + 'matching_scores': tensor([0.2189, 0.2073, 0.2414, ... |
| 84 | + ])}] |
| 85 | +``` |
| 86 | + |
| 87 | +We have trimmed the output but there's 401 matches! |
| 88 | + |
| 89 | +```python |
| 90 | +len(outputs[0]["keypoints0"]) |
| 91 | +# 401 |
| 92 | +``` |
| 93 | + |
| 94 | +We can visualize them using the processor's [`~EfficientLoFTRImageProcessor.visualize_keypoint_matching`] method. |
| 95 | + |
| 96 | +```python |
| 97 | +plot_images = processor.visualize_keypoint_matching(images, outputs) |
| 98 | +plot_images |
| 99 | +``` |
| 100 | + |
| 101 | + |
| 102 | + |
| 103 | +Optionally, you can use the [`Pipeline`] API and set the task to `keypoint-matching`. |
| 104 | + |
| 105 | +```python |
| 106 | +from transformers import pipeline |
| 107 | + |
| 108 | +image_1 = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/bee.jpg" |
| 109 | +image_2 = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/bee_edited.jpg" |
| 110 | + |
| 111 | +pipe = pipeline("keypoint-matching", model="zju-community/matchanything_eloftr") |
| 112 | +pipe([image_1, image_2]) |
| 113 | +``` |
| 114 | + |
| 115 | +The output looks like following. |
| 116 | + |
| 117 | +```bash |
| 118 | +[{'keypoint_image_0': {'x': 2444, 'y': 2869}, |
| 119 | + 'keypoint_image_1': {'x': 837, 'y': 1500}, |
| 120 | + 'score': 0.9756593704223633}, |
| 121 | + {'keypoint_image_0': {'x': 1248, 'y': 2819}, |
| 122 | + 'keypoint_image_1': {'x': 862, 'y': 866}, |
| 123 | + 'score': 0.9735618829727173}, |
| 124 | + {'keypoint_image_0': {'x': 1547, 'y': 3317}, |
| 125 | + 'keypoint_image_1': {'x': 1436, 'y': 1500}, |
| 126 | + ... |
| 127 | + } |
| 128 | +] |
| 129 | +``` |
0 commit comments