吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3935|回复: 82
收起左侧

[原创工具] 识别图片内容OCR并重命名文件

  [复制链接]
old船长 发表于 2025-3-26 19:38
在工作场景中,经常出现通过拍摄设备获取图片后,未及时进行有效命名的情况。这些图片中往往包含关键信息(如合同编号、产品型号、日期等),需要人工识别并命名,存在以下痛点:
  • 效率低下:人工识别图片文字并命名耗时耗力
  • 易出错:视觉疲劳或信息误读导致命名错误
  • 管理混乱:无序命名影响后续检索与归档
  • 追溯困难:缺乏系统性命名规则导致信息追溯成本


微信图片_20250326190628.png

https://wwqb.lanzout.com/b00zxmrwzg

密码:b3jc

网盘已经编译好的可以直接运行,关于环境需要安装
Umi-OCR 后打开http接口


如上图,必须允许HTTP服务才能使用HTTP接口(默认开启)。如果需要允许被局域网访问,请将主机切换到任何可用地址。在全局设置页中勾选高级才会显示。


所以写了一个python程序进行识别并重命名。
本工具基于 [Umi-OCR HTTP API](https://github.com/hiroi-sora/Umi-OCR/blob/main/docs/http/api_ocr.md) 开发,关键集成点包括:

1. **服务检查** - 通过调用 `/api/ocr/get_options` 接口检查服务状态
2. **文字识别** - 使用 `/api/ocr` 接口发送图片并获取识别结果
3. **灵活的请求方式** - 支持多种请求方法,提高兼容性和成功率
4. **智能结果解析** - 适应不同API版本的返回格式
5. **字符归一化处理** - OCR结果经过智能处理,修正常见错误:
   - 智能保护特定模式:自动保护"1hao"、"2hao"等中文数字标识不被错误转换
   - 单向字符转换:只进行'l'→'1'转换,绝不进行'1'→'l'转换
   - 数字"1"和字母"l"的混淆修正(如"[l16l]"→"[1161]")
   - 根据上下文智能判断字符替换(如在数字序列中出现的字母"l"→"1")
   - 方括号内数字序列的特殊处理(针对产品代码)
   - 其他常见OCR错误(如字母"O"与数字"0"的混淆)
   - 二次检查防护:检测并修复任何可能的错误转换(如确保"1hao"不会变成"lhao")
6. **OCR错误模式检测** - 自动检测并过滤无意义的OCR结果:
   - 识别包含大量重复字符(如"uuluululluul")的模式
   - 智能分析字符分布,过滤掉明显错误的文本
   - 详细记录归一化和过滤过程,便于调试和优化
附件有打包好的程序(exe)
功能特点
- 自动识别当前目录及其所有子目录下的图片文件(支持JPG、PNG、BMP等常见格式)
- 通过OCR技术精确提取图片中的文字内容
- 智能识别产品代码/零件号等特殊文本格式(如"[1232]-CN")
- 智能处理字符混淆问题,特别是数字"1"和字母"l"的混淆(如"[l16999l]"→"[1169991]")
- 自动过滤OCR错误文本,排除无意义的字符序列
- 过滤带小数点的文本(但保留日期格式如"2024.10.25")
- 纯数字文本只保留大于720的值
- 使用提取的文字内容自动重命名图片文件
- 智能处理重复文件名(使用"-1"、"-2"等后缀)
- 提供简单的图形界面(批处理脚本)和命令行界面
- 完整的中文支持和详细的日志记录
- 多种OCR请求方法,提高成功率
- 智能处理模块依赖和SSL证书问题,增强兼容性
- 自动错误恢复和备选方案,确保在各种环境下稳定运行
我使用了Umi-OCR作为识别引擎,Umi-OCR 原文地址   https://github.com/hiroi-sora/Umi-OCR
## 详细安装与配置指南,

自定义配置
如果需要自定义程序行为,可以在文件中修改以下参数:
# Umi-OCR 服务连接设置
UMI_OCR_HOST = "http://127.0.0.1"  # Umi-OCR HTTP 服务主机地址
UMI_OCR_PORT = 1224                # Umi-OCR HTTP 服务端口

### 1. 安装 Umi-OCR

1. 从 [Umi-OCR GitHub 仓库](https://github.com/hiroi-sora/Umi-OCR) 下载最新版本的 Umi-OCR
2. 按照官方安装指南完成安装和配置

### 2. 配置 Umi-OCR 的 HTTP 服务

这是使用本工具的**关键步骤**:

1. 打开 Umi-OCR 软件
2. 进入设置界面
3. 找到 HTTP 服务设置选项并启用
4. 确保 HTTP 服务端口设置为 1224(默认端口)
5. 保存设置并确保 HTTP 服务已成功启动(通常会有指示)

[Python] 纯文本查看 复制代码
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
import os
import sys
import json
import re
import logging
from time import sleep
import ssl
import warnings
 
# 禁用SSL警告
warnings.filterwarnings("ignore", message="Unverified HTTPS request")
 
# 尝试修复SSL证书问题(Windows下常见问题)
try:
    _create_unverified_https_context = ssl._create_unverified_context
except AttributeError:
    # 旧版本Python已经默认不验证
    pass
else:
    # 禁用证书验证
    ssl._create_default_https_context = _create_unverified_https_context
 
print("正在初始化程序...", flush=True)
 
# 尝试导入requests模块,如果失败则提供清晰的错误信息
requests = None
try:
    print("正在加载requests模块...", flush=True)
    import requests
    # 禁用SSL警告
    from requests.packages.urllib3.exceptions import InsecureRequestWarning
    requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
except ImportError:
    print("错误: 未能导入'requests'模块。")
    print("请使用以下命令安装它: pip install requests")
    print("或者运行批处理文件'run_ocr_rename.bat',它会自动安装必要的依赖。")
    sys.exit(1)
except Exception as e:
    print(f"警告: 加载requests模块时出现问题: {e}")
    print("尝试使用替代方法...", flush=True)
    try:
        # 尝试在禁用SSL的情况下导入
        import os
        os.environ['PYTHONHTTPSVERIFY'] = '0'
        import requests
        # 禁用SSL验证
        requests.packages.urllib3.disable_warnings()
    except Exception as e2:
        print(f"错误: 无法加载requests模块: {e2}")
        print("请尝试重新安装requests模块: pip install --upgrade requests")
        sys.exit(1)
 
# 设置requests全局配置,禁用SSL验证
if requests:
    requests.packages.urllib3.disable_warnings()
    # 创建一个自定义的会话对象,禁用SSL验证
    session = requests.Session()
    session.verify = False
 
# 尝试导入imghdr模块,如果失败则使用自定义的文件类型检测
try:
    import imghdr
    HAS_IMGHDR = True
except ImportError:
    print("注意: 未能导入'imghdr'模块。将使用基于文件扩展名的图片检测方法。")
    HAS_IMGHDR = False
 
# 配置日志
try:
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(levelname)s - %(message)s',
        handlers=[
            logging.FileHandler("ocr_rename.log", encoding='utf-8'),
            logging.StreamHandler()
        ]
    )
    logger = logging.getLogger(__name__)
except Exception as e:
    print(f"警告: 无法配置日志系统: {e}")
    # 创建一个简单的日志替代品
    class SimpleLogger:
        def info(self, msg): print(f"[信息] {msg}")
        def error(self, msg): print(f"[错误] {msg}")
        def warning(self, msg): print(f"[警告] {msg}")
    logger = SimpleLogger()
 
# Umi-OCR API 设置
UMI_OCR_HOST = "http://127.0.0.1"
UMI_OCR_PORT = 1224  # Umi-OCR HTTP 服务默认端口
UMI_OCR_BASE_URL = f"{UMI_OCR_HOST}:{UMI_OCR_PORT}"
 
def check_umi_ocr_service():
    """Check if Umi-OCR service is running"""
    try:
        response = requests.get(f"{UMI_OCR_BASE_URL}/api/ocr/get_options", timeout=5)
        if response.status_code == 200:
            logger.info("Umi-OCR服务运行正常")
            return True
        else:
            logger.error(f"Umi-OCR服务返回状态码: {response.status_code}")
            return False
    except requests.exceptions.RequestException as e:
        logger.error(f"无法连接到Umi-OCR服务: {e}")
        return False
 
def get_image_type(file_path):
    """获取图片类型,支持imghdr缺失的情况"""
    # 通过文件扩展名判断
    file_ext = os.path.splitext(file_path)[1].lower().lstrip('.')
    if file_ext in ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tiff', 'tif']:
        return file_ext
         
    # 如果有imghdr模块,使用它来判断
    if HAS_IMGHDR:
        img_type = imghdr.what(file_path)
        if img_type:
            return img_type
             
    # 简单的文件头检测(作为备选方案)
    try:
        with open(file_path, 'rb') as f:
            header = f.read(12)
            # JPEG: FF D8 FF
            if header.startswith(b'\xff\xd8\xff'):
                return 'jpeg'
            # PNG: 89 50 4E 47 0D 0A 1A 0A
            elif header.startswith(b'\x89PNG\r\n\x1a\n'):
                return 'png'
            # GIF: 47 49 46 38
            elif header.startswith(b'GIF89a') or header.startswith(b'GIF87a'):
                return 'gif'
            # BMP: 42 4D
            elif header.startswith(b'BM'):
                return 'bmp'
    except Exception:
        pass
         
    return None
 
import base64
import time
import random
 
# 重试设置
MAX_RETRIES = 3
RETRY_DELAY = 1
 
def encode_image_to_base64(image_path):
    """将图片编码为base64格式"""
    try:
        with open(image_path, 'rb') as image_file:
            base64_data = base64.b64encode(image_file.read()).decode('utf-8')
            return base64_data
    except Exception as e:
        logger.error(f"编码图片为base64失败: {e}")
        return None
 
def extract_text_from_image(image_path):
    """使用Umi-OCR API从图片中提取文字"""
    try:
        # 确认文件存在
        if not os.path.exists(image_path):
            logger.error(f"文件不存在: {image_path}")
            return None
             
        # 确认是图片文件
        img_type = get_image_type(image_path)
        if not img_type:
            logger.error(f"文件不是有效的图片: {image_path}")
            return None
         
        # 尝试获取base64编码的图片数据
        base64_data = encode_image_to_base64(image_path)
        if not base64_data:
            logger.error(f"无法读取图片数据: {image_path}")
            return None
             
        # 设置重试计数器
        retries = 0
         
        # 尝试方法1: 使用base64编码的图片数据
        while retries < MAX_RETRIES:
            try:
                logger.info(f"尝试使用base64方法发送OCR请求 (尝试 {retries+1}/{MAX_RETRIES}): {image_path}")
                 
                # 构建请求数据 - 使用base64编码
                data = {
                    "task_id": "image_rename_task",
                    "base64": base64_data
                }
                 
                # 发送OCR请求
                response = session.post(
                    f"{UMI_OCR_BASE_URL}/api/ocr",
                    json=data,
                    timeout=30
                )
                 
                # 如果成功则返回结果
                if response.status_code == 200:
                    result = response.json()
                    logger.info(f"base64方法OCR结果: {result}")
                    if result.get('code') == 100:
                        return parse_ocr_result(result, image_path)
                    elif result.get('code') != 802# 忽略特定错误代码
                        # 添加错误码详细信息
                        error_code = result.get('code')
                        error_msg = result.get('msg', '未知错误')
                        logger.warning(f"OCR请求返回错误码 {error_code}: {error_msg}")
                 
                logger.info("base64方法失败,尝试文件上传方法")
                break  # 如果接收到了响应但不成功,跳过此方法
                 
            except (requests.exceptions.ConnectionError,
                   requests.exceptions.Timeout,
                   requests.exceptions.ConnectionAbortedError) as e:
                retries += 1
                if retries < MAX_RETRIES:
                    # 添加随机时间避免同步问题
                    delay = RETRY_DELAY + random.uniform(0, 1)
                    logger.warning(f"连接错误,{delay:.1f}秒后重试 ({retries}/{MAX_RETRIES}): {str(e)}")
                    time.sleep(delay)
                else:
                    logger.error(f"base64方法连接失败,达到最大重试次数: {e}")
            except Exception as e:
                logger.warning(f"base64方法失败: {e}")
                break
         
        # 方法2: 上传文件内容
        retries = 0
        while retries < MAX_RETRIES:
            try:
                logger.info(f"尝试使用文件上传方法发送OCR请求 (尝试 {retries+1}/{MAX_RETRIES}): {image_path}")
                 
                # 确定正确的MIME类型
                mime_types = {
                    'jpeg': 'image/jpeg',
                    'jpg': 'image/jpeg',
                    'png': 'image/png',
                    'gif': 'image/gif',
                    'bmp': 'image/bmp',
                    'tiff': 'image/tiff',
                    'tif': 'image/tiff'
                }
                mime_type = mime_types.get(img_type, f'image/{img_type}')
                 
                # 打开图片文件并创建multipart表单数据
                with open(image_path, 'rb') as img_file:
                    files = {'file': (os.path.basename(image_path), img_file, mime_type)}
                     
                    # 发送OCR请求
                    response = session.post(
                        f"{UMI_OCR_BASE_URL}/api/ocr",
                        files=files,
                        timeout=30
                    )
                     
                    # 处理响应
                    if response.status_code == 200:
                        result = response.json()
                        logger.info(f"文件上传方法OCR结果: {result}")
                        return parse_ocr_result(result, image_path)
                    else:
                        logger.error(f"OCR请求失败,状态码 {response.status_code}: {response.text}")
                         
                # 如果执行到这里,说明请求成功发送但返回了错误
                # 不再重试此方法
                break
                     
            except (requests.exceptions.ConnectionError,
                   requests.exceptions.Timeout,
                   requests.exceptions.ConnectionAbortedError) as e:
                retries += 1
                if retries < MAX_RETRIES:
                    # 添加随机时间避免同步问题
                    delay = RETRY_DELAY + random.uniform(0, 1)
                    logger.warning(f"连接错误,{delay:.1f}秒后重试 ({retries}/{MAX_RETRIES}): {str(e)}")
                    time.sleep(delay)
                else:
                    logger.error(f"文件上传方法连接失败,达到最大重试次数: {e}")
            except Exception as e:
                logger.error(f"文件上传方法失败: {e}")
                break
 
        # 作为最后手段,尝试直接使用系统路径
        if retries == MAX_RETRIES:
            try:
                logger.info(f"尝试使用系统路径方法发送OCR请求: {image_path}")
                abs_path = os.path.abspath(image_path)
                data = {"task_id": "image_rename_task", "path": abs_path}
                 
                response = session.post(
                    f"{UMI_OCR_BASE_URL}/api/ocr",
                    json=data,
                    timeout=30
                )
                 
                if response.status_code == 200:
                    result = response.json()
                    logger.info(f"系统路径方法OCR结果: {result}")
                    if result.get('code') == 100:
                        return parse_ocr_result(result, image_path)
            except Exception as e:
                logger.error(f"系统路径方法失败: {e}")
         
        logger.error(f"所有OCR方法都失败,无法识别图片: {image_path}")
        return None
 
    except Exception as e:
        logger.error(f"处理图片时出错 {image_path}: {e}")
        return None
 
def is_date_format(text):
    """检查文本是否包含日期格式,如 YYYY.MM.DD"""
    # 匹配常见日期格式 如 2024.10.25
    if re.search(r'\d{4}\.\d{1,2}\.\d{1,2}', text):
        return True
    return False
 
def normalize_confused_characters(text):
    """处理OCR中常见的字符混淆问题,特别是1和l的混淆"""
    if not text or not isinstance(text, str):
        return text
         
    # 原始文本备份
    original_text = text
     
    
     
    # 创建一个字典来存储需要保护的部分
    protected_parts = {}
    placeholder_template = "PROTECTED_{}"
    placeholder_count = 0
     
    # 保护特定模式免受修改
    for pattern in chinese_number_patterns:
        if pattern in text:
            placeholder = placeholder_template.format(placeholder_count)
            text = text.replace(pattern, placeholder)
            protected_parts[placeholder] = pattern
            placeholder_count += 1
            logger.info(f"保护特定模式: {pattern}")
     
    # 处理数字上下文中的 'l' → '1' 混淆(但绝不进行 '1' → 'l' 的转换)
     
    # 1. 处理纯数字序列中的 'l'
    if re.search(r'\d+l+\d+', text) or re.search(r'^l+\d+', text) or re.search(r'\d+l+$', text):
        text = text.replace('l', '1')
     
    # 2. 处理常见的产品代码格式中的 'l'
    # 例如: [l1l] → [111]
    if re.search(r'\[\w*l\w*\]', text):
        # 在方括号内替换
        parts = re.split(r'(\[\w*\])', text)
        for i, part in enumerate(parts):
            if part.startswith('[') and part.endswith(']'):
                parts[i] = part.replace('l', '1')
        text = ''.join(parts)
     
    # 3. 处理非连续的混淆
    # 只有当字符串看起来像数字序列或产品代码时,才进行替换
    if re.search(r'^\d*l+\d*$', text) or re.search(r'^[A-Z\d\-\[\]]*l+[A-Z\d\-\[\]]*$', text):
        text = text.replace('l', '1')
     
    # 4. 处理其他OCR常见混淆
    # O-0 混淆
    if text.isalnum() and re.search(r'[A-Z]+O\d+', text):
        text = re.sub(r'([A-Z]+)O(\d+)', r'\1 0\2', text)
     
    # 恢复受保护的模式
    for placeholder, original in protected_parts.items():
        text = text.replace(placeholder, original)
     
    # 检查是否有增序数字被错误地转换为字母
    # 例如确保"1hao"不会变成"lhao"(即使上面没有检测到)
    if '1' in original_text and 'l' in text:
        for match in re.finditer(r'1[a-z]+', original_text):
            original_part = match.group(0)
            # 在当前文本中找到可能的错误转换
            potential_error = original_part.replace('1', 'l')
            if potential_error in text:
                text = text.replace(potential_error, original_part)
                logger.info(f"修正错误转换: {potential_error} → {original_part}")
     
    # 如果看起来像是大量OCR错误字符串(如 "wulmllluululmululuululuunluuluulluuululunululululunul")
    # 这种无意义的重复序列通常是OCR错误
    if len(text) > 30 and 'u' in text and 'l' in text and text.count('u') > 10 and text.count('l') > 10:
        logger.info(f"检测到OCR错误文本: {text}")
        return None  # 对于明显的OCR错误,返回None
     
    # 如果修改了文本,记录日志
    if text != original_text:
        logger.info(f"字符归一化: {original_text} → {text}")
         
    return text
 
def is_part_number(text):
    """检查文本是否看起来像零件号/产品代码"""
    # 先归一化文本,处理字符混淆问题
    normalized_text = normalize_confused_characters(text)
    if not normalized_text:
        return False
    text = normalized_text
     
    # 检查日期格式文本(特殊处理)
    if is_date_format(text):
        return True
         
    # 检查特定关键词模式,包含 shaixuanhouheidian-
    if 'shaixuanhouheidian-' in text:
        return True
     
    # 过滤含有小数点的文本 (但排除日期格式)
    if '.' in text and not is_date_format(text):
        return False
     
    # 检查是否包含方括号、连字符和百分号的组合 - 类似于用户示例 "[1161]-CFS-40%HF40%"
    if re.search(r'\[\d+\]', text) and ('-' in text or '%' in text):
        return True
     
    # 检查常见产品代码模式 (如果没有方括号模式)
    # 例如: ABC-123, XYZ-456-789, 123-ABC-456
    if re.search(r'[A-Z0-9]+-[A-Z0-9]+', text):
        return True
         
    # 检查是否含有特殊字符组合,通常出现在产品编码中
    special_chars = ['-', '%', '#', '/', '\\', '_']
    has_special = any(char in text for char in special_chars)
    has_alphanumeric = re.search(r'[A-Z0-9]{2,}', text)
     
    # 检查全数字且大于720的情况
    if text.isdigit() and int(text) > 720:
        return True
     
    return has_special and has_alphanumeric
 
def parse_ocr_result(result, image_path):
    """Parse the OCR result and extract text"""
    # 检查OCR是否成功
    if result.get('code') == 100:
        # 准备收集所有可能的文本
        all_text_candidates = []
        part_numbers = []
         
        # 检查数据结构,根据API不同版本可能有所变化
        data = result.get('data', {})
         
        # 从API响应中提取所有文本候选项
        # 处理新版API格式 (列表格式)
        if isinstance(data, list):
            for item in data:
                if isinstance(item, dict) and 'text' in item:
                    text = item.get('text', '').strip()
                    if text:
                        # 应用字符归一化
                        normalized = normalize_confused_characters(text)
                        if normalized:
                            all_text_candidates.append(normalized)
                            # 检查是否是产品代码/零件号
                            if is_part_number(normalized):
                                part_numbers.append(normalized)
         
        # 处理可能的老版本API格式
        elif isinstance(data, dict):
            if 'text' in data:
                if isinstance(data['text'], list):
                    for text in data['text']:
                        if text.strip():
                            all_text_candidates.append(text.strip())
                            if is_part_number(text):
                                part_numbers.append(text)
                elif isinstance(data['text'], str) and data['text'].strip():
                    all_text_candidates.append(data['text'].strip())
                    if is_part_number(data['text']):
                        part_numbers.append(data['text'])
            elif 'results' in data and isinstance(data['results'], list):
                for item in data['results']:
                    if isinstance(item, dict) and 'text' in item:
                        text = item.get('text', '').strip()
                        if text:
                            all_text_candidates.append(text)
                            if is_part_number(text):
                                part_numbers.append(text)
         
        # 如果仍未找到文本,尝试递归搜索
        if not all_text_candidates:
            def extract_all_text(obj):
                texts = []
                if isinstance(obj, dict):
                    for k, v in obj.items():
                        if k == 'text' and isinstance(v, str) and v.strip():
                            texts.append(v.strip())
                            if is_part_number(v):
                                part_numbers.append(v.strip())
                        else:
                            texts.extend(extract_all_text(v))
                elif isinstance(obj, list):
                    for item in obj:
                        texts.extend(extract_all_text(item))
                return texts
             
            all_text_candidates = extract_all_text(data)
         
        logger.info(f"从图片提取的文本候选: {all_text_candidates}")
        if part_numbers:
            logger.info(f"找到产品代码/零件号: {part_numbers}")
         
        # 选择最佳文本作为文件名
        selected_text = None
         
        # 智能过滤文本候选项,保留日期格式
        filtered_candidates = []
        for t in all_text_candidates:
            # 保留日期格式文本
            if is_date_format(t):
                filtered_candidates.append(t)
            # 保留包含特定关键词的文本
            elif 'shaixuanhouheidian-' in t:
                filtered_candidates.append(t)
            # 过滤其他带小数点的文本
            elif '.' not in t:
                filtered_candidates.append(t)
         
        filtered_part_numbers = []
        for p in part_numbers:
            # 保留日期格式文本
            if is_date_format(p):
                filtered_part_numbers.append(p)
            # 保留包含特定关键词的文本
            elif 'shaixuanhouheidian-' in p:
                filtered_part_numbers.append(p)
            # 过滤其他带小数点的文本
            elif '.' not in p:
                filtered_part_numbers.append(p)
         
        if filtered_candidates:
            all_text_candidates = filtered_candidates
        if filtered_part_numbers:
            part_numbers = filtered_part_numbers
         
        logger.info(f"过滤后的文本候选: {all_text_candidates}")
        logger.info(f"过滤后的产品代码: {part_numbers}")
         
        # 处理全是数字的情况
        numeric_candidates = []
        for text in all_text_candidates:
            if text.isdigit() and int(text) > 720:
                numeric_candidates.append(text)
                logger.info(f"找到大于720的数字: {text}")
         
        # 优先级1: 匹配的产品代码/零件号
        if part_numbers:
            # 优先选择最长的产品代码,通常包含更多信息
            selected_text = max(part_numbers, key=len)
            logger.info(f"找到产品代码/零件号: {selected_text}")
         
        # 优先级2: 大于720的数字
        elif numeric_candidates:
            selected_text = max(numeric_candidates, key=int)
            logger.info(f"使用大于720的数字: {selected_text}")
         
        # 优先级3: 最长的文本块,可能包含更多有意义的信息
        elif all_text_candidates:
            # 过滤掉太短的文本
            candidates = [t for t in all_text_candidates if len(t) > 3]
            if candidates:
                selected_text = max(candidates, key=len)
                logger.info(f"使用最长的文本块: {selected_text}")
            else:
                selected_text = all_text_candidates[0]
                logger.info(f"使用首个文本块: {selected_text}")
         
        # 如果仍然没有找到有效文本,使用原始文件名的一部分作为备选
        if not selected_text:
            base_name = os.path.basename(image_path)
            name_parts = os.path.splitext(base_name)[0].split()
             
            # 尝试使用文件名中的非数字部分
            non_numeric_parts = [part for part in name_parts if not part.isdigit()]
            if non_numeric_parts:
                selected_text = '-'.join(non_numeric_parts)
                logger.info(f"使用文件名部分作为替代文本: {selected_text}")
            else:
                # 如果没有非数字部分,使用整个文件名(不含扩展名)
                selected_text = os.path.splitext(base_name)[0]
                logger.info(f"使用原始文件名作为替代文本: {selected_text}")
         
        # 清理文本用于文件名
        result = clean_text_for_filename(selected_text)
        if result:
            return result
        else:
            # 最终回退:如果清理后的文本仍为空,使用原始文件名前缀
            filename_prefix = os.path.basename(image_path).split('(')[0].strip()
            if not filename_prefix:
                filename_prefix = "OCR_image"
            logger.info(f"使用文件名前缀作为最终替代文本: {filename_prefix}")
            return filename_prefix
    else:
        logger.error(f"OCR失败: {image_path}: {result.get('msg', '未知错误')}")
        # 使用原始文件名前缀作为备选
        filename_prefix = os.path.basename(image_path).split('(')[0].strip()
        if not filename_prefix:
            filename_prefix = "OCR_image"
        logger.info(f"OCR失败,使用文件名前缀作为替代文本: {filename_prefix}")
        return filename_prefix
 
def clean_text_for_filename(text):
    """为文件名清理并准备文本"""
    if not text or text.strip() == "":
        return None
         
    # 移除换行符和多余的空格
    text = re.sub(r'\s+', ' ', text).strip()
     
    # 限制长度,避免文件名过长错误(可调整)
    max_length = 100
    if len(text) > max_length:
        text = text[:max_length]
     
    # 移除文件名中不允许的字符
    text = re.sub(r'[\\/*?:"<>|]', '', text)
     
    # 移除任何剩余的问题字符和尾部空格
    text = text.strip()
     
    return text if text else None
 
def is_image_file(filename):
    """检查文件是否为图片"""
    # 使用我们的get_image_type函数,它已经包含了后备机制
    return get_image_type(filename) is not None
 
def process_images_in_directory(directory="."):
    """处理指定目录及其所有子目录中的图片"""
    # 检查Umi-OCR服务是否运行
    if not check_umi_ocr_service():
        logger.error("Umi-OCR服务未运行,请先启动服务。")
        print("请确保已安装并运行Umi-OCR,且启用了HTTP服务功能。")
        print("您可以从这里下载Umi-OCR: https://github.com/hiroi-sora/Umi-OCR")
        return
     
    # 使用os.walk递归获取所有目录中的图片文件
    all_image_files = []
    for root, _, files in os.walk(directory):
        for filename in files:
            file_path = os.path.join(root, filename)
            if is_image_file(file_path):
                # 保存相对路径和文件名
                rel_path = os.path.relpath(file_path, directory)
                all_image_files.append(rel_path)
     
    if not all_image_files:
        logger.warning("未找到任何图片文件。")
        print("未找到任何图片文件。")
        return
     
    logger.info(f"在目录及子目录中找到 {len(all_image_files)} 个图片文件等待处理")
    print(f"找到 {len(all_image_files)} 个图片文件,开始处理...")
     
    # 处理每张图片
    success_count = 0
    for i, rel_path in enumerate(all_image_files):
        # 计算完整路径
        file_path = os.path.join(directory, rel_path)
        # 获取目录和文件名
        file_dir = os.path.dirname(file_path)
        filename = os.path.basename(file_path)
         
        logger.info(f"正在处理图片 {i+1}/{len(all_image_files)}: {rel_path}")
        print(f"正在处理图片 ({i+1}/{len(all_image_files)}): {rel_path}")
         
        # 使用OCR提取文本
        extracted_text = extract_text_from_image(file_path)
         
        if extracted_text:
            # 保留原始扩展名
            file_ext = os.path.splitext(filename)[1]
            new_filename = f"{extracted_text}{file_ext}"
            new_file_path = os.path.join(file_dir, new_filename)
             
            # 检查新文件名是否已存在
            counter = 1
            while os.path.exists(new_file_path):
                new_filename = f"{extracted_text}-{counter}{file_ext}"
                new_file_path = os.path.join(file_dir, new_filename)
                counter += 1
             
            try:
                # 重命名文件
                os.rename(file_path, new_file_path)
                new_rel_path = os.path.relpath(new_file_path, directory)
                logger.info(f"已重命名: {rel_path} -> {new_rel_path}")
                print(f"&#10003; 已成功重命名: {rel_path} -> {new_rel_path}")
                success_count += 1
                 
                # 添加小延迟以避免API过载
                sleep(0.1)
            except Exception as e:
                logger.error(f"重命名 {rel_path} 失败: {e}")
                print(f"× 重命名失败: {rel_path}")
        else:
            logger.warning(f"无法从 {rel_path} 提取文本或文本不适合用作文件名")
            print(f"× 无法提取文本: {rel_path}")
     
    logger.info(f"处理完成。成功重命名 {success_count} 个文件,共 {len(all_image_files)} 个文件。")
    print(f"\n处理完成!成功重命名 {success_count} 个文件,共 {len(all_image_files)} 个文件。")
    print(f"详细日志请查看 ocr_rename.log 文件。")
 
if __name__ == "__main__":
    print("=== 图片文字提取并重命名工具 ===")
    print("本程序将识别当前目录及其所有子目录中的图片文字,并用识别的文字重命名图片文件。")
    print("正在初始化...")
     
    try:
        process_images_in_directory()
        print("\n程序执行完毕。")
    except Exception as e:
        logger.error(f"发生意外错误: {e}")
        print(f"发生错误: {e}")
        print("请查看 ocr_rename.log 获取详细信息。")

免费评分

参与人数 15吾爱币 +20 热心值 +12 收起 理由
Tian555 + 1 谢谢@Thanks!
nihil7 + 1 谢谢@Thanks!
yhtq2025 + 1 + 1 我很赞同!
xcswang + 1 + 1 热心回复!
aria1983 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
leechjia + 1 + 1 谢谢@Thanks!
wxn2023 + 1 + 1 心有可为,实有可行
抱薪风雪雾 + 1 + 1 谢谢@Thanks!
schtg + 1 + 1 谢谢@Thanks!
HillBoom + 1 + 1 用心讨论,共获提升!
kim945 + 1 明天试试,我有海量的设备铭牌照片,看看会识别成什么
Hameel + 1 热心回复!
youbudenibuxiao + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
独孤求嗳 + 1 + 1 我很赞同!
风之暇想 + 7 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

xhcy001 发表于 2025-3-30 20:09
umi-ocr这个软件本身有离线版呢 目前最新版本2.1.5版98兆大小,你怎么不用离线版免得还要搞什么服务器。2.这款软件识别文字后自动复制在剪切板上,我使用按键精灵在剪切板if判断有没自定义关键字,然后操作鼠标点击,然后延时输入汉字。这样就做成了一个自动化工具。可惜我对潘森不熟,不会打包。只学了VBA。既然你会写潘森代码,干嘛不写成 自动化工具 作用更大。 可以安装在机器人摄像头上,自动识别,自动点击鼠标操作。。。。   你如果有意向可以留言交流。
helian147 发表于 2025-3-27 10:04
WARNING - base64方法失败: name 'chinese_number_patterns' is not defined

看了下源代码,chinese_number_patterns变量没有定义吧
HHAK003 发表于 2025-3-26 23:05
如果图片里面文字比较多,比如身份证照片或者户口簿照片,可否识别出图片上的姓名后 重命名文件吗?
q8155128 发表于 2025-3-26 23:32
应该是个好东西啊
crystalZ 发表于 2025-3-26 23:48
ocr的结果要人工校验的
IvesLam 发表于 2025-3-27 00:05
做标书,在安排凭证的时候,可以省事很多了,谢谢分享。
52PJ070 发表于 2025-3-27 00:15
不错的OCR,挺实用的,感谢提供分享!
shengdan4321 发表于 2025-3-27 00:34
感谢楼主分享
zhuangzhong 发表于 2025-3-27 02:48
感谢分享,很有用。
fxd680126 发表于 2025-3-27 06:10
感谢分享!下载备用。
lxh309 发表于 2025-3-27 06:29
感谢感谢你,多享好用软件
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-4-10 21:54

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表