{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Optimized OCR Pipeline (CRNN + CTC)\n", "Efficient implementation for RTX 4060 (8GB) with 24GB RAM" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "d:\\OCR-MODEL\\myenv\\lib\\site-packages\\tqdm\\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", " from .autonotebook import tqdm as notebook_tqdm\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Device: cuda\n", "GPU: NVIDIA GeForce RTX 4060 Laptop GPU\n" ] } ], "source": [ "# Setup\n", "import os\n", "import torch\n", "import torch.nn as nn\n", "import torch.optim as optim\n", "from torch.utils.data import Dataset, DataLoader\n", "import torchvision.transforms as T\n", "from PIL import Image\n", "from tqdm.auto import tqdm\n", "from datasets import load_dataset\n", "import Levenshtein\n", "\n", "device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n", "print(f\"Device: {device}\")\n", "if torch.cuda.is_available():\n", " print(f\"GPU: {torch.cuda.get_device_name(0)}\")\n", " torch.backends.cudnn.benchmark = True\n", " torch.cuda.empty_cache()" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "# Character encoding\n", "CHARS = \"0123456789abcdefghijklmnopqrstuvwxyz\"\n", "BLANK = 0\n", "char2idx = {c: i+1 for i, c in enumerate(CHARS)}\n", "idx2char = {i+1: c for i, c in enumerate(CHARS)}\n", "idx2char[BLANK] = \"\"\n", "VOCAB_SIZE = len(CHARS) + 1\n", "\n", "def encode(text):\n", " return [char2idx[c] for c in text.lower() if c in char2idx]\n", "\n", "def decode(indices):\n", " chars, last = [], None\n", " for i in indices:\n", " if i != BLANK and i != last:\n", " chars.append(idx2char.get(i, ''))\n", " last = i\n", " return ''.join(chars)" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "# Efficient Dataset\n", "class OCRDataset(Dataset):\n", " def __init__(self, data, h=32, train=False):\n", " self.data = data\n", " self.h = h\n", " self.train = train\n", " self.normalize = T.Normalize(0.5, 0.5)\n", " if train:\n", " self.aug = T.Compose([\n", " T.RandomRotation(2, fill=255),\n", " T.ColorJitter(0.2, 0.2)\n", " ])\n", " \n", " def __len__(self):\n", " return len(self.data)\n", " \n", " def __getitem__(self, i):\n", " img = self.data[i]['image'].convert('L')\n", " if self.train and self.aug:\n", " img = self.aug(img)\n", " w, h = img.size\n", " img = img.resize((int(w * self.h / h), self.h), Image.BILINEAR)\n", " img = self.normalize(T.ToTensor()(img))\n", " return img, self.data[i]['label']\n", "\n", "def collate(batch):\n", " imgs, texts = zip(*batch)\n", " enc = [encode(t) for t in texts]\n", " valid = [i for i, e in enumerate(enc) if e]\n", " if not valid:\n", " return None\n", " imgs = [imgs[i] for i in valid]\n", " enc = [enc[i] for i in valid]\n", " texts = [texts[i] for i in valid]\n", " \n", " max_w = max(img.shape[2] for img in imgs)\n", " padded = torch.stack([nn.functional.pad(img, (0, max_w - img.shape[2])) for img in imgs])\n", " targets = torch.IntTensor([c for seq in enc for c in seq])\n", " lengths = torch.IntTensor([len(seq) for seq in enc])\n", " return padded, targets, lengths, texts" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "# Optimized CRNN Model\n", "class CRNN(nn.Module):\n", " def __init__(self, vocab_size=VOCAB_SIZE, hidden=256):\n", " super().__init__()\n", " # Efficient CNN backbone\n", " self.cnn = nn.Sequential(\n", " nn.Conv2d(1, 64, 3, 1, 1), nn.ReLU(True),\n", " nn.MaxPool2d(2, 2), # 16x\n", " \n", " nn.Conv2d(64, 128, 3, 1, 1), nn.ReLU(True),\n", " nn.MaxPool2d(2, 2), # 8x\n", " \n", " nn.Conv2d(128, 256, 3, 1, 1), nn.BatchNorm2d(256), nn.ReLU(True),\n", " nn.Conv2d(256, 256, 3, 1, 1), nn.ReLU(True),\n", " nn.MaxPool2d((2, 1)), # 4x\n", " \n", " nn.Conv2d(256, 512, 3, 1, 1), nn.BatchNorm2d(512), nn.ReLU(True),\n", " nn.MaxPool2d((2, 1)), # 2x\n", " \n", " nn.Conv2d(512, 512, 2, 1, 0), nn.BatchNorm2d(512), nn.ReLU(True)\n", " )\n", " self.rnn = nn.LSTM(512, hidden, 2, bidirectional=True, batch_first=False)\n", " self.fc = nn.Linear(hidden * 2, vocab_size)\n", " \n", " def forward(self, x):\n", " x = self.cnn(x) # (B, 512, 1, W)\n", " x = x.squeeze(2).permute(2, 0, 1) # (W, B, 512)\n", " x, _ = self.rnn(x) # (W, B, 512)\n", " return self.fc(x) # (W, B, vocab)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Loading dataset...\n", "Demo mode: 50000 samples\n", "Train batches: 391, Val batches: 79\n" ] } ], "source": [ "# Load data efficiently\n", "DEMO = True # Set False for full training\n", "SUBSET = 50000 if DEMO else None\n", "\n", "print(\"Loading dataset...\")\n", "ds = load_dataset(\"priyank-m/MJSynth_text_recognition\")\n", "\n", "if SUBSET:\n", " print(f\"Demo mode: {SUBSET} samples\")\n", " train_ds = OCRDataset(ds['train'].select(range(min(SUBSET, len(ds['train'])))), train=True)\n", " val_ds = OCRDataset(ds['val'].select(range(min(SUBSET//5, len(ds['val'])))))\n", "else:\n", " train_ds = OCRDataset(ds['train'], train=True)\n", " val_ds = OCRDataset(ds['val'])\n", "\n", "# Optimal batch size for 8GB GPU\n", "BS = 128\n", "train_loader = DataLoader(train_ds, BS, shuffle=True, collate_fn=collate, num_workers=0, pin_memory=True)\n", "val_loader = DataLoader(val_ds, BS, shuffle=False, collate_fn=collate, num_workers=0, pin_memory=True)\n", "\n", "print(f\"Train batches: {len(train_loader)}, Val batches: {len(val_loader)}\")" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "# Training setup\n", "model = CRNN().to(device)\n", "criterion = nn.CTCLoss(blank=BLANK, zero_infinity=True)\n", "optimizer = optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4)\n", "scheduler = optim.lr_scheduler.OneCycleLR(optimizer, max_lr=1e-3, \n", " steps_per_epoch=len(train_loader), \n", " epochs=30 if DEMO else 50)\n", "scaler = torch.amp.GradScaler('cuda')\n", "\n", "def train_epoch(model, loader, opt, crit, sched):\n", " model.train()\n", " total = 0\n", " for batch in tqdm(loader, leave=False):\n", " if batch is None:\n", " continue\n", " imgs, targets, t_lens, _ = batch\n", " imgs = imgs.to(device)\n", " \n", " opt.zero_grad(set_to_none=True)\n", " with torch.amp.autocast('cuda'):\n", " preds = model(imgs)\n", " i_lens = torch.full((imgs.size(0),), preds.size(0), dtype=torch.long)\n", " loss = crit(nn.functional.log_softmax(preds, 2), targets, i_lens, t_lens)\n", " \n", " if not torch.isnan(loss):\n", " scaler.scale(loss).backward()\n", " scaler.unscale_(opt)\n", " torch.nn.utils.clip_grad_norm_(model.parameters(), 5)\n", " scaler.step(opt)\n", " scaler.update()\n", " sched.step()\n", " total += loss.item()\n", " return total / len(loader)\n", "\n", "@torch.no_grad()\n", "def evaluate(model, loader):\n", " model.eval()\n", " cer, wer, chars, words = 0, 0, 0, 1\n", " for batch in tqdm(loader, leave=False):\n", " if batch is None:\n", " continue\n", " imgs, _, _, texts = batch\n", " preds = model(imgs.to(device)).argmax(2).T.cpu() # (B, W)\n", " \n", " for pred, true in zip(preds, texts):\n", " pred_str = decode(pred.tolist())\n", " cer += Levenshtein.distance(pred_str, true)\n", " wer += pred_str != true\n", " chars += len(true)\n", " words += 1\n", " return cer/chars, wer/words" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ " 0%| | 0/391 [00:00 4\u001b[0m torch\u001b[38;5;241m.\u001b[39monnx\u001b[38;5;241m.\u001b[39mexport(\u001b[43mmodel\u001b[49m, dummy, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mexport/model.onnx\u001b[39m\u001b[38;5;124m'\u001b[39m, \n\u001b[0;32m 5\u001b[0m input_names\u001b[38;5;241m=\u001b[39m[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124minput\u001b[39m\u001b[38;5;124m'\u001b[39m], output_names\u001b[38;5;241m=\u001b[39m[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124moutput\u001b[39m\u001b[38;5;124m'\u001b[39m],\n\u001b[0;32m 6\u001b[0m dynamic_axes\u001b[38;5;241m=\u001b[39m{\u001b[38;5;124m'\u001b[39m\u001b[38;5;124minput\u001b[39m\u001b[38;5;124m'\u001b[39m: {\u001b[38;5;241m0\u001b[39m: \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mbatch\u001b[39m\u001b[38;5;124m'\u001b[39m, \u001b[38;5;241m3\u001b[39m: \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mwidth\u001b[39m\u001b[38;5;124m'\u001b[39m}, \n\u001b[0;32m 7\u001b[0m \u001b[38;5;124m'\u001b[39m\u001b[38;5;124moutput\u001b[39m\u001b[38;5;124m'\u001b[39m: {\u001b[38;5;241m0\u001b[39m: \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mseq\u001b[39m\u001b[38;5;124m'\u001b[39m, \u001b[38;5;241m1\u001b[39m: \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mbatch\u001b[39m\u001b[38;5;124m'\u001b[39m}},\n\u001b[0;32m 8\u001b[0m opset_version\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m14\u001b[39m)\n\u001b[0;32m 9\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mModel exported to export/model.onnx\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", "\u001b[1;31mNameError\u001b[0m: name 'model' is not defined" ] } ], "source": [ "# Export to ONNX\n", "os.makedirs('export', exist_ok=True)\n", "dummy = torch.randn(1, 1, 32, 128).to(device)\n", "torch.onnx.export(model, dummy, 'export/model.onnx', \n", " input_names=['input'], output_names=['output'],\n", " dynamic_axes={'input': {0: 'batch', 3: 'width'}, \n", " 'output': {0: 'seq', 1: 'batch'}},\n", " opset_version=14)\n", "print(\"Model exported to export/model.onnx\")" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "C:\\Users\\Jagadeesh\\AppData\\Local\\Temp\\ipykernel_15540\\4199438618.py:7: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://github.com/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on GitHub for any issues related to this experimental feature.\n", " model.load_state_dict(torch.load(model_path, map_location=device))\n" ] }, { "data": { "image/png": "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q1y5clqb1atXa7Hk5GRXy0xISPCZbteundZmy5YtWmz79u3KNrVq1dJiffv2dfVdsnz58ht+Voq70NBQLda7d2+f6fr167ua17p167RYUlKSKkxly+rpp1KlSlqsbt26PtNxcXFam5iYGC1Wpgz9WC+2BAAAFiPRAwBgMRI9AAAWI9EDAGCxYl2M51+QJqZOnarFfv/994AFabNnzy5RxXiXLl3ymX7//fe1Ntu2bXNVoDN37lxXxXgnT570mX799de1NikpKSq/YmNjfaabN2+utYmPj1fB5P/ZGDlypNYmIyMj3/Nv3bq1FuvRo4fPdEREhNbms88+02LLli1ztcy33nrLZ7pt27YBC83E5MmTlW26d++uxXr16qXF/v33Xy323nvv+Uzv3r1blSSmwtWlS5cGLMbzeDxabMGCBVps4cKFKlhM38luC/QqVqx4w+8R8eijj2qxoUOHuireLA3o0QMAYDESPQAAFiPRAwBgsWJ9jR4lg+n6m+k6oP/ARrt27QrqNXrTMv2v0Zuux7u9fmiaf3FlqsEwDSTjlv+gQn/88YfWxrRtTYOWtGnTxtXAQ240a9bMVZ1Kfpk+G61atdJiderUUUXNNBBTjRo1inw96tWr5zPdpEkTV387phqsY8eOabEjR474TP/5559amz179mixzZs3u6rVijMMwGMbevQAAFiMRA8AgMVI9AAAWIxEDwCAxSjGQ4FFR0e7Ksy6ePGiz/TGjRtdDXzh9ilUpkFRTEVjbgrBrly5osXS09NVSTF48GAtNnDgwHzPz79IyjQozeHDh10VjPkP+JPX/NwwfTZMy8wv0wAuY8eO1WIDBgxQxYFpfQub/xPzZs2a5aqo0fREwTNnzgQcBGjKlCmuXrdq1SpXg0bNnDlTi1WoUEHZhB49AAAWI9EDAGAxEj0AABYj0QMAYDGK8VBgdevWdVXM5j+i1datW7U258+f12JVq1Z1tR6mYrCkpKSAr2vRooWr15WkYjzT6HAFGTEuLCysgGt043mZnsRWXJmK/YK5fUoa/4JI0740FeOZ2kVGRmqxMWPGBCzimzhxohYztVtueKrjsGHDXI1+WJLRowcAwGIkegAALEaiBwDAYiR6AAAsRjEeCiwqKkqLxcbGBizGO3jwoNYmOTk538V427dvDzhilqmQqn379lps//79rpYJoHD5/82aRnic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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "'limited'" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Inference function\n", "import matplotlib.pyplot as plt\n", "\n", "@torch.no_grad()\n", "def predict(img_path, model_path='checkpoints/best.pth'):\n", " model = CRNN().to(device)\n", " model.load_state_dict(torch.load(model_path, map_location=device))\n", " model.eval()\n", " \n", " img = Image.open(img_path).convert('L')\n", " w, h = img.size\n", " img = img.resize((int(w * 32 / h), 32), Image.BILINEAR)\n", " \n", " tensor = T.Normalize(0.5, 0.5)(T.ToTensor()(img)).unsqueeze(0).to(device)\n", " pred = model(tensor).argmax(2).squeeze(1).cpu()\n", " text = decode(pred.tolist())\n", " \n", " plt.figure(figsize=(10, 2))\n", " plt.imshow(img, cmap='gray')\n", " plt.title(f\"Prediction: {text}\")\n", " plt.axis('off')\n", " plt.show()\n", " \n", " return text\n", "\n", "# Example usage:\n", "predict('image copy 3.png')" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [], "source": [ "import onnxruntime as ort\n", "import numpy as np\n", "from PIL import Image\n", "import torchvision.transforms as T\n", "import matplotlib.pyplot as plt\n", "\n", "# Define your alphabet exactly as in training\n", "alphabet = \"0123456789abcdefghijklmnopqrstuvwxyz\" # replace with your chars\n", "blank_idx = 0 # Usually 0 in CTC training\n", "\n", "def ctc_greedy_decode(pred_indices, blank=blank_idx):\n", " \"\"\"Convert CTC output indices to string.\"\"\"\n", " prev_idx = None\n", " result = []\n", "\n", " for idx in pred_indices:\n", " if idx != blank and idx != prev_idx:\n", " result.append(idx)\n", " prev_idx = idx\n", "\n", " text = ''.join([alphabet[i - 1] for i in result]) # subtract 1 if alphabet doesn't include blank\n", " return text\n", " \n", "def predict_onnx(img_path, onnx_path='crnn.onnx'):\n", " # Load ONNX model\n", " ort_session = ort.InferenceSession(onnx_path)\n", "\n", " # Load and preprocess image\n", " img = Image.open(img_path).convert('L')\n", " w, h = img.size\n", " new_w = max(int(w * 32 / h), 32)\n", " img = img.resize((new_w, 32), Image.BILINEAR)\n", "\n", " tensor = T.ToTensor()(img)\n", " tensor = T.Normalize((0.5,), (0.5,))(tensor)\n", " tensor = tensor.unsqueeze(0).numpy() # batch dimension\n", "\n", " # Run inference\n", " ort_inputs = {ort_session.get_inputs()[0].name: tensor}\n", " preds = ort_session.run(None, ort_inputs)[0] # shape: (seq_len, batch, num_classes)\n", "\n", " # Remove batch dimension\n", " preds = preds[:, 0, :] # shape: (seq_len, num_classes)\n", "\n", " # Greedy decode\n", " pred_indices = np.argmax(preds, axis=1) # shape: (seq_len,)\n", " text = ctc_greedy_decode(pred_indices.tolist()) # collapse repeats & remove blank\n", "\n", " # Plot\n", " plt.figure(figsize=(10, 2))\n", " plt.imshow(img, cmap='gray')\n", " plt.title(f\"Prediction: {text}\")\n", " plt.axis('off')\n", " plt.show()\n", "\n", " return text\n" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAxoAAACVCAYAAADfTozCAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAJKBJREFUeJzt3Qe0FEXWwPFGMgoSnhJEsgSRrBJUUKIEERAQEyqioAIr4Lquu6iwunhAd1fXCKuCCiJIEhGQLKjkjIAkMeAKSjAgAvq+c3vP9He76BrmvVcvjPx/53CoeV3T09Pd09M1detWrtTU1FQPAAAAABw6w+XKAAAAAICGBgAAAIBMQY8GAAAAAOdoaAAAAABwjoYGAAAAAOdoaAAAAABwjoYGAAAAAOdoaAAAAABwjoYGAAAAAOdoaABAgipUqODddtttweNFixZ5uXLl8v93Rdb36KOPJs0x+eyzz/xtfvLJJ52tc8yYMf46Zd3p8frrr3vVq1f38ubN6xUtWtT/25VXXun/AwBkHRoaAJJC7OYz9q9AgQJe1apVvX79+nnffPONl0zee++9pGpMJJOtW7f6jcHKlSt7o0eP9kaNGpXdmwQAp6082b0BAJAWw4YN8ypWrOgdPXrUW7p0qffCCy/4N+6bNm3yChUqlKU7s2nTpt7PP//s5cuXL03Pk+197rnnIhsbsr48eU7vS/Mtt9zi9ejRw8ufP3+anyu9S7/99pv39NNPe1WqVAn+/v777zveSgDAqZze32YAkk7btm29iy++2C/37t3bK1GihPePf/zDmz59unfDDTdEPuenn37yzjzzTOfbcsYZZ/g9Ky65Xl8yyp07t/8vPfbt2+f/HwuZiklrYxAAkHGETgFIas2bN/f/3717t/+/hM2cddZZ3s6dO7127dp5hQsX9m666SZ/mfzS/a9//curWbOmf0NfsmRJr0+fPt7BgwdD60xNTfUee+wxr2zZsn4vyVVXXeVt3rz5pNe2jdFYvny5/9rFihXzGzi1a9f2f2GPbZ/0ZggdChZvjMbatWv9BlaRIkX899aiRQtv2bJlkaFlH374oTdo0CDvnHPO8V+7c+fO3v79+0N1Dx8+7IcYyf+nsmrVKq9NmzZeSkqKV7BgQb83qVevXpF1//nPf3rly5f36zVr1szvZdI2bNjgv/9KlSr5+79UqVL+ur777rtTjtGQhmT79u29MmXK+D0dEhr1t7/9zfv1119DY2geeeQRvyzvX+9Lc4xG7NhNnDjRe/zxx/1jLdsk+3bHjh2h7VmyZInXrVs3r1y5cv5rn3/++d7AgQP93ictdu599dVXXqdOnfyybMf9998f2k4R63WpVauW/7pS7+qrr/b3t/bGG294DRo08Pdp8eLF/Z6eL7744hRHDQByBno0ACQ1aVAI6dmIOXHihH9zfPnll/uDlGMhVdKokJvY22+/3RswYIDfOHn22Wf9G3m5QZfBw+Lhhx/2GxrSWJB/a9as8Vq3bu0dO3bslNszd+5cr0OHDl7p0qW9P/zhD/7N9JYtW7x3333XfyzbsHfvXr+eDFo+FWngXHHFFX4j44EHHvC38aWXXvJvmhcvXuw1bNgwVL9///5+A0duuOVGXRpWMo7lrbfeCupMnTrV3wevvvpqaHB7VO+AvG+5CX7wwQf9XgJZ55QpU06q+9prr3k//PCDd++99/phbXITLY3AjRs3+g262L7ZtWuX/9qyX+S9yRgK+V8aTrrBZZLjJjfu0oiS/xcsWOAfp++//94bOXKkX0feq2yHvD8JqZN60siL54knnvB7pqQxIA2vESNG+A1TaSzGTJo0yTty5Ih39913++fZihUrvH//+9/el19+6S/TpEEh554cFzn35s2b5z311FN+w0ieH3PHHXf470kakNIzJ+esNGhkP8R67KQBNGTIEK979+5+HWkwyutKyJ6cs2avDQDkOKkAkAReffXVVLlkzZs3L3X//v2pX3zxReqECRNSS5QokVqwYMHUL7/80q936623+vUefPDB0POXLFni/33cuHGhv8+ePTv093379qXmy5cvtX379qm//fZbUO+hhx7y68n6YxYuXOj/Tf4XJ06cSK1YsWJq+fLlUw8ePBh6Hb2ue++9139eFPn7I488Ejzu1KmTvz07d+4M/rZ3797UwoULpzZt2vSk/dOyZcvQaw0cODA1d+7cqYcOHTqprvwfz9SpU/16K1eutNbZvXu3X0cfA7F8+XL/7/L6MUeOHDnp+W+++aZf74MPPjhp+2Td8Z7bp0+f1EKFCqUePXo0+JvsO3munCNas2bN/H/msatRo0bqL7/8Evz96aef9v++cePGuK89fPjw1Fy5cqXu2bMn+Fvs3Bs2bFiobr169VIbNGgQPF6wYIFfb8CA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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "'sbajafinance'" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "predict_onnx('image copy 2.png', 'export/model.onnx')" ] } ], "metadata": { "kernelspec": { "display_name": "myenv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.0" } }, "nbformat": 4, "nbformat_minor": 4 }