{"id":20560320,"url":"https://github.com/binnette/hiking-signs-ai","last_synced_at":"2025-08-23T11:14:14.055Z","repository":{"id":262347737,"uuid":"886938195","full_name":"Binnette/hiking-signs-ai","owner":"Binnette","description":"Using AI on hiking signs pictures","archived":false,"fork":false,"pushed_at":"2024-11-15T16:40:13.000Z","size":2071,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-06T07:47:22.123Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Binnette.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-11-11T22:04:17.000Z","updated_at":"2024-11-15T16:40:16.000Z","dependencies_parsed_at":"2024-11-12T00:28:58.362Z","dependency_job_id":"5271ae2a-92f8-4c08-84cc-26c1ac841deb","html_url":"https://github.com/Binnette/hiking-signs-ai","commit_stats":null,"previous_names":["binnette/hiking-signs-ai"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Binnette/hiking-signs-ai","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Binnette%2Fhiking-signs-ai","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Binnette%2Fhiking-signs-ai/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Binnette%2Fhiking-signs-ai/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Binnette%2Fhiking-signs-ai/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Binnette","download_url":"https://codeload.github.com/Binnette/hiking-signs-ai/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Binnette%2Fhiking-signs-ai/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271746776,"owners_count":24813583,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-23T02:00:09.327Z","response_time":69,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-16T03:54:13.489Z","updated_at":"2025-08-23T11:14:14.022Z","avatar_url":"https://github.com/Binnette.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🚶‍♂️ Hiking Signs Detection and OCR 🧭\n\n[🇫🇷 Version française](README_fr.md)\n\n- The result of this work can be found in this [geojson file](./hikingSigns.geojson).\n- Consider also adding those hiking signs to OSM using this [MapRoulette Challenge](https://maproulette.org/browse/challenges/49849) 🌍\n\n## About this Repository 📚\n\n### Origins 🌟\n\nSince 2018, I have been taking pictures 📸 of every hiking sign that I came across during my hikes 🥾. These kinds of signs, also known as guideposts:\n\n| On a Pole ⛳️ | On a Tree 🌳 |\n|--------------|--------------|\n| ![Guidepost on a pole](assets/1-guidepost-on-pole.jpg) | ![Guidepost on a tree](assets/2-guidepost-on-tree.jpg) |\n\nMy original idea was to just extract the location from the Exif data of the picture and then create a simple MapRoulette challenge to show the pictures and ask contributors to add the guidepost with its name and altitude ⛰️.\n\nBut in this first idea, you need to read the info on the picture and type it on your keyboard ⌨️. It's okay if you only set the name and the elevation of the guidepost. But it can be very long if you want to create the destination relations with their name, distance, and time ⏲️. It's even longer since there can be a lot of different destinations on each guidepost.\n\nSince I have thousands of guidepost pictures 🖼️, it will take ages. So why not use OCR (character recognition)? My first approach was just a quick and dirty test. I simply created a Python script to loop through my thousands of guidepost pictures. The script would run OCR with the Python library EasyOCR and store the result in a geojson file. The result was terrible. EasyOCR was trying to recognize characters in every part of the image, including the background, forest 🌲, street signs 🚏, etc.\n\nExample of pictures containing unwanted text:\n\n| Indication Panel 🚫 | Restriction Panel 🛑 |\n|----------------------|---------------------|\n| ![Picture with unwanted text](assets/3-unwanted-text.jpg) | ![Picture with unwanted text](assets/4-unwanted-text.jpg) |\n\nRunning the OCR on full pictures was a bad idea, as it was trying to find letters and words everywhere. So I had to crop ✂️ my thousands of images to the inscriptions of the guideposts. To do so, I trained a model to recognize parts of the guideposts and also the signs that do not interest me in order to exclude them.\n\n### Label Images with Label Studio 🏷️\n\nInstall [Label Studio](https://labelstud.io/):\n\n```bash\n# Create and activate python venv\npython -m venv venv\nsource venv/bin/activate\n# Install and run label-studio\npip install -U label-studio\nlabel-studio\n```\n\nThen Label Studio opens itself in your web browser 🌐.\n\n1. I created 2 projects \"Hiking Sign Train 🚞\" and \"Hiking Sign Test 🧪\".\n2. For both, I selected the type `Semantic Segmentation with Polygons` 📐.\n3. I created my categories and set IDs:\n\n```xml\n\u003cView\u003e\n  \u003cHeader value=\"Select label and click the image to start\"/\u003e\n  \u003cImage name=\"image\" value=\"$image\" zoom=\"true\" zoomControl=\"true\"/\u003e\n  \u003cPolygonLabels name=\"label\" toName=\"image\" strokeWidth=\"3\" pointSize=\"small\" opacity=\"0.9\"\u003e\n    \u003cLabel category=\"1\" value=\"top\" background=\"#f66151\"/\u003e\n    \u003cLabel category=\"2\" value=\"destination\" background=\"#dc8add\"/\u003e\n    \u003cLabel category=\"3\" value=\"poster\" background=\"#2ec27e\"/\u003e\n    \u003cLabel category=\"4\" value=\"bike_sign\" background=\"#e66100\"/\u003e\n    \u003cLabel category=\"5\" value=\"street_sign\" background=\"#865e3c\"/\u003e\n    \u003cLabel category=\"6\" value=\"panel\" background=\"#241f31\"/\u003e\n  \u003c/PolygonLabels\u003e\n\u003c/View\u003e\n```\n\n4. I imported 100 pictures into the train project and 15 into the test project.\n5. Then I started to manually annotate each picture, like this ✏️:\n\n| Without Annotation ❌ | With Annotations ✔️ |\n|-----------------------|---------------------|\n| ![Without annotation](assets/5-without-annotation.jpg) | ![With annotations](assets/6-with-annotations.jpg) |\n\nI drew polygons around each element:\n1. **top**: for the cap of the guidepost. Some guideposts don't have it. The top can be yellow or green.\n2. **destination**: for the possible destinations. Usually, they have a name, a number of kilometers, and some have an estimated duration in hours. Most of them also have arrows and a yellow background.\n3. **poster**: for posters, usually regulations or for lost dogs 🐶 and cats 🐱.\n4. **bike_sign**: for dedicated bicycle signs 🚴.\n5. **street_sign**: for road regulations or street name signs 🚏, etc.\n6. **panel**: for tiny regulation panels like no fire 🔥, no swimming 🏊, etc.\n\nI annotated both my training pictures and test pictures and exported my project from Label Studio as a COCO format zip file. I then extracted the zip files into folders: `coco-test-hiking-sign` and `coco-train-hiking-sign`.\n\n### Get Ready to Train a Model 🖥️\n\nThen I wrote a Python script to train a model. I used [PyTorch](https://pytorch.org/) and the open-source framework [Detectron2](https://github.com/facebookresearch/detectron2) for object detection in my pictures.\n\nFirst, I trained my model on my laptop 💻, which is old and doesn't have a good GPU. The training lasted for 2.5 hours for only 500 iterations. Then I used this model on my pictures, and the result was terrible. My model was barely detecting the different parts of the guidepost and was misidentifying them.\n\nSo I used a desktop computer with an NVIDIA GPU 🎮. As it was not my computer, I had to use WSL since `Detectron2` is compatible only with Linux systems 🐧.\n\n1. Install [WSL](https://github.com/microsoft/WSL)\n2. Install Ubuntu on WSL 2+\n3. Install Python, NVIDIA drivers, and other packages:\n\n```bash\napt install python3 python3-dev git ubuntu-drivers nvidia-smi\nubuntu-drivers list --gpgpu\n# I tried the 'open' driver but it didn't work\nubuntu-drivers install nvidia:550\n# Instructions from https://developer.nvidia.com/cuda-downloads?target_os=Linux\u0026target_arch=x86_64\u0026Distribution=WSL-Ubuntu\u0026target_version=2.0\u0026target_type=deb_network\nwget https://developer.download.nvidia.com/compute/cuda/repos/wsl-ubuntu/x86_64/cuda-keyring_1.1-1_all.deb\ndpkg -i cuda-keyring_1.1-1_all.deb\napt-get update\napt-get -y install cuda-toolkit-12-6\nnvidia-smi\n```\n\n4. The last command should show info about your GPU and the CUDA version.\n\nNow you can try this script to see if PyTorch can use your NVIDIA GPU:\n\n```bash\n# Activate python venv\nsource venv/bin/activate\npip install wheel\npip install torch torchvision\npython 11-testGpu.py\n```\n\nThen I finally installed Detectron2:\n\n```bash\ngit clone https://github.com/facebookresearch/detectron2.git\npip install -e detectron2\n```\n\n### Train a Model 🏋️‍♂️\n\n```bash\npython 20-trainObjectDetectionModel.py\n```\n\nWith 500 iterations, the training lasted for 5 minutes on this computer instead of 2.5 hours on my laptop. But still, the model was terrible. So I increased the iterations to 5000+, the training lasted around 2 hours, and the model was \"PERFECT\" 🥳. I was quite happy with the result.\n\n### Test My Model 🔬\n\nSo I tested my model by using it to annotate my pictures:\n\n```bash\npython 21-testObjectDetectionModel.py\n```\n\nHere is a visualization of objects detected by my model:\n\n| Detection 🔍 | Detection 🔍 |\n|--------------|--------------|\n| ![Annotated by model](assets/7-object-detection.jpg) | ![Annotated by model](assets/8-object-detection.jpg) |\n\nAs you can see, my model is pretty confident in recognizing the top and destination panels of guideposts.\n\n### Use the Model to Crop Pictures ✂️\n\nNow that the model is trained, I wrote another script to crop images according to the given top and destination areas. It outputs cropped images in folders `crop/top` and `crop/destination`.\n\n```bash\npython 22-cropUsingObjectDetectionModel.py\n```\n\n### Create a MapRoulette Challenge 🌐\n\nIn order to create my MapRoulette challenge, I needed to upload my pictures on a website 🌍. Of course, I chose [Panoramax](https://panoramax.openstreetmap.fr/). I used the old command line tool `geovisio` to upload my pictures on Panoramax because it creates a `toml` report file.\n\nNow it is time to create a geojson to regroup all the information. The following script:\n1. Extracts latitude and longitude from image Exif data 🗺️.\n2. Extracts the Panoramax image URL from the geovisio toml file 📝.\n3. Loops through every cropped image and uses OCR on them to extract text 🖋️.\n4. Creates a geojson feature for each picture 🌍.\n5. Finally outputs a geojson file with all this data 📄.\n\nThen, you can open this geojson file with JOSM and convert it into an `osm` file. Use [mr-cli](https://github.com/maproulette/mr-cli) to convert this osm file into a MapRoulette Cooperative Challenge geojson file:\n\n```bash\nmr cooperative change --out hikingSignsCoopMrChallenge.json hikingSigns.osm\n```\n\nFinally, create the challenge on MapRoulette and upload the geojson file. The challenge can be found here: [MapRoulette Challenge](https://maproulette.org/browse/challenges/49849) 🌟.\n\n# What's Next 🔮\n\n- Work in Progress: use vision llm to extract characters from images instead of classic OCR 🤖.\n- Fix panels' perspectives to make OCR work better 🛠️.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbinnette%2Fhiking-signs-ai","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbinnette%2Fhiking-signs-ai","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbinnette%2Fhiking-signs-ai/lists"}