Easy-to-manage touch screens
무료 오픈소스 터치스크린으로 USB 설정 가이드, 드래그‑드롭 편집기, 로컬 제어를 제공하며 Home Assistant용으로 제작되었습니다
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Scanner de cartas Pokémon TCG pela câmera do celular, direto no navegador. Aponte a câmera para a carta e o site diz qual é, com preço de referência e links de busca para a Liga Pokémon e a MYP Cards. Pokémon TCG card scanner for the phone browser. Point the camera at a card to identify it.README 전문 보기 ↗
Scanner de cartas Pokémon TCG pela câmera do celular, direto no navegador. Aponte a câmera para a carta e o site diz qual é, com preço de referência e links de busca para a Liga Pokémon e a MYP Cards.
Pokémon TCG card scanner for the phone browser. Point the camera at a card to identify it.
Cartas em japonês ou coreano são identificadas pela arte e caem na versão em inglês equivalente. O servidor publicado também tem algumas coleções japonesas inteiras (ex.: Storm Emeralda), com a imagem da carta japonesa.
câmera → retícula viva (scanic, segue os 4 cantos da carta)
→ foto nítida → YOLO acha os 4 cantos → corte com correção de perspectiva
→ POST /recognize (servidor)
DINOv2-S @336 → similaridade de cosseno contra ~20 mil cartas
→ menos a penalidade das cartas "ímã" (as que parecem com qualquer foto)
→ desempate por pHash quando dois prints têm a mesma arte
→ só quando o match é ambíguo, OCR (RapidOCR): o nome no topo da carta e, se ainda faltar, o número no rodapé em alta resolução
→ resultado + preço de referência + links de busca
O reconhecimento roda num servidor, não no celular. Rodar o modelo no navegador travava a tela e levava segundos por carta. No servidor quente, um scan leva ~0,5s.
O site publicado usa o nosso servidor. Para usar o reconhecimento no seu projeto, veja Usar o reconhecimento no seu projeto.
Decisões de arquitetura e o porquê de cada uma: docs/arquitetura.md.
npm install
cp .env.example .env # ajuste VITE_RECOGNIZE_URL se usar outro servidor
npm run dev # http://localhost:5173
A câmera exige HTTPS, mas localhost é exceção. Para testar no celular, publique o build (veja abaixo) ou use um túnel HTTPS.
npm run typecheck
npm run build # gera dist/, um site estático
O dist/ é estático e roda em qualquer host (Vercel, Netlify, GitHub Pages, Cloudflare Pages). Configure as variáveis no build:
| Variável | Uso |
|---|---|
VITE_RECOGNIZE_URL | URL do servidor de reconhecimento. Vazio = o servidor publicado (https://cartinhas-recognize.fly.dev). |
VITE_FEEDBACK_URL | Opcional. Link do botão "Mandar feedback". |
VITE_TELEMETRY_URL | Opcional. Endpoint que recebe a telemetria anônima (POST, corpo JSON em texto puro). Vazio = sem telemetria. |
Não existe um modelo treinado por nós. O modelo é o DINOv2-small público (Meta, Apache-2.0), sem ajuste. O que faz o reconhecimento funcionar é o que está em volta dele:
Tudo isso está no código de server/, com os scripts para gerar o modelo e o índice. Há duas formas de usar:
Siga o server/README.md. O atalho é baixar da última release o modelo, o índice e a penalidade prontos, e pular direto para rodar. Do zero:
VITE_RECOGNIZE_URL.Diferenças para o servidor publicado:
Um build sem VITE_RECOGNIZE_URL já usa o nosso servidor (https://cartinhas-recognize.fly.dev), que aceita chamadas de qualquer site. Serve para testar e para uso pessoal, com estes limites:
Para um site com tráfego de verdade, rode o seu servidor (opção 1).
POST {VITE_RECOGNIZE_URL}/recognize, multipart/form-data:
file: JPEG da carta (lado maior até 900px).pre: 1 quando a imagem já é só a carta recortada. Sem ele, o servidor detecta a carta na foto.footer (opcional, só com pre=1): JPEG dos 28% de baixo da mesma carta, em resolução cheia (até 1200px de largura). O servidor lê o número da carta nesta imagem. No file reduzido, o número tem ~12px e o OCR não lê.Resposta:
{
"card": { "api_id": "sv07-158", "name": "Lapras ex", "number": "158", "set_id": "sv07",
"set_name": "Stellar Crown", "printed_total": 142,
"image_url": "https://assets.tcgdex.net/en/sv/sv07/158/low.webp", "cos": 0.91 },
"confident": true,
"not_card": false,
"ocr": { "number": "158", "total": "142", "match": true, "name": "Lapras ex 220", "name_match": true },
"candidates": [ "… até 5 cartas no mesmo formato de card …" ]
}
confident: true quando o número lido confere, quando a semelhança é alta com folga sobre a 2ª candidata, ou quando o nome lido confere e a semelhança é alta com folga sobre as outras cartas desse nome. Com false, mostre as candidatas para a pessoa escolher.not_card: true quando a foto não parece carta nenhuma (mão, mesa, tecido). O card continua vindo, mas não deve ser mostrado.candidates: a carta escolhida primeiro, depois a ordem do ranking do servidor, que desconta as cartas "ímã" e põe na frente as cartas do nome lido. Por isso o cos nem sempre aparece em ordem decrescente.ocr: o que o servidor leu na carta. name é o texto lido no topo e name_match diz se ele casou com o nome de alguma carta. Os dois vêm vazios quando o OCR não rodou (match decisivo ou quadro sem carta).400 quando o file não é uma imagem; 413 quando a imagem tem mais de 16 milhões de pixels.api_id é o id do TCGdex, na maioria das cartas: https://api.tcgdex.net/v2/en/cards/{api_id} traz o resto dos dados. As exceções são as coleções que só o servidor publicado tem (ex.: m6-jp-87).GET /health acorda o servidor. O site chama essa rota ao abrir, porque a máquina suspende quando fica ociosa e o primeiro scan depois disso pode levar ~20s.
Uma foto da carta, de preferência só a carta (sem pre, o servidor procura a carta na foto). Troque SERVER pelo seu servidor (veja Usar o reconhecimento no seu projeto).
curl
curl -F "[email protected]" https://cartinhas-recognize.fly.dev/recognize
JavaScript (navegador ou Node 18+)
const SERVER = "https://cartinhas-recognize.fly.dev";
// `foto` é um Blob/File: um <input type="file">, ou canvas.toBlob(cb, "image/jpeg", 0.8).
async function reconhecer(foto) {
const form = new FormData();
form.append("file", foto, "carta.jpg");
const res = await fetch(`${SERVER}/recognize`, { method: "POST", body: form });
const r = await res.json();
if (r.not_card) return null;
return { carta: r.card, certeza: r.confident, candidatas: r.candidates };
}
Python
import requests
SERVER = "https://cartinhas-recognize.fly.dev"
with open("carta.jpg", "rb") as f:
r = requests.post(f"{SERVER}/recognize", files={"file": ("carta.jpg", f, "image/jpeg")}, timeout=60).json()
if r["not_card"]:
print("Não é uma carta")
else:
c = r["card"]
print(c["name"], c["set_name"], c["number"], "(certeza)" if r["confident"] else "(confira)")
for alt in r["candidates"]:
print(" -", alt["api_id"], alt["cos"])
Saída com a foto do Altaria de Surging Sparks:
Altaria Surging Sparks 134 (certeza)
- sv08-134 0.916
- swsh3.5-49 0.732
- swsh12-143 0.733
- swsh7-106 0.716
- sv03-160 0.746
A primeira chamada com a máquina dormindo leva até ~20s. Use um timeout de pelo menos 40s, ou chame GET /health antes.
A foto da carta vai para o servidor só para ser identificada. Ela fica no máximo ~6 horas em disco para depuração e depois é apagada. O site não tem conta nem cookie. A lista de cartas fica só no seu aparelho.
A versão publicada coleta telemetria anônima para medir o acerto do scanner: um ID aleatório por aparelho (gerado no localStorage), o país (vindo da CDN), a origem do acesso, os eventos de scan (cartas candidatas, confiança, tempo, "não é essa") e os dados técnicos da câmera que o navegador informa (nome da lente, modo e distância de foco, resolução). Não vai foto, IP nem nada que identifique a pessoa. O código está em src/telemetry.ts. Ele só envia quando o build define VITE_TELEMETRY_URL, então um build seu não manda nada.