[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M7C8GYMPRXBABY4GP9D2M2DF":3,"skill-same:01M7C8GYMPRXBABY4GP9D2M2DF":45},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":19,"linkOnly":20,"markdownContent":21,"markdownBody":22,"frontMatter":23,"refName":24,"commitSha":25,"skillDirectory":26,"forksCount":27,"repositoryHtmlUrl":28,"repositoryHomepageUrl":6,"ownerHtmlUrl":29,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":30,"install":31,"resources":44},"01M7C8GYMPRXBABY4GP9D2M2DF","image-ocr",null,"Extract text content from images using Tesseract OCR via Python","linyouxue\u002Fskill-repair-benchmark","linyouxue","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F216151464?v=4","01M7BJEVCXKD74KH6FG9CBVSQB","林宥学","01M7BJEVE02NP217V93BKZFC0J",0,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Flinyouxue\u002Fskill-repair-benchmark\u002Fblob\u002Fmain\u002Fjpg-ocr-stat\u002Fresult\u002Fwml-claude4.7\u002Fskills\u002Fimage-ocr\u002FSKILL.md",[],[],"2026-11-15 14:19:17",false,"---\nname: image-ocr\ndescription: Extract text content from images using Tesseract OCR via Python\n---\n\n# Image OCR Skill\n\n## Purpose\nThis skill enables accurate text extraction from image files (JPG, PNG, etc.) using Tesseract OCR via the `pytesseract` Python library. It is suitable for scanned documents, screenshots, photos of text, receipts, forms, and other visual content containing text.\n\n## When to Use\n- Extracting text from scanned documents or photos\n- Reading text from screenshots or image captures\n- Processing batch image files that contain textual information\n- Converting visual documents to machine-readable text\n- Extracting structured data from forms, receipts, or tables in images\n\n## Required Libraries\n\nThe following Python libraries are required:\n\n```python\nimport pytesseract\nfrom PIL import Image\nimport json\nimport os\n```\n\n## Input Requirements\n- **File formats**: JPG, JPEG, PNG, WEBP\n- **Image quality**: Minimum 300 DPI recommended for printed text; clear and legible text\n- **File size**: Under 5MB per image (resize if necessary)\n- **Text language**: Specify if non-English to improve accuracy\n\n## Output Schema\nAll extracted content must be returned as valid JSON conforming to this schema:\n\n```json\n{\n  \"success\": true,\n  \"filename\": \"example.jpg\",\n  \"extracted_text\": \"Full raw text extracted from the image...\",\n  \"confidence\": \"high|medium|low\",\n  \"metadata\": {\n    \"language_detected\": \"en\",\n    \"text_regions\": 3,\n    \"has_tables\": false,\n    \"has_handwriting\": false\n  },\n  \"warnings\": [\n    \"Text partially obscured in bottom-right corner\",\n    \"Low contrast detected in header section\"\n  ]\n}\n```\n\n\n### Field Descriptions\n\n- `success`: Boolean indicating whether text extraction completed\n- `filename`: Original image filename\n- `extracted_text`: Complete text content in reading order (top-to-bottom, left-to-right)\n- `confidence`: Overall OCR confidence level based on image quality and text clarity\n- `metadata.language_detected`: ISO 639-1 language code\n- `metadata.text_regions`: Number of distinct text blocks identified\n- `metadata.has_tables`: Whether tabular data structures were detected\n- `metadata.has_handwriting`: Whether handwritten text was detected\n- `warnings`: Array of quality issues or potential errors\n\n\n## Code Examples\n\n### Basic OCR Extraction\n\n```python\nimport pytesseract\nfrom PIL import Image\n\ndef extract_text_from_image(image_path):\n    \"\"\"Extract text from a single image using Tesseract OCR.\"\"\"\n    img = Image.open(image_path)\n    text = pytesseract.image_to_string(img)\n    return text.strip()\n```\n\n### OCR with Confidence Data\n\n```python\nimport pytesseract\nfrom PIL import Image\n\ndef extract_with_confidence(image_path):\n    \"\"\"Extract text with per-word confidence scores.\"\"\"\n    img = Image.open(image_path)\n\n    # Get detailed OCR data including confidence\n    data = pytesseract.image_to_data(img, output_type=pytesseract.Output.DICT)\n\n    words = []\n    confidences = []\n\n    for i, word in enumerate(data['text']):\n        if word.strip():  # Skip empty strings\n            words.append(word)\n            confidences.append(data['conf'][i])\n\n    # Calculate average confidence\n    avg_confidence = sum(c for c in confidences if c > 0) \u002F len([c for c in confidences if c > 0]) if confidences else 0\n\n    return {\n        'text': ' '.join(words),\n        'average_confidence': avg_confidence,\n        'word_count': len(words)\n    }\n```\n\n### Full OCR with JSON Output\n\n```python\nimport pytesseract\nfrom PIL import Image\nimport json\nimport os\n\ndef ocr_to_json(image_path):\n    \"\"\"Perform OCR and return results as JSON.\"\"\"\n    filename = os.path.basename(image_path)\n    warnings = []\n\n    try:\n        img = Image.open(image_path)\n\n        # Get detailed OCR data\n        data = pytesseract.image_to_data(img, output_type=pytesseract.Output.DICT)\n\n        # Extract text preserving structure\n        text = pytesseract.image_to_string(img)\n\n        # Calculate confidence\n        confidences = [c for c in data['conf'] if c > 0]\n        avg_conf = sum(confidences) \u002F len(confidences) if confidences else 0\n\n        # Determine confidence level\n        if avg_conf >= 80:\n            confidence = \"high\"\n        elif avg_conf >= 50:\n            confidence = \"medium\"\n        else:\n            confidence = \"low\"\n            warnings.append(f\"Low OCR confidence: {avg_conf:.1f}%\")\n\n        # Count text regions (blocks)\n        block_nums = set(data['block_num'])\n        text_regions = len([b for b in block_nums if b > 0])\n\n        result = {\n            \"success\": True,\n            \"filename\": filename,\n            \"extracted_text\": text.strip(),\n            \"confidence\": confidence,\n            \"metadata\": {\n                \"language_detected\": \"en\",\n                \"text_regions\": text_regions,\n                \"has_tables\": False,\n                \"has_handwriting\": False\n            },\n            \"warnings\": warnings\n        }\n\n    except Exception as e:\n        result = {\n            \"success\": False,\n            \"filename\": filename,\n            \"extracted_text\": \"\",\n            \"confidence\": \"low\",\n            \"metadata\": {\n                \"language_detected\": \"unknown\",\n                \"text_regions\": 0,\n                \"has_tables\": False,\n                \"has_handwriting\": False\n            },\n            \"warnings\": [f\"OCR failed: {str(e)}\"]\n        }\n\n    return result\n\n# Usage\nresult = ocr_to_json(\"document.jpg\")\nprint(json.dumps(result, indent=2))\n```\n\n### Batch Processing Multiple Images\n\n```python\nimport pytesseract\nfrom PIL import Image\nimport json\nimport os\nfrom pathlib import Path\n\ndef process_image_directory(directory_path, output_file):\n    \"\"\"Process all images in a directory and save results.\"\"\"\n    image_extensions = {'.jpg', '.jpeg', '.png', '.webp'}\n    results = []\n\n    for file_path in sorted(Path(directory_path).iterdir()):\n        if file_path.suffix.lower() in image_extensions:\n            result = ocr_to_json(str(file_path))\n            results.append(result)\n            print(f\"Processed: {file_path.name}\")\n\n    # Save results\n    with open(output_file, 'w') as f:\n        json.dump(results, f, indent=2)\n\n    return results\n```\n\n\n## Tesseract Configuration Options\n\n### Language Selection\n\n```python\n# Specify language (default is English)\ntext = pytesseract.image_to_string(img, lang='eng')\n\n# Multiple languages\ntext = pytesseract.image_to_string(img, lang='eng+fra+deu')\n```\n\n### Page Segmentation Modes (PSM)\n\nUse `--psm` to control how Tesseract segments the image:\n\n```python\n# PSM 3: Fully automatic page segmentation (default)\ntext = pytesseract.image_to_string(img, config='--psm 3')\n\n# PSM 4: Assume single column of text\ntext = pytesseract.image_to_string(img, config='--psm 4')\n\n# PSM 6: Assume uniform block of text\ntext = pytesseract.image_to_string(img, config='--psm 6')\n\n# PSM 11: Sparse text - find as much text as possible\ntext = pytesseract.image_to_string(img, config='--psm 11')\n```\n\nCommon PSM values:\n- `0`: Orientation and script detection (OSD) only\n- `3`: Fully automatic page segmentation (default)\n- `4`: Single column of text of variable sizes\n- `6`: Uniform block of text\n- `7`: Single text line\n- `11`: Sparse text\n- `13`: Raw line\n\n\n## Image Preprocessing\n\nFor better OCR accuracy, preprocess images:\n\n```python\nfrom PIL import Image, ImageFilter, ImageOps\n\ndef preprocess_image(image_path):\n    \"\"\"Preprocess image for better OCR results.\"\"\"\n    img = Image.open(image_path)\n\n    # Convert to grayscale\n    img = img.convert('L')\n\n    # Increase contrast\n    img = ImageOps.autocontrast(img)\n\n    # Apply slight sharpening\n    img = img.filter(ImageFilter.SHARPEN)\n\n    return img\n\n# Use preprocessed image for OCR\nimg = preprocess_image(\"document.jpg\")\ntext = pytesseract.image_to_string(img)\n```\n\n### Advanced Preprocessing Strategies\n\nFor difficult images (low contrast, faded text, dark backgrounds), try multiple preprocessing approaches:\n\n1. **Grayscale + Autocontrast** - Basic enhancement for most images\n2. **Inverted** - Use `ImageOps.invert()` for dark backgrounds with light text\n3. **Scaling** - Upscale small images (e.g., 2x or 3x) before OCR to improve character recognition\n4. **Thresholding** - Convert to binary using `img.point(lambda p: 255 if p > threshold else 0)` with different threshold values (e.g., 100, 128)\n5. **Sharpening** - Apply `ImageFilter.SHARPEN` to improve edge clarity\n\n\n## Multi-Pass OCR Strategy\n\nFor challenging images, a single OCR pass may miss text. Use multiple passes with different configurations:\n\n1. **Try multiple PSM modes** - Different page segmentation modes work better for different layouts (e.g., `--psm 6` for blocks, `--psm 4` for columns, `--psm 11` for sparse text)\n\n2. **Try multiple preprocessing variants** - Run OCR on several preprocessed versions of the same image\n\n3. **Combine results** - Aggregate text from all passes to maximize extraction coverage\n\n```python\ndef multi_pass_ocr(image_path):\n    \"\"\"Run OCR with multiple strategies and combine results.\"\"\"\n    img = Image.open(image_path)\n    gray = ImageOps.grayscale(img)\n\n    # Generate preprocessing variants\n    variants = [\n        ImageOps.autocontrast(gray),\n        ImageOps.invert(ImageOps.autocontrast(gray)),\n        gray.filter(ImageFilter.SHARPEN),\n    ]\n\n    # PSM modes to try\n    psm_modes = ['--psm 6', '--psm 4', '--psm 11']\n\n    all_text = []\n    for variant in variants:\n        for psm in psm_modes:\n            try:\n                text = pytesseract.image_to_string(variant, config=psm)\n                if text.strip():\n                    all_text.append(text)\n            except Exception:\n                pass\n\n    # Combine all extracted text\n    return \"\\n\".join(all_text)\n```\n\nThis approach improves extraction for receipts, faded documents, and images with varying quality.\n\n\n## Structured Field Extraction from Receipts\n\n> Scope: this section applies ONLY when the current task supplies a receipt-style\n> image **and** declares (in its task prompt) a keyword priority list, an\n> exclusion keyword list, and\u002For a convention for how failed extractions are\n> written back. It is a post-OCR text-processing overlay. It does NOT change the\n> generic OCR flow above, the JSON output schema, or any guidance for\n> non-receipt documents. Do not import these rules into `openai-vision`, `pdf`,\n> `video-frame-extraction`, or into `xlsx` guidance for unrelated tasks.\n>\n> Negative boundary: never fabricate a value, never fold receipt-specific hints\n> (currency tokens, locale date orderings, keyword tiers) into any other skill,\n> and never hard-code expected outputs from a particular task.\n\nReceipt OCR failures almost always sit in the layer **between** raw Tesseract\noutput and the final written field, not inside Tesseract itself. The mechanisms\nbelow are reusable anchors that compose on top of (not instead of) the\nMulti-Pass OCR Strategy.\n\n### Mechanism anchor: receipt_total_keyword_priority_disambiguation\n\nWhen the task prompt supplies a priority list of total-keywords (e.g.\n`GRAND TOTAL > TOTAL RM \u002F TOTAL: RM > TOTAL AMOUNT > TOTAL \u002F AMOUNT \u002F TOTAL DUE \u002F AMOUNT DUE \u002F BALANCE DUE \u002F NETT TOTAL \u002F NET TOTAL`)\nand an exclusion list (e.g. `SUBTOTAL`, `SUB TOTAL`, `TAX`, `GST`, `SST`,\n`DISCOUNT`, `CHANGE`, `CASH TENDERED`), use **exactly those tiers and exclusions\nas given by the task** – do not reorder, drop, or silently extend them. Then\napply this deterministic selection procedure to the raw OCR text:\n\n1. **Normalize once.** Split OCR output into non-empty lines. For keyword\n   matching only, build a canonicalized copy of each line (uppercase, collapse\n   whitespace, strip trailing punctuation). Numeric extraction still runs on\n   the original line.\n2. **Detect a summary-section cutoff.** Scan lines in order; if a line matches\n   `GST\\s*SUMMARY`, `TAX\\s*SUMMARY`, `SST\\s*SUMMARY`, or a tax-table column\n   header such as `Amount\\s*\\(RM\\)\\s*Tax\\s*\\(RM\\)` \u002F `Tax\\s*Code`, record its\n   index. All lines **after** that index are ineligible as total candidates\n   (they are part of a tax breakdown table and reuse words like \"Total\" or\n   \"Amount\" as column labels).\n3. **Walk the tiers in the given priority order.** For each tier:\n   a. Collect every eligible (pre-cutoff) line whose canonicalized form\n      contains a tier keyword and does **not** contain any exclusion keyword.\n   b. Also exclude lines that are tabular column headers – i.e. lines in which\n      a tier keyword co-occurs with other column tokens such as\n      `DESCRIPTION`, `ITEM`, `QTY`, `U.PRICE`, `UNIT PRICE`, `PRICE` (these\n      precede the first line item, not the summary total).\n   c. Within the surviving candidates, iterate from last to first and accept\n      the first line whose own text yields a parseable monetary token (see\n      `monetary_two_decimal_invariant`).\n4. **Guarded split-line fallback.** Only when the chosen keyword line contains\n   **zero digits** may you consult the *immediately following* non-empty line\n   for its last monetary token. The fallback line itself must (a) be\n   pre-cutoff, (b) not match any exclusion keyword, and (c) not match a\n   payment\u002Ftender keyword: `CASH`, `CASH RECEIVED`, `CASH TENDERED`,\n   `TENDERED`, `PAYMENT`, `PAID`, `RECEIVED`, `CHANGE`, `VISA`, `MASTERCARD`,\n   `MASTER`, `CARD`, `CREDIT`, `DEBIT`. If the keyword line contains digits\n   that failed numeric parsing, that is an OCR corruption, not a line split –\n   do **not** fall through; back off to an earlier same-tier candidate\n   instead.\n5. **Tier fall-through only on empty tier.** Only move to the next priority\n   tier when the current tier yields no accepted candidate. Never let a lower\n   tier override a successful higher-tier match.\n\n### Mechanism anchor: monetary_two_decimal_invariant\n\nReceipt totals are printed with exactly two fractional digits. Treat this as a\nstructural invariant on the monetary-token regex used for total extraction\n(not for prices, quantities, dates, IDs, or any other numeric field):\n\n- Primary pattern: `(?\u003C![\\w.])(\\d{1,3}(?:,\\d{3})+\\.\\d{2}|\\d+\\.\\d{2})(?![\\w.])`.\n- A one-decimal match (`88.1`) is treated as an OCR truncation artifact and\n  rejected in favor of another candidate in the same tier. Do not silently\n  pad a one-decimal value to two decimals.\n- Normalize thousands separators (`1,234.56` → `1234.56`) before formatting\n  the final written string to `\"{:.2f}\"`.\n\n### Mechanism anchor: ocr_amount_normalization_and_arithmetic_crosscheck\n\nTesseract regularly substitutes letters for digits inside the amount token\n(`O\u002Fo\u002FQ → 0`, `l\u002FI\u002F| → 1`, `S → 5`, `B → 8`, `Z → 2`, `u → 0`, `G → 6`). Apply\nconfusable substitution **only** inside tokens that are already dominated by\ndigits and decimal points **and** sit on a line that matched a total\u002Famount\ntier keyword; never apply it to free text, product names, invoice IDs, phone\nnumbers, GST IDs, or dates. After substitution, re-apply the two-decimal\nregex. If both subtotal and tax are confidently parsed on the same receipt,\nprefer a candidate consistent with `subtotal + tax − discount ± rounding`\nover an outlier that differs by exactly one leading-digit substitution; skip\nthe arithmetic check when subtotal\u002Ftax are not both reliably present.\n\n### Mechanism anchor: multi_variant_consensus_over_tier_priority\n\nWhen the Multi-Pass OCR Strategy produces several candidate `(tier, value,\nanchor_type)` tuples for the same receipt, reconcile them before writing:\n\n1. Rank candidates by `(tier_rank, corroborated_support, cross_variant_support,\n   position)` in that order.\n2. **Corroborated support** = number of *other* total-adjacent lines in the\n   same variant whose parsed value matches the candidate within rounding\n   tolerance (e.g. a matching `Subtotal`, a matching `Payment`, or\n   `Cash − Change`). A single candidate corroborated by another non-tier\n   anchor outranks a higher cross-variant frequency with no corroboration.\n3. **Cross-variant support** is used only to break ties among equally\n   corroborated candidates at the same tier.\n4. Never use consensus voting to override a strictly higher-priority tier when\n   the higher tier has a cleanly anchored match; never use it to pick numbers\n   that did not match any tier keyword.\n\n### Mechanism anchor: conditional_multi_pass_ocr_on_noisy_numeric_fields\n\nThe Multi-Pass OCR Strategy section above is additive: you do not need to run\nall variants on every image. Trigger an extra OCR pass only when the chosen\ntotal line contains non-`[0-9.,]` characters inside the numeric span, the\nmonetary regex fails on every tier, or no date candidate passes validation. A\nreasonable receipt-tuned fallback set is grayscale + `ImageOps.autocontrast`\nat 1x, 2x, and 3x upscales combined with `--psm 6` and `--psm 4`. This is a\nfallback, not a default: do not run exhaustive variant sweeps unconditionally\non large batches.\n\n### Mechanism anchor: receipt_date_label_binding_and_locale\n\nReceipts often contain more than one date-like token (transaction date, print\ntimestamp, closing date, loyalty\u002Fpromo date). Bind the extracted date to a\nlabel and locale rather than taking the first regex match:\n\n1. Prefer lines whose canonicalized text contains `DATE`, `INVOICE DATE`, or\n   OCR variants such as `DATA` \u002F `DAIE` (collapse to `DATE` after\n   normalization), and that are **not** preceded by `PRINT`, `CLOSED`,\n   `CLOSING`, `FRN`, `LOYALTY`, or `PROMO`.\n2. If the task prompt declares a locale (e.g. Malaysian receipts: DD\u002FMM\u002FYYYY\n   primary, DD\u002FMM\u002FYY and DD-MM-YY secondary), parse under that convention\n   first. In the absence of a declared locale, do not assume one.\n3. When a batch of related receipts is processed together, infer the\n   dominant day\u002Fmonth ordering from unambiguous rows (any row whose first\n   component exceeds 12 fixes the ordering) and apply it to ambiguous rows\n   where both components are ≤12. Never override an unambiguous row based on\n   batch majority.\n4. Emit the chosen date in ISO 8601 (`YYYY-MM-DD`).\n\n### Mechanism anchor: receipt_date_lenient_parse\n\nIf the strict date regex fails, run a bounded salvage pass before giving up:\n\n- Allow `I`, `l`, `|` as separator or digit substitutes near a `DATE` label.\n- Accept an 8-digit run adjacent to a date label and segment it as `DDMMYYYY`\n  (or `YYYYMMDD` when the first four digits form a plausible year), only if\n  the resulting `(day, month, year)` triple is calendar-valid.\n- For a date-shaped token with exactly one out-of-range component, enumerate\n  single-character substitutions from the OCR confusables table and accept a\n  repair only if (a) exactly one candidate is calendar-valid and (b) its\n  year\u002Fmonth\u002Fday fall in a plausible receipt range.\n- Do not use lenient parsing to fabricate a date when no date-shaped token\n  exists, and do not apply 8-digit segmentation to numeric IDs or phone\n  numbers that happen to be eight digits long.\n\n### Mechanism anchor: null_vs_empty_string_contract\n\nWhen the task contract declares that a failed extraction must be written as\n`null`, the writeback must distinguish \"no value\" from \"empty value\":\n\n- Return Python `None` from the extraction function on failure, never `\"\"`,\n  `\"None\"`, `\"nan\"`, or a formatted string.\n- Pass `None` directly into `openpyxl` cell \u002F pandas DataFrame cell\n  assignments; do not route it through `f\"{value}\"` or `str(value)`.\n- Do not generalize this to tasks whose contract uses `\"\"` or a specific\n  placeholder; the rule is scoped to the current receipt-extraction task\n  family. See `xlsx\u002FSKILL.md` for the writeback-side companion note.\n\n### Mechanism anchor: xlsx_delivery_contract_self_check (cross-reference)\n\nAfter writing the final workbook for a receipt-extraction task, reload it\nwith `openpyxl.load_workbook(path, data_only=True)` and assert the task's\ndeclared schema (sheet name, column order, row ordering, per-cell type)\nbefore terminating. Verify that cells intended as `null` satisfy\n`cell.value is None` – not `cell.value == \"\"`. See `xlsx\u002FSKILL.md`\n\"Preserving null vs empty string on writeback\" for the generic form.\n\n### Workflow note: batch-diagnostic-then-batch-edit\n\nFor batch OCR extraction over N images, prefer a single diagnostic pass that\ndumps, per image, the per-variant OCR slice around keywords, the candidate\n`(tier, value, anchor)` list, and the current prediction. Then plan a small\nconsolidated set of pattern\u002Fvariant edits that address several images\ntogether, and re-run once. Avoid an iteration-per-image interactive debug\nloop; it exhausts iteration budgets without converging the heuristics.\n\n\n## Error Handling\n\n### Common Issues and Solutions\n\n**Issue**: Tesseract not found\n\n```python\n# Verify Tesseract is installed\ntry:\n    pytesseract.get_tesseract_version()\nexcept pytesseract.TesseractNotFoundError:\n    print(\"Tesseract is not installed or not in PATH\")\n```\n\n**Issue**: Poor OCR quality\n\n- Preprocess image (grayscale, contrast, sharpen)\n- Use appropriate PSM mode for the document type\n- Ensure image resolution is sufficient (300+ DPI)\n\n**Issue**: Empty or garbage output\n\n- Check if image contains actual text\n- Try different PSM modes\n- Verify image is not corrupted\n\n\n## Quality Self-Check\n\nBefore returning results, verify:\n\n- [ ] Output is valid JSON (use `json.loads()` to validate)\n- [ ] All required fields are present (`success`, `filename`, `extracted_text`, `confidence`, `metadata`)\n- [ ] Text preserves logical reading order\n- [ ] Confidence level reflects actual OCR quality\n- [ ] Warnings array includes all detected issues\n- [ ] Special characters are properly escaped in JSON\n- [ ] For receipt-extraction tasks: the chosen total line matched a declared\n      priority tier (not a tabular column header, not a post-summary line);\n      the amount passes the two-decimal invariant; failed extractions are\n      emitted as Python `None` rather than `\"\"`.\n\n\n## Limitations\n\n- Tesseract works best with printed text; handwriting recognition is limited\n- Accuracy decreases with decorative fonts, artistic text, or extreme stylization\n- Mathematical equations and special notation may not extract accurately\n- Redacted or watermarked text cannot be recovered\n- Severe image degradation (blur, noise, low resolution) reduces accuracy\n- Complex multi-column layouts may require custom PSM configuration\n\n\n## Version History\n\n- **1.0.0** (2026-01-13): Initial release with Tesseract\u002Fpytesseract OCR\n- **1.1.0**: Added \"Structured Field Extraction from Receipts\" overlay with\n  bounded mechanism anchors for keyword-priority total selection, two-decimal\n  monetary invariant, OCR amount normalization, multi-variant consensus,\n  label-bound date extraction, lenient date salvage, and the\n  null-vs-empty-string writeback contract. Overlay is scoped to receipt-style\n  OCR tasks that declare their own keyword\u002Fexclusion\u002Fnull conventions and\n  does not alter generic OCR behavior.\n","# Image OCR Skill\n\n## Purpose\nThis skill enables accurate text extraction from image files (JPG, PNG, etc.) using Tesseract OCR via the `pytesseract` Python library. It is suitable for scanned documents, screenshots, photos of text, receipts, forms, and other visual content containing text.\n\n## When to Use\n- Extracting text from scanned documents or photos\n- Reading text from screenshots or image captures\n- Processing batch image files that contain textual information\n- Converting visual documents to machine-readable text\n- Extracting structured data from forms, receipts, or tables in images\n\n## Required Libraries\n\nThe following Python libraries are required:\n\n```python\nimport pytesseract\nfrom PIL import Image\nimport json\nimport os\n```\n\n## Input Requirements\n- **File formats**: JPG, JPEG, PNG, WEBP\n- **Image quality**: Minimum 300 DPI recommended for printed text; clear and legible text\n- **File size**: Under 5MB per image (resize if necessary)\n- **Text language**: Specify if non-English to improve accuracy\n\n## Output Schema\nAll extracted content must be returned as valid JSON conforming to this schema:\n\n```json\n{\n  \"success\": true,\n  \"filename\": \"example.jpg\",\n  \"extracted_text\": \"Full raw text extracted from the image...\",\n  \"confidence\": \"high|medium|low\",\n  \"metadata\": {\n    \"language_detected\": \"en\",\n    \"text_regions\": 3,\n    \"has_tables\": false,\n    \"has_handwriting\": false\n  },\n  \"warnings\": [\n    \"Text partially obscured in bottom-right corner\",\n    \"Low contrast detected in header section\"\n  ]\n}\n```\n\n\n### Field Descriptions\n\n- `success`: Boolean indicating whether text extraction completed\n- `filename`: Original image filename\n- `extracted_text`: Complete text content in reading order (top-to-bottom, left-to-right)\n- `confidence`: Overall OCR confidence level based on image quality and text clarity\n- `metadata.language_detected`: ISO 639-1 language code\n- `metadata.text_regions`: Number of distinct text blocks identified\n- `metadata.has_tables`: Whether tabular data structures were detected\n- `metadata.has_handwriting`: Whether handwritten text was detected\n- `warnings`: Array of quality issues or potential errors\n\n\n## Code Examples\n\n### Basic OCR Extraction\n\n```python\nimport pytesseract\nfrom PIL import Image\n\ndef extract_text_from_image(image_path):\n    \"\"\"Extract text from a single image using Tesseract OCR.\"\"\"\n    img = Image.open(image_path)\n    text = pytesseract.image_to_string(img)\n    return text.strip()\n```\n\n### OCR with Confidence Data\n\n```python\nimport pytesseract\nfrom PIL import Image\n\ndef extract_with_confidence(image_path):\n    \"\"\"Extract text with per-word confidence scores.\"\"\"\n    img = Image.open(image_path)\n\n    # Get detailed OCR data including confidence\n    data = pytesseract.image_to_data(img, output_type=pytesseract.Output.DICT)\n\n    words = []\n    confidences = []\n\n    for i, word in enumerate(data['text']):\n        if word.strip():  # Skip empty strings\n            words.append(word)\n            confidences.append(data['conf'][i])\n\n    # Calculate average confidence\n    avg_confidence = sum(c for c in confidences if c > 0) \u002F len([c for c in confidences if c > 0]) if confidences else 0\n\n    return {\n        'text': ' '.join(words),\n        'average_confidence': avg_confidence,\n        'word_count': len(words)\n    }\n```\n\n### Full OCR with JSON Output\n\n```python\nimport pytesseract\nfrom PIL import Image\nimport json\nimport os\n\ndef ocr_to_json(image_path):\n    \"\"\"Perform OCR and return results as JSON.\"\"\"\n    filename = os.path.basename(image_path)\n    warnings = []\n\n    try:\n        img = Image.open(image_path)\n\n        # Get detailed OCR data\n        data = pytesseract.image_to_data(img, output_type=pytesseract.Output.DICT)\n\n        # Extract text preserving structure\n        text = pytesseract.image_to_string(img)\n\n        # Calculate confidence\n        confidences = [c for c in data['conf'] if c > 0]\n        avg_conf = sum(confidences) \u002F len(confidences) if confidences else 0\n\n        # Determine confidence level\n        if avg_conf >= 80:\n            confidence = \"high\"\n        elif avg_conf >= 50:\n            confidence = \"medium\"\n        else:\n            confidence = \"low\"\n            warnings.append(f\"Low OCR confidence: {avg_conf:.1f}%\")\n\n        # Count text regions (blocks)\n        block_nums = set(data['block_num'])\n        text_regions = len([b for b in block_nums if b > 0])\n\n        result = {\n            \"success\": True,\n            \"filename\": filename,\n            \"extracted_text\": text.strip(),\n            \"confidence\": confidence,\n            \"metadata\": {\n                \"language_detected\": \"en\",\n                \"text_regions\": text_regions,\n                \"has_tables\": False,\n                \"has_handwriting\": False\n            },\n            \"warnings\": warnings\n        }\n\n    except Exception as e:\n        result = {\n            \"success\": False,\n            \"filename\": filename,\n            \"extracted_text\": \"\",\n            \"confidence\": \"low\",\n            \"metadata\": {\n                \"language_detected\": \"unknown\",\n                \"text_regions\": 0,\n                \"has_tables\": False,\n                \"has_handwriting\": False\n            },\n            \"warnings\": [f\"OCR failed: {str(e)}\"]\n        }\n\n    return result\n\n# Usage\nresult = ocr_to_json(\"document.jpg\")\nprint(json.dumps(result, indent=2))\n```\n\n### Batch Processing Multiple Images\n\n```python\nimport pytesseract\nfrom PIL import Image\nimport json\nimport os\nfrom pathlib import Path\n\ndef process_image_directory(directory_path, output_file):\n    \"\"\"Process all images in a directory and save results.\"\"\"\n    image_extensions = {'.jpg', '.jpeg', '.png', '.webp'}\n    results = []\n\n    for file_path in sorted(Path(directory_path).iterdir()):\n        if file_path.suffix.lower() in image_extensions:\n            result = ocr_to_json(str(file_path))\n            results.append(result)\n            print(f\"Processed: {file_path.name}\")\n\n    # Save results\n    with open(output_file, 'w') as f:\n        json.dump(results, f, indent=2)\n\n    return results\n```\n\n\n## Tesseract Configuration Options\n\n### Language Selection\n\n```python\n# Specify language (default is English)\ntext = pytesseract.image_to_string(img, lang='eng')\n\n# Multiple languages\ntext = pytesseract.image_to_string(img, lang='eng+fra+deu')\n```\n\n### Page Segmentation Modes (PSM)\n\nUse `--psm` to control how Tesseract segments the image:\n\n```python\n# PSM 3: Fully automatic page segmentation (default)\ntext = pytesseract.image_to_string(img, config='--psm 3')\n\n# PSM 4: Assume single column of text\ntext = pytesseract.image_to_string(img, config='--psm 4')\n\n# PSM 6: Assume uniform block of text\ntext = pytesseract.image_to_string(img, config='--psm 6')\n\n# PSM 11: Sparse text - find as much text as possible\ntext = pytesseract.image_to_string(img, config='--psm 11')\n```\n\nCommon PSM values:\n- `0`: Orientation and script detection (OSD) only\n- `3`: Fully automatic page segmentation (default)\n- `4`: Single column of text of variable sizes\n- `6`: Uniform block of text\n- `7`: Single text line\n- `11`: Sparse text\n- `13`: Raw line\n\n\n## Image Preprocessing\n\nFor better OCR accuracy, preprocess images:\n\n```python\nfrom PIL import Image, ImageFilter, ImageOps\n\ndef preprocess_image(image_path):\n    \"\"\"Preprocess image for better OCR results.\"\"\"\n    img = Image.open(image_path)\n\n    # Convert to grayscale\n    img = img.convert('L')\n\n    # Increase contrast\n    img = ImageOps.autocontrast(img)\n\n    # Apply slight sharpening\n    img = img.filter(ImageFilter.SHARPEN)\n\n    return img\n\n# Use preprocessed image for OCR\nimg = preprocess_image(\"document.jpg\")\ntext = pytesseract.image_to_string(img)\n```\n\n### Advanced Preprocessing Strategies\n\nFor difficult images (low contrast, faded text, dark backgrounds), try multiple preprocessing approaches:\n\n1. **Grayscale + Autocontrast** - Basic enhancement for most images\n2. **Inverted** - Use `ImageOps.invert()` for dark backgrounds with light text\n3. **Scaling** - Upscale small images (e.g., 2x or 3x) before OCR to improve character recognition\n4. **Thresholding** - Convert to binary using `img.point(lambda p: 255 if p > threshold else 0)` with different threshold values (e.g., 100, 128)\n5. **Sharpening** - Apply `ImageFilter.SHARPEN` to improve edge clarity\n\n\n## Multi-Pass OCR Strategy\n\nFor challenging images, a single OCR pass may miss text. Use multiple passes with different configurations:\n\n1. **Try multiple PSM modes** - Different page segmentation modes work better for different layouts (e.g., `--psm 6` for blocks, `--psm 4` for columns, `--psm 11` for sparse text)\n\n2. **Try multiple preprocessing variants** - Run OCR on several preprocessed versions of the same image\n\n3. **Combine results** - Aggregate text from all passes to maximize extraction coverage\n\n```python\ndef multi_pass_ocr(image_path):\n    \"\"\"Run OCR with multiple strategies and combine results.\"\"\"\n    img = Image.open(image_path)\n    gray = ImageOps.grayscale(img)\n\n    # Generate preprocessing variants\n    variants = [\n        ImageOps.autocontrast(gray),\n        ImageOps.invert(ImageOps.autocontrast(gray)),\n        gray.filter(ImageFilter.SHARPEN),\n    ]\n\n    # PSM modes to try\n    psm_modes = ['--psm 6', '--psm 4', '--psm 11']\n\n    all_text = []\n    for variant in variants:\n        for psm in psm_modes:\n            try:\n                text = pytesseract.image_to_string(variant, config=psm)\n                if text.strip():\n                    all_text.append(text)\n            except Exception:\n                pass\n\n    # Combine all extracted text\n    return \"\\n\".join(all_text)\n```\n\nThis approach improves extraction for receipts, faded documents, and images with varying quality.\n\n\n## Structured Field Extraction from Receipts\n\n> Scope: this section applies ONLY when the current task supplies a receipt-style\n> image **and** declares (in its task prompt) a keyword priority list, an\n> exclusion keyword list, and\u002For a convention for how failed extractions are\n> written back. It is a post-OCR text-processing overlay. It does NOT change the\n> generic OCR flow above, the JSON output schema, or any guidance for\n> non-receipt documents. Do not import these rules into `openai-vision`, `pdf`,\n> `video-frame-extraction`, or into `xlsx` guidance for unrelated tasks.\n>\n> Negative boundary: never fabricate a value, never fold receipt-specific hints\n> (currency tokens, locale date orderings, keyword tiers) into any other skill,\n> and never hard-code expected outputs from a particular task.\n\nReceipt OCR failures almost always sit in the layer **between** raw Tesseract\noutput and the final written field, not inside Tesseract itself. The mechanisms\nbelow are reusable anchors that compose on top of (not instead of) the\nMulti-Pass OCR Strategy.\n\n### Mechanism anchor: receipt_total_keyword_priority_disambiguation\n\nWhen the task prompt supplies a priority list of total-keywords (e.g.\n`GRAND TOTAL > TOTAL RM \u002F TOTAL: RM > TOTAL AMOUNT > TOTAL \u002F AMOUNT \u002F TOTAL DUE \u002F AMOUNT DUE \u002F BALANCE DUE \u002F NETT TOTAL \u002F NET TOTAL`)\nand an exclusion list (e.g. `SUBTOTAL`, `SUB TOTAL`, `TAX`, `GST`, `SST`,\n`DISCOUNT`, `CHANGE`, `CASH TENDERED`), use **exactly those tiers and exclusions\nas given by the task** – do not reorder, drop, or silently extend them. Then\napply this deterministic selection procedure to the raw OCR text:\n\n1. **Normalize once.** Split OCR output into non-empty lines. For keyword\n   matching only, build a canonicalized copy of each line (uppercase, collapse\n   whitespace, strip trailing punctuation). Numeric extraction still runs on\n   the original line.\n2. **Detect a summary-section cutoff.** Scan lines in order; if a line matches\n   `GST\\s*SUMMARY`, `TAX\\s*SUMMARY`, `SST\\s*SUMMARY`, or a tax-table column\n   header such as `Amount\\s*\\(RM\\)\\s*Tax\\s*\\(RM\\)` \u002F `Tax\\s*Code`, record its\n   index. All lines **after** that index are ineligible as total candidates\n   (they are part of a tax breakdown table and reuse words like \"Total\" or\n   \"Amount\" as column labels).\n3. **Walk the tiers in the given priority order.** For each tier:\n   a. Collect every eligible (pre-cutoff) line whose canonicalized form\n      contains a tier keyword and does **not** contain any exclusion keyword.\n   b. Also exclude lines that are tabular column headers – i.e. lines in which\n      a tier keyword co-occurs with other column tokens such as\n      `DESCRIPTION`, `ITEM`, `QTY`, `U.PRICE`, `UNIT PRICE`, `PRICE` (these\n      precede the first line item, not the summary total).\n   c. Within the surviving candidates, iterate from last to first and accept\n      the first line whose own text yields a parseable monetary token (see\n      `monetary_two_decimal_invariant`).\n4. **Guarded split-line fallback.** Only when the chosen keyword line contains\n   **zero digits** may you consult the *immediately following* non-empty line\n   for its last monetary token. The fallback line itself must (a) be\n   pre-cutoff, (b) not match any exclusion keyword, and (c) not match a\n   payment\u002Ftender keyword: `CASH`, `CASH RECEIVED`, `CASH TENDERED`,\n   `TENDERED`, `PAYMENT`, `PAID`, `RECEIVED`, `CHANGE`, `VISA`, `MASTERCARD`,\n   `MASTER`, `CARD`, `CREDIT`, `DEBIT`. If the keyword line contains digits\n   that failed numeric parsing, that is an OCR corruption, not a line split –\n   do **not** fall through; back off to an earlier same-tier candidate\n   instead.\n5. **Tier fall-through only on empty tier.** Only move to the next priority\n   tier when the current tier yields no accepted candidate. Never let a lower\n   tier override a successful higher-tier match.\n\n### Mechanism anchor: monetary_two_decimal_invariant\n\nReceipt totals are printed with exactly two fractional digits. Treat this as a\nstructural invariant on the monetary-token regex used for total extraction\n(not for prices, quantities, dates, IDs, or any other numeric field):\n\n- Primary pattern: `(?\u003C![\\w.])(\\d{1,3}(?:,\\d{3})+\\.\\d{2}|\\d+\\.\\d{2})(?![\\w.])`.\n- A one-decimal match (`88.1`) is treated as an OCR truncation artifact and\n  rejected in favor of another candidate in the same tier. Do not silently\n  pad a one-decimal value to two decimals.\n- Normalize thousands separators (`1,234.56` → `1234.56`) before formatting\n  the final written string to `\"{:.2f}\"`.\n\n### Mechanism anchor: ocr_amount_normalization_and_arithmetic_crosscheck\n\nTesseract regularly substitutes letters for digits inside the amount token\n(`O\u002Fo\u002FQ → 0`, `l\u002FI\u002F| → 1`, `S → 5`, `B → 8`, `Z → 2`, `u → 0`, `G → 6`). Apply\nconfusable substitution **only** inside tokens that are already dominated by\ndigits and decimal points **and** sit on a line that matched a total\u002Famount\ntier keyword; never apply it to free text, product names, invoice IDs, phone\nnumbers, GST IDs, or dates. After substitution, re-apply the two-decimal\nregex. If both subtotal and tax are confidently parsed on the same receipt,\nprefer a candidate consistent with `subtotal + tax − discount ± rounding`\nover an outlier that differs by exactly one leading-digit substitution; skip\nthe arithmetic check when subtotal\u002Ftax are not both reliably present.\n\n### Mechanism anchor: multi_variant_consensus_over_tier_priority\n\nWhen the Multi-Pass OCR Strategy produces several candidate `(tier, value,\nanchor_type)` tuples for the same receipt, reconcile them before writing:\n\n1. Rank candidates by `(tier_rank, corroborated_support, cross_variant_support,\n   position)` in that order.\n2. **Corroborated support** = number of *other* total-adjacent lines in the\n   same variant whose parsed value matches the candidate within rounding\n   tolerance (e.g. a matching `Subtotal`, a matching `Payment`, or\n   `Cash − Change`). A single candidate corroborated by another non-tier\n   anchor outranks a higher cross-variant frequency with no corroboration.\n3. **Cross-variant support** is used only to break ties among equally\n   corroborated candidates at the same tier.\n4. Never use consensus voting to override a strictly higher-priority tier when\n   the higher tier has a cleanly anchored match; never use it to pick numbers\n   that did not match any tier keyword.\n\n### Mechanism anchor: conditional_multi_pass_ocr_on_noisy_numeric_fields\n\nThe Multi-Pass OCR Strategy section above is additive: you do not need to run\nall variants on every image. Trigger an extra OCR pass only when the chosen\ntotal line contains non-`[0-9.,]` characters inside the numeric span, the\nmonetary regex fails on every tier, or no date candidate passes validation. A\nreasonable receipt-tuned fallback set is grayscale + `ImageOps.autocontrast`\nat 1x, 2x, and 3x upscales combined with `--psm 6` and `--psm 4`. This is a\nfallback, not a default: do not run exhaustive variant sweeps unconditionally\non large batches.\n\n### Mechanism anchor: receipt_date_label_binding_and_locale\n\nReceipts often contain more than one date-like token (transaction date, print\ntimestamp, closing date, loyalty\u002Fpromo date). Bind the extracted date to a\nlabel and locale rather than taking the first regex match:\n\n1. Prefer lines whose canonicalized text contains `DATE`, `INVOICE DATE`, or\n   OCR variants such as `DATA` \u002F `DAIE` (collapse to `DATE` after\n   normalization), and that are **not** preceded by `PRINT`, `CLOSED`,\n   `CLOSING`, `FRN`, `LOYALTY`, or `PROMO`.\n2. If the task prompt declares a locale (e.g. Malaysian receipts: DD\u002FMM\u002FYYYY\n   primary, DD\u002FMM\u002FYY and DD-MM-YY secondary), parse under that convention\n   first. In the absence of a declared locale, do not assume one.\n3. When a batch of related receipts is processed together, infer the\n   dominant day\u002Fmonth ordering from unambiguous rows (any row whose first\n   component exceeds 12 fixes the ordering) and apply it to ambiguous rows\n   where both components are ≤12. Never override an unambiguous row based on\n   batch majority.\n4. Emit the chosen date in ISO 8601 (`YYYY-MM-DD`).\n\n### Mechanism anchor: receipt_date_lenient_parse\n\nIf the strict date regex fails, run a bounded salvage pass before giving up:\n\n- Allow `I`, `l`, `|` as separator or digit substitutes near a `DATE` label.\n- Accept an 8-digit run adjacent to a date label and segment it as `DDMMYYYY`\n  (or `YYYYMMDD` when the first four digits form a plausible year), only if\n  the resulting `(day, month, year)` triple is calendar-valid.\n- For a date-shaped token with exactly one out-of-range component, enumerate\n  single-character substitutions from the OCR confusables table and accept a\n  repair only if (a) exactly one candidate is calendar-valid and (b) its\n  year\u002Fmonth\u002Fday fall in a plausible receipt range.\n- Do not use lenient parsing to fabricate a date when no date-shaped token\n  exists, and do not apply 8-digit segmentation to numeric IDs or phone\n  numbers that happen to be eight digits long.\n\n### Mechanism anchor: null_vs_empty_string_contract\n\nWhen the task contract declares that a failed extraction must be written as\n`null`, the writeback must distinguish \"no value\" from \"empty value\":\n\n- Return Python `None` from the extraction function on failure, never `\"\"`,\n  `\"None\"`, `\"nan\"`, or a formatted string.\n- Pass `None` directly into `openpyxl` cell \u002F pandas DataFrame cell\n  assignments; do not route it through `f\"{value}\"` or `str(value)`.\n- Do not generalize this to tasks whose contract uses `\"\"` or a specific\n  placeholder; the rule is scoped to the current receipt-extraction task\n  family. See `xlsx\u002FSKILL.md` for the writeback-side companion note.\n\n### Mechanism anchor: xlsx_delivery_contract_self_check (cross-reference)\n\nAfter writing the final workbook for a receipt-extraction task, reload it\nwith `openpyxl.load_workbook(path, data_only=True)` and assert the task's\ndeclared schema (sheet name, column order, row ordering, per-cell type)\nbefore terminating. Verify that cells intended as `null` satisfy\n`cell.value is None` – not `cell.value == \"\"`. See `xlsx\u002FSKILL.md`\n\"Preserving null vs empty string on writeback\" for the generic form.\n\n### Workflow note: batch-diagnostic-then-batch-edit\n\nFor batch OCR extraction over N images, prefer a single diagnostic pass that\ndumps, per image, the per-variant OCR slice around keywords, the candidate\n`(tier, value, anchor)` list, and the current prediction. Then plan a small\nconsolidated set of pattern\u002Fvariant edits that address several images\ntogether, and re-run once. Avoid an iteration-per-image interactive debug\nloop; it exhausts iteration budgets without converging the heuristics.\n\n\n## Error Handling\n\n### Common Issues and Solutions\n\n**Issue**: Tesseract not found\n\n```python\n# Verify Tesseract is installed\ntry:\n    pytesseract.get_tesseract_version()\nexcept pytesseract.TesseractNotFoundError:\n    print(\"Tesseract is not installed or not in PATH\")\n```\n\n**Issue**: Poor OCR quality\n\n- Preprocess image (grayscale, contrast, sharpen)\n- Use appropriate PSM mode for the document type\n- Ensure image resolution is sufficient (300+ DPI)\n\n**Issue**: Empty or garbage output\n\n- Check if image contains actual text\n- Try different PSM modes\n- Verify image is not corrupted\n\n\n## Quality Self-Check\n\nBefore returning results, verify:\n\n- [ ] Output is valid JSON (use `json.loads()` to validate)\n- [ ] All required fields are present (`success`, `filename`, `extracted_text`, `confidence`, `metadata`)\n- [ ] Text preserves logical reading order\n- [ ] Confidence level reflects actual OCR quality\n- [ ] Warnings array includes all detected issues\n- [ ] Special characters are properly escaped in JSON\n- [ ] For receipt-extraction tasks: the chosen total line matched a declared\n      priority tier (not a tabular column header, not a post-summary line);\n      the amount passes the two-decimal invariant; failed extractions are\n      emitted as Python `None` rather than `\"\"`.\n\n\n## Limitations\n\n- Tesseract works best with printed text; handwriting recognition is limited\n- Accuracy decreases with decorative fonts, artistic text, or extreme stylization\n- Mathematical equations and special notation may not extract accurately\n- Redacted or watermarked text cannot be recovered\n- Severe image degradation (blur, noise, low resolution) reduces accuracy\n- Complex multi-column layouts may require custom PSM configuration\n\n\n## Version History\n\n- **1.0.0** (2026-01-13): Initial release with Tesseract\u002Fpytesseract OCR\n- **1.1.0**: Added \"Structured Field Extraction from Receipts\" overlay with\n  bounded mechanism anchors for keyword-priority total selection, two-decimal\n  monetary invariant, OCR amount normalization, multi-variant consensus,\n  label-bound date extraction, lenient date salvage, and the\n  null-vs-empty-string writeback contract. Overlay is scoped to receipt-style\n  OCR tasks that declare their own keyword\u002Fexclusion\u002Fnull conventions and\n  does not alter generic OCR behavior.\n",{"name":5,"description":7},"main","cdcb6d230f3b15c3c33f8a2bdd16439f43602cbe","jpg-ocr-stat\u002Fresult\u002Fwml-claude4.7\u002Fskills\u002Fimage-ocr",1,"https:\u002F\u002Fgithub.com\u002Flinyouxue\u002Fskill-repair-benchmark","https:\u002F\u002Fgithub.com\u002Flinyouxue","2026-11-14 20:28:35",{"source":8,"skillName":5,"commands":32,"prompt":42,"zipAvailable":43},[33,36,39],{"tool":34,"command":35},"npx","npx skills add linyouxue\u002Fskill-repair-benchmark --skill image-ocr",{"tool":37,"command":38},"bunx","bunx skills add linyouxue\u002Fskill-repair-benchmark --skill image-ocr",{"tool":40,"command":41},"pnpm","pnpm dlx skills add linyouxue\u002Fskill-repair-benchmark --skill image-ocr","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Flinyouxue\u002Fskill-repair-benchmark\u002Fblob\u002Fmain\u002Fjpg-ocr-stat\u002Fresult\u002Fwml-claude4.7\u002Fskills\u002Fimage-ocr\u002FSKILL.md",true,[],[46,53,60,66,71,76],{"skillNo":47,"name":48,"nameZh":6,"description":49,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":50,"categories":51,"keywords":52,"publishedAt":19},"01M7E1HGVRY73NBJ022CJG13X6","video-frame-extraction","Extract frames from video files and save them as images using OpenCV","https:\u002F\u002Fgithub.com\u002Flinyouxue\u002Fskill-repair-benchmark\u002Fblob\u002Fmain\u002Fjpg-ocr-stat\u002Fresult\u002Fskilltta-claude4.7\u002Fgroup_03\u002Frun_03\u002Finputs\u002Fskills\u002Fvideo-frame-extraction\u002FSKILL.md",[],[],{"skillNo":54,"name":55,"nameZh":6,"description":56,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":57,"categories":58,"keywords":59,"publishedAt":19},"01M7E1HAG5R7CJ9C8NYJS771MB","openai-vision","Analyze images and multi-frame sequences using OpenAI GPT vision models","https:\u002F\u002Fgithub.com\u002Flinyouxue\u002Fskill-repair-benchmark\u002Fblob\u002Fmain\u002Fjpg-ocr-stat\u002Fresult\u002Fskilltta-claude4.7\u002Fgroup_03\u002Frun_03\u002Finputs\u002Fskills\u002Fopenai-vision\u002FSKILL.md",[],[],{"skillNo":61,"name":5,"nameZh":6,"description":62,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":63,"categories":64,"keywords":65,"publishedAt":19},"01M7E1H7DADKB6QBWKC2KQ63SZ","Extract text content from images using Tesseract OCR via Python, including structured field extraction from receipts and forms","https:\u002F\u002Fgithub.com\u002Flinyouxue\u002Fskill-repair-benchmark\u002Fblob\u002Fmain\u002Fjpg-ocr-stat\u002Fresult\u002Fskilltta-claude4.7\u002Fgroup_03\u002Frun_03\u002Finputs\u002Fskills\u002Fimage-ocr\u002FSKILL.md",[],[],{"skillNo":67,"name":48,"nameZh":6,"description":49,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":68,"categories":69,"keywords":70,"publishedAt":19},"01M7E1H0WBNT43TENGQY7AVZHD","https:\u002F\u002Fgithub.com\u002Flinyouxue\u002Fskill-repair-benchmark\u002Fblob\u002Fmain\u002Fjpg-ocr-stat\u002Fresult\u002Fskilltta-claude4.7\u002Fgroup_03\u002Frun_02\u002Finputs\u002Fskills\u002Fvideo-frame-extraction\u002FSKILL.md",[],[],{"skillNo":72,"name":55,"nameZh":6,"description":56,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":73,"categories":74,"keywords":75,"publishedAt":19},"01M7E1GTC7D4N4Q2EF03875J9E","https:\u002F\u002Fgithub.com\u002Flinyouxue\u002Fskill-repair-benchmark\u002Fblob\u002Fmain\u002Fjpg-ocr-stat\u002Fresult\u002Fskilltta-claude4.7\u002Fgroup_03\u002Frun_02\u002Finputs\u002Fskills\u002Fopenai-vision\u002FSKILL.md",[],[],{"skillNo":77,"name":5,"nameZh":6,"description":62,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":78,"categories":79,"keywords":80,"publishedAt":19},"01M7E1GQ92CWFMGH53KSSE4WER","https:\u002F\u002Fgithub.com\u002Flinyouxue\u002Fskill-repair-benchmark\u002Fblob\u002Fmain\u002Fjpg-ocr-stat\u002Fresult\u002Fskilltta-claude4.7\u002Fgroup_03\u002Frun_02\u002Finputs\u002Fskills\u002Fimage-ocr\u002FSKILL.md",[],[]]