img2ppt 1.3.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
img2ppt-1.3.4/PKG-INFO ADDED
@@ -0,0 +1,221 @@
1
+ Metadata-Version: 2.4
2
+ Name: img2ppt
3
+ Version: 1.3.4
4
+ Summary: スライド画像/PDF から PowerPoint を自動生成するパイプライン (Windows 専用)
5
+ License-Expression: MIT
6
+ Keywords: powerpoint,ocr,pptx,slide,image
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Environment :: Console
9
+ Classifier: Intended Audience :: End Users/Desktop
10
+ Classifier: Operating System :: Microsoft :: Windows :: Windows 11
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Topic :: Multimedia :: Graphics :: Presentation
14
+ Requires-Python: >=3.12
15
+ Description-Content-Type: text/markdown
16
+ Requires-Dist: Pillow>=10.4.0
17
+ Requires-Dist: opencv-python>=4.11.0
18
+ Requires-Dist: numpy>=1.26.4
19
+ Requires-Dist: winrt-Windows.Media.Ocr>=3.2.1
20
+ Requires-Dist: python-pptx>=1.0.0
21
+ Requires-Dist: pypdfium2>=0.28.0
22
+ Requires-Dist: janome>=0.5.0
23
+
24
+ # img2ppt
25
+
26
+ スライド画像(JPG/PNG)または PDF を PowerPoint(.pptx)に自動変換するツールです。
27
+ テキスト・図形・背景を自動認識し、16:9 スライドとして再構成します。
28
+
29
+ > **動作環境**: Windows 11 専用 / Python 3.12 以上
30
+
31
+ ---
32
+
33
+ ## インストール
34
+
35
+ ```cmd
36
+ pip install .
37
+ ```
38
+
39
+ これだけで全依存ライブラリ(OpenCV / Pillow / python-pptx / pypdfium2 / janome 等)が一括インストールされます。
40
+
41
+ ### インストール確認
42
+
43
+ ```cmd
44
+ img2ppt --help
45
+ ```
46
+
47
+ ---
48
+
49
+ ## 使い方
50
+
51
+ ### 基本
52
+
53
+ ```cmd
54
+ :: 画像ファイルを変換
55
+ img2ppt スライド.jpg -d 出力フォルダ
56
+
57
+ :: クリップボードの画像を変換(引数なし)
58
+ img2ppt -d 出力フォルダ
59
+
60
+ :: PDF を変換(全ページ → 1 ファイルの PPTX)
61
+ img2ppt 資料.pdf -d 出力フォルダ
62
+ ```
63
+
64
+ ### 入力モード
65
+
66
+ | モード | 方法 | 説明 |
67
+ | --- | --- | --- |
68
+ | ファイル指定 | `img2ppt <ファイルパス>` | JPG / PNG / PDF を直接指定 |
69
+ | クリップボード | `img2ppt`(引数なし) | 画像をコピーしてからそのまま実行 |
70
+ | PDF | 拡張子 `.pdf` のファイルを指定 | 全ページを 1 つの PPTX にまとめる |
71
+
72
+ ---
73
+
74
+ ## 出力ファイル
75
+
76
+ `-d` で指定したディレクトリに以下が出力されます。
77
+
78
+ | ファイル | 説明 |
79
+ | --- | --- |
80
+ | `{stem}.pptx` | **最終 PowerPoint** |
81
+ | `{stem}.json` | 認識結果の構造化 JSON |
82
+ | `{stem}_04_inpainted.png` | テキスト除去後の画像 |
83
+ | `{stem}_05_1_segment_filled.png` | セグメント検出結果(シルエット) |
84
+ | `{stem}_05_2_mask_segment.png` | セグメントマスク画像 |
85
+ | `{stem}_06_1_content_inpainted.png` | コンテンツ除去後の画像 |
86
+ | `{stem}_06_2_mask_colorize.png` | カラー分割マスク |
87
+ | `{stem}_06_3_colorized.png` | カラー分割結果 |
88
+
89
+ デバッグ用の可視化画像(`vis_` プレフィックス)は `--debug` を付けると追加出力されます。
90
+
91
+ ---
92
+
93
+ ## オプション一覧
94
+
95
+ ### 基本オプション
96
+
97
+ | オプション | デフォルト | 説明 |
98
+ | --- | --- | --- |
99
+ | `-d`, `--output-dir` | `test/` | 出力ディレクトリ |
100
+ | `-o`, `--output-json` | 自動 | 最終 JSON の出力パス(単一ページ時のみ有効) |
101
+ | `-v`, `--verbose` | オフ | 詳細ログを表示(省略時はプログレスバーのみ) |
102
+ | `--debug` | オフ | デバッグ用可視化画像(`vis_xxx`)を出力 |
103
+ | `--keep-tmp` | オフ | 処理後も一時ディレクトリを削除しない |
104
+ | `--tmp-dir` | システム一時領域 | 一時ディレクトリの作成場所を指定 |
105
+ | `--pdf-dpi` | `150` | PDF ページ変換の解像度(DPI) |
106
+
107
+ ### 出力パス個別指定
108
+
109
+ | オプション | 説明 |
110
+ | --- | --- |
111
+ | `--output-inpaint` | Step4 テキスト除去画像の出力パス |
112
+ | `--output-seg-filled` | Step5 シルエット画像の出力パス |
113
+ | `--output-mask-seg` | Step5 マスク画像の出力パス |
114
+
115
+ ### 画像鮮明化(Step1)
116
+
117
+ 低解像度・圧縮ノイズが多い画像の OCR 精度を上げるための前処理です。
118
+ PNG または長辺 1280px 以上の JPG は自動スキップされます。
119
+
120
+ | オプション | デフォルト | 説明 |
121
+ | --- | --- | --- |
122
+ | `--denoise-h` | `10` | ノイズ除去強度(大きいほど強く除去、ぼけやすい) |
123
+ | `--bilateral-d` | `9` | エッジ保持フィルタの近傍径 |
124
+ | `--sigma-color` | `60.0` | エッジ保持フィルタの色シグマ |
125
+ | `--sigma-space` | `60.0` | エッジ保持フィルタの空間シグマ |
126
+ | `--unsharp-radius` | `2` | 輪郭強調の半径(px) |
127
+ | `--unsharp-percent` | `200` | 輪郭強調の強度(%) |
128
+ | `--unsharp-threshold` | `2` | 輪郭強調の閾値 |
129
+
130
+ ### テキスト除去(Step4)
131
+
132
+ | オプション | デフォルト | 説明 |
133
+ | --- | --- | --- |
134
+ | `--sample-offset` | `3` | 背景色サンプリング位置(bbox 端から何 px 外側か) |
135
+ | `--fill-expand` | `2` | テキスト除去の塗り範囲拡張量(px) |
136
+
137
+ ### セグメント検出(Step5)
138
+
139
+ | オプション | デフォルト | 説明 |
140
+ | --- | --- | --- |
141
+ | `--sat-max` | `40` | 背景とみなす最大彩度(HSV-S、0〜255) |
142
+ | `--val-min` | `200` | 背景とみなす最小輝度(HSV-V、0〜255) |
143
+
144
+ ### OCR 品質フィルタ(Step2.5)
145
+
146
+ | オプション | デフォルト | 説明 |
147
+ | --- | --- | --- |
148
+ | `--ocr-min-height` | `8` | bbox 最小高さ(px)。これ未満は除外 |
149
+ | `--ocr-threshold` | `0.45` | 総合スコア閾値。これ未満のテキストを除外 |
150
+
151
+ ### PowerPoint 生成(Step7)
152
+
153
+ | オプション | デフォルト | 説明 |
154
+ | --- | --- | --- |
155
+ | `--bg-tolerance` | `20` | 図形の背景透過許容幅(RGB 各チャンネル ±n) |
156
+
157
+ ---
158
+
159
+ ## 実行例
160
+
161
+ ```cmd
162
+ :: 高解像度 PDF を変換
163
+ img2ppt 資料.pdf -d C:\output --pdf-dpi 200
164
+
165
+ :: デバッグ: 全中間ファイルを残して詳細ログを表示
166
+ img2ppt スライド.jpg -d C:\output --debug --keep-tmp --verbose
167
+
168
+ :: クリップボード画像を変換してデスクトップに保存
169
+ img2ppt -d %USERPROFILE%\Desktop
170
+
171
+ :: OCR フィルタを緩めて誤除外を減らす
172
+ img2ppt スライド.jpg -d C:\output --ocr-threshold 0.35
173
+ ```
174
+
175
+ ---
176
+
177
+ ## うまくいかないときは
178
+
179
+ | 症状 | 試すこと |
180
+ | --- | --- |
181
+ | テキストが認識されない | `--ocr-threshold` を下げる(例: `0.35`) |
182
+ | 誤認識テキストが残る | `--ocr-threshold` を上げる(例: `0.55`) |
183
+ | 背景色で塗りつぶしがうまくいかない | `--sample-offset` や `--fill-expand` を調整する |
184
+ | 図形の背景が透過しすぎる / されない | `--bg-tolerance` を調整する(大きくすると透過が広がる) |
185
+ | 低彩度アイコンが検出されない | `--sat-max` を上げる(例: `50`) |
186
+ | 処理の途中経過を確認したい | `--debug --keep-tmp --verbose` を付けて実行する |
187
+
188
+ ---
189
+
190
+ ## パイプライン概要
191
+
192
+ | ステップ | モジュール | 処理内容 |
193
+ | --- | --- | --- |
194
+ | Step1 | `clarify.py` | 画像鮮明化(ノイズ除去・輪郭強調) |
195
+ | Step2 | `ocr.py` | Windows.Media.Ocr でテキスト検出・認識 |
196
+ | Step2.5 | `ocr_filter.py` | geometric / unicode / bigram / morph スコアで文字化けを除外 |
197
+ | Step3 | `split.py` | OCR bbox 後処理・複数テキスト混入 bbox を分割 |
198
+ | Step4 | `inpaint.py` | テキスト領域を背景色で除去・修復 |
199
+ | Step5 | `segment.py` | HSV 2値化 + 連結成分ラベリングでオブジェクト検出 |
200
+ | Step6 | `colorize.py` | container を投影プロファイル法で矩形分割・内包正規化 |
201
+ | Step7 | `pptx_gen.py` | JSON + 中間画像から PowerPoint スライドを生成 |
202
+
203
+ ---
204
+
205
+ ## 依存ライブラリ
206
+
207
+ | ライブラリ | ライセンス | 用途 |
208
+ | --- | --- | --- |
209
+ | `winrt-Windows.Media.Ocr` | MIT | テキスト検出・認識(Windows 標準 OCR) |
210
+ | `opencv-python` | Apache 2.0 | 画像処理 |
211
+ | `Pillow` | HPND(MIT互換) | 画像入出力 |
212
+ | `numpy` | BSD | 配列処理 |
213
+ | `python-pptx` | MIT | PowerPoint ファイル生成 |
214
+ | `pypdfium2` | Apache 2.0 | PDF → 画像変換 |
215
+ | `janome` | MIT | 形態素解析(OCR 品質フィルタ) |
216
+
217
+ ---
218
+
219
+ ## ライセンス
220
+
221
+ MIT License
@@ -0,0 +1,198 @@
1
+ # img2ppt
2
+
3
+ スライド画像(JPG/PNG)または PDF を PowerPoint(.pptx)に自動変換するツールです。
4
+ テキスト・図形・背景を自動認識し、16:9 スライドとして再構成します。
5
+
6
+ > **動作環境**: Windows 11 専用 / Python 3.12 以上
7
+
8
+ ---
9
+
10
+ ## インストール
11
+
12
+ ```cmd
13
+ pip install .
14
+ ```
15
+
16
+ これだけで全依存ライブラリ(OpenCV / Pillow / python-pptx / pypdfium2 / janome 等)が一括インストールされます。
17
+
18
+ ### インストール確認
19
+
20
+ ```cmd
21
+ img2ppt --help
22
+ ```
23
+
24
+ ---
25
+
26
+ ## 使い方
27
+
28
+ ### 基本
29
+
30
+ ```cmd
31
+ :: 画像ファイルを変換
32
+ img2ppt スライド.jpg -d 出力フォルダ
33
+
34
+ :: クリップボードの画像を変換(引数なし)
35
+ img2ppt -d 出力フォルダ
36
+
37
+ :: PDF を変換(全ページ → 1 ファイルの PPTX)
38
+ img2ppt 資料.pdf -d 出力フォルダ
39
+ ```
40
+
41
+ ### 入力モード
42
+
43
+ | モード | 方法 | 説明 |
44
+ | --- | --- | --- |
45
+ | ファイル指定 | `img2ppt <ファイルパス>` | JPG / PNG / PDF を直接指定 |
46
+ | クリップボード | `img2ppt`(引数なし) | 画像をコピーしてからそのまま実行 |
47
+ | PDF | 拡張子 `.pdf` のファイルを指定 | 全ページを 1 つの PPTX にまとめる |
48
+
49
+ ---
50
+
51
+ ## 出力ファイル
52
+
53
+ `-d` で指定したディレクトリに以下が出力されます。
54
+
55
+ | ファイル | 説明 |
56
+ | --- | --- |
57
+ | `{stem}.pptx` | **最終 PowerPoint** |
58
+ | `{stem}.json` | 認識結果の構造化 JSON |
59
+ | `{stem}_04_inpainted.png` | テキスト除去後の画像 |
60
+ | `{stem}_05_1_segment_filled.png` | セグメント検出結果(シルエット) |
61
+ | `{stem}_05_2_mask_segment.png` | セグメントマスク画像 |
62
+ | `{stem}_06_1_content_inpainted.png` | コンテンツ除去後の画像 |
63
+ | `{stem}_06_2_mask_colorize.png` | カラー分割マスク |
64
+ | `{stem}_06_3_colorized.png` | カラー分割結果 |
65
+
66
+ デバッグ用の可視化画像(`vis_` プレフィックス)は `--debug` を付けると追加出力されます。
67
+
68
+ ---
69
+
70
+ ## オプション一覧
71
+
72
+ ### 基本オプション
73
+
74
+ | オプション | デフォルト | 説明 |
75
+ | --- | --- | --- |
76
+ | `-d`, `--output-dir` | `test/` | 出力ディレクトリ |
77
+ | `-o`, `--output-json` | 自動 | 最終 JSON の出力パス(単一ページ時のみ有効) |
78
+ | `-v`, `--verbose` | オフ | 詳細ログを表示(省略時はプログレスバーのみ) |
79
+ | `--debug` | オフ | デバッグ用可視化画像(`vis_xxx`)を出力 |
80
+ | `--keep-tmp` | オフ | 処理後も一時ディレクトリを削除しない |
81
+ | `--tmp-dir` | システム一時領域 | 一時ディレクトリの作成場所を指定 |
82
+ | `--pdf-dpi` | `150` | PDF ページ変換の解像度(DPI) |
83
+
84
+ ### 出力パス個別指定
85
+
86
+ | オプション | 説明 |
87
+ | --- | --- |
88
+ | `--output-inpaint` | Step4 テキスト除去画像の出力パス |
89
+ | `--output-seg-filled` | Step5 シルエット画像の出力パス |
90
+ | `--output-mask-seg` | Step5 マスク画像の出力パス |
91
+
92
+ ### 画像鮮明化(Step1)
93
+
94
+ 低解像度・圧縮ノイズが多い画像の OCR 精度を上げるための前処理です。
95
+ PNG または長辺 1280px 以上の JPG は自動スキップされます。
96
+
97
+ | オプション | デフォルト | 説明 |
98
+ | --- | --- | --- |
99
+ | `--denoise-h` | `10` | ノイズ除去強度(大きいほど強く除去、ぼけやすい) |
100
+ | `--bilateral-d` | `9` | エッジ保持フィルタの近傍径 |
101
+ | `--sigma-color` | `60.0` | エッジ保持フィルタの色シグマ |
102
+ | `--sigma-space` | `60.0` | エッジ保持フィルタの空間シグマ |
103
+ | `--unsharp-radius` | `2` | 輪郭強調の半径(px) |
104
+ | `--unsharp-percent` | `200` | 輪郭強調の強度(%) |
105
+ | `--unsharp-threshold` | `2` | 輪郭強調の閾値 |
106
+
107
+ ### テキスト除去(Step4)
108
+
109
+ | オプション | デフォルト | 説明 |
110
+ | --- | --- | --- |
111
+ | `--sample-offset` | `3` | 背景色サンプリング位置(bbox 端から何 px 外側か) |
112
+ | `--fill-expand` | `2` | テキスト除去の塗り範囲拡張量(px) |
113
+
114
+ ### セグメント検出(Step5)
115
+
116
+ | オプション | デフォルト | 説明 |
117
+ | --- | --- | --- |
118
+ | `--sat-max` | `40` | 背景とみなす最大彩度(HSV-S、0〜255) |
119
+ | `--val-min` | `200` | 背景とみなす最小輝度(HSV-V、0〜255) |
120
+
121
+ ### OCR 品質フィルタ(Step2.5)
122
+
123
+ | オプション | デフォルト | 説明 |
124
+ | --- | --- | --- |
125
+ | `--ocr-min-height` | `8` | bbox 最小高さ(px)。これ未満は除外 |
126
+ | `--ocr-threshold` | `0.45` | 総合スコア閾値。これ未満のテキストを除外 |
127
+
128
+ ### PowerPoint 生成(Step7)
129
+
130
+ | オプション | デフォルト | 説明 |
131
+ | --- | --- | --- |
132
+ | `--bg-tolerance` | `20` | 図形の背景透過許容幅(RGB 各チャンネル ±n) |
133
+
134
+ ---
135
+
136
+ ## 実行例
137
+
138
+ ```cmd
139
+ :: 高解像度 PDF を変換
140
+ img2ppt 資料.pdf -d C:\output --pdf-dpi 200
141
+
142
+ :: デバッグ: 全中間ファイルを残して詳細ログを表示
143
+ img2ppt スライド.jpg -d C:\output --debug --keep-tmp --verbose
144
+
145
+ :: クリップボード画像を変換してデスクトップに保存
146
+ img2ppt -d %USERPROFILE%\Desktop
147
+
148
+ :: OCR フィルタを緩めて誤除外を減らす
149
+ img2ppt スライド.jpg -d C:\output --ocr-threshold 0.35
150
+ ```
151
+
152
+ ---
153
+
154
+ ## うまくいかないときは
155
+
156
+ | 症状 | 試すこと |
157
+ | --- | --- |
158
+ | テキストが認識されない | `--ocr-threshold` を下げる(例: `0.35`) |
159
+ | 誤認識テキストが残る | `--ocr-threshold` を上げる(例: `0.55`) |
160
+ | 背景色で塗りつぶしがうまくいかない | `--sample-offset` や `--fill-expand` を調整する |
161
+ | 図形の背景が透過しすぎる / されない | `--bg-tolerance` を調整する(大きくすると透過が広がる) |
162
+ | 低彩度アイコンが検出されない | `--sat-max` を上げる(例: `50`) |
163
+ | 処理の途中経過を確認したい | `--debug --keep-tmp --verbose` を付けて実行する |
164
+
165
+ ---
166
+
167
+ ## パイプライン概要
168
+
169
+ | ステップ | モジュール | 処理内容 |
170
+ | --- | --- | --- |
171
+ | Step1 | `clarify.py` | 画像鮮明化(ノイズ除去・輪郭強調) |
172
+ | Step2 | `ocr.py` | Windows.Media.Ocr でテキスト検出・認識 |
173
+ | Step2.5 | `ocr_filter.py` | geometric / unicode / bigram / morph スコアで文字化けを除外 |
174
+ | Step3 | `split.py` | OCR bbox 後処理・複数テキスト混入 bbox を分割 |
175
+ | Step4 | `inpaint.py` | テキスト領域を背景色で除去・修復 |
176
+ | Step5 | `segment.py` | HSV 2値化 + 連結成分ラベリングでオブジェクト検出 |
177
+ | Step6 | `colorize.py` | container を投影プロファイル法で矩形分割・内包正規化 |
178
+ | Step7 | `pptx_gen.py` | JSON + 中間画像から PowerPoint スライドを生成 |
179
+
180
+ ---
181
+
182
+ ## 依存ライブラリ
183
+
184
+ | ライブラリ | ライセンス | 用途 |
185
+ | --- | --- | --- |
186
+ | `winrt-Windows.Media.Ocr` | MIT | テキスト検出・認識(Windows 標準 OCR) |
187
+ | `opencv-python` | Apache 2.0 | 画像処理 |
188
+ | `Pillow` | HPND(MIT互換) | 画像入出力 |
189
+ | `numpy` | BSD | 配列処理 |
190
+ | `python-pptx` | MIT | PowerPoint ファイル生成 |
191
+ | `pypdfium2` | Apache 2.0 | PDF → 画像変換 |
192
+ | `janome` | MIT | 形態素解析(OCR 品質フィルタ) |
193
+
194
+ ---
195
+
196
+ ## ライセンス
197
+
198
+ MIT License
@@ -0,0 +1,3 @@
1
+ """img2ppt — スライド画像/PDF → PowerPoint 自動変換パイプライン"""
2
+
3
+ __version__ = "1.3.4"
@@ -0,0 +1,3 @@
1
+ from img2ppt.cli import main
2
+
3
+ main()
@@ -0,0 +1,259 @@
1
+ """
2
+ 画像鮮明化モジュール
3
+
4
+ イラスト・図版・JPG画像向けに以下の処理を順に適用する:
5
+ 1. fastNlMeansDenoisingColored : JPEGブロックノイズ・輝度ムラを除去
6
+ 2. bilateralFilter : 輪郭を保持しつつ色むら・揺らぎを平滑化
7
+ 3. UnsharpMask : エッジ・輪郭線を鮮明化
8
+
9
+ --roi を指定した場合は対象領域のみ処理して元画像に合成する。
10
+ 省略した場合は画像全体を処理する。アルファチャンネルは保持される。
11
+ """
12
+
13
+ import argparse
14
+ import shutil
15
+ import sys
16
+ from pathlib import Path
17
+
18
+ import cv2
19
+ import numpy as np
20
+ from PIL import Image, ImageFilter
21
+
22
+
23
+ def _process_region(
24
+ bgr: np.ndarray,
25
+ denoise_h: int,
26
+ denoise_template: int,
27
+ denoise_search: int,
28
+ bilateral_d: int,
29
+ bilateral_sigma_color: float,
30
+ bilateral_sigma_space: float,
31
+ unsharp_radius: int,
32
+ unsharp_percent: int,
33
+ unsharp_threshold: int,
34
+ ) -> np.ndarray:
35
+ """BGR配列に鮮明化処理を適用してBGR配列で返す。"""
36
+
37
+ # ── Step1: ノイズ除去 ────────────────────────────────────────────────
38
+ # denoise_h : ノイズ除去強度。大きいほど強くノイズを除去するが、細部がぼける。
39
+ # JPEGアーティファクトが目立つ画像では 10〜15、きれいな画像は 5 程度。
40
+ # denoise_template: テンプレートウィンドウサイズ(px、奇数)。大きいほど精度が上がるが遅い。通常 7。
41
+ # denoise_search : 探索ウィンドウサイズ(px、奇数)。大きいほど広範囲を比較して精度UP。通常 21。
42
+ denoised = cv2.fastNlMeansDenoisingColored(
43
+ bgr,
44
+ h=denoise_h,
45
+ hColor=denoise_h,
46
+ templateWindowSize=denoise_template,
47
+ searchWindowSize=denoise_search,
48
+ )
49
+
50
+ # ── Step2: バイラテラルフィルタ ──────────────────────────────────────
51
+ # 輪郭(エッジ)を保持しながら色むら・ノイズを平滑化する。
52
+ # bilateral_d : フィルタの近傍径(px)。大きいほど広範囲を平滑化するが遅い。通常 9。
53
+ # bilateral_sigma_color: 色空間のシグマ値。大きいほど色の違いを無視して平滑化する。
54
+ # 小さいと色の差が少し違うだけで別エッジとみなし、強いシャープ感が残る。
55
+ # 通常 60〜80。大きくすると塗り絵的な質感になる。
56
+ # bilateral_sigma_space: 座標空間のシグマ値。大きいほど遠くのピクセルも参照する。
57
+ # 通常 60〜80。大きくすると広域にぼかしが広がる。
58
+ bilateral = cv2.bilateralFilter(
59
+ denoised,
60
+ d=bilateral_d,
61
+ sigmaColor=bilateral_sigma_color,
62
+ sigmaSpace=bilateral_sigma_space,
63
+ )
64
+
65
+ # ── Step3: アンシャープマスク(輪郭強調) ────────────────────────────
66
+ # ぼかした画像との差分を元画像に加算してエッジを際立たせる。
67
+ # unsharp_radius : ぼかし半径(px)。大きいほど広いエッジを強調する。通常 1〜3。
68
+ # 大きくすると輪郭が太くなり、小さくすると細かいテクスチャが強調される。
69
+ # unsharp_percent : 強調強度(%)。100が等倍加算、200で2倍加算。
70
+ # 上げるほどシャープになるが、ノイズや縞も強調されてしまう。
71
+ # 通常 150〜200。OCR前処理として使う場合は高め(160〜200)が有効。
72
+ # unsharp_threshold: この輝度差(0〜255)以下のエッジは強調しない。
73
+ # 0にするとノイズも強調される。2〜5 程度が無難。
74
+ pil = Image.fromarray(cv2.cvtColor(bilateral, cv2.COLOR_BGR2RGB))
75
+ sharpened = pil.filter(
76
+ ImageFilter.UnsharpMask(
77
+ radius=unsharp_radius,
78
+ percent=unsharp_percent,
79
+ threshold=unsharp_threshold,
80
+ )
81
+ )
82
+ return cv2.cvtColor(np.array(sharpened), cv2.COLOR_RGB2BGR)
83
+
84
+
85
+ def clarify(
86
+ src: Path,
87
+ dst: Path,
88
+ roi: tuple[int, int, int, int] | None = None,
89
+ skip_if_clean: bool = True,
90
+ # ノイズ除去強度: 上げると強くノイズを消すが細部がぼける(JPEGアーティファクトが強い画像は 10〜15)
91
+ denoise_h: int = 10,
92
+ # テンプレートウィンドウ: 通常 7 固定で問題なし
93
+ denoise_template: int = 7,
94
+ # 探索ウィンドウ: 通常 21 固定で問題なし(大きくすると精度UP・速度DOWN)
95
+ denoise_search: int = 21,
96
+ # バイラテラル近傍径: 大きくすると広範囲平滑化(遅くなる)。通常 9
97
+ bilateral_d: int = 9,
98
+ # 色シグマ: 大きくすると色差を無視して平滑化。通常 60〜80
99
+ bilateral_sigma_color: float = 60.0,
100
+ # 座標シグマ: 大きくすると広域にぼかし。通常 60〜80
101
+ bilateral_sigma_space: float = 60.0,
102
+ # アンシャープ半径: 大きくすると太いエッジを強調。通常 2
103
+ unsharp_radius: int = 2,
104
+ # アンシャープ強度(%): 上げるほどシャープ。OCR前処理は 160〜200 が有効
105
+ unsharp_percent: int = 200,
106
+ # アンシャープ閾値: この輝度差以下のエッジは無視(ノイズ抑制)。通常 2
107
+ unsharp_threshold: int = 2,
108
+ ) -> Path:
109
+ """
110
+ 画像を鮮明化して dst に保存し、dst を返す。
111
+
112
+ roi: (x1, y1, x2, y2) を指定した場合はその領域のみ処理。
113
+ None の場合は画像全体を処理。
114
+ skip_if_clean: True の場合、高品質画像はスキップ(元画像をコピー)。
115
+ - PNG ファイル(ロスレス)
116
+ - JPG/JPEG で1280px以上の解像度
117
+ """
118
+ raw = np.frombuffer(src.read_bytes(), dtype=np.uint8)
119
+ img_bgr = cv2.imdecode(raw, cv2.IMREAD_UNCHANGED)
120
+ if img_bgr is None:
121
+ raise FileNotFoundError(f"画像を読み込めません: {src}")
122
+
123
+ has_alpha = img_bgr.ndim == 3 and img_bgr.shape[2] == 4
124
+ if has_alpha:
125
+ bgr, alpha = img_bgr[:, :, :3], img_bgr[:, :, 3]
126
+ else:
127
+ bgr, alpha = img_bgr, None
128
+
129
+ h, w = bgr.shape[:2]
130
+
131
+ # ── スキップ判定:高品質画像の場合は元画像をコピーして返す ────────────
132
+ if skip_if_clean and roi is None:
133
+ suffix = src.suffix.lower()
134
+ is_png = suffix == ".png"
135
+ is_high_res_jpg = suffix in [".jpg", ".jpeg"] and max(h, w) >= 1280
136
+ if is_png or is_high_res_jpg:
137
+ dst.parent.mkdir(parents=True, exist_ok=True)
138
+ shutil.copy2(src, dst)
139
+ print(f"鮮明化スキップ: {src.name} ({w}×{h} px) ← 高品質ファイル")
140
+ return dst
141
+ kwargs = dict(
142
+ denoise_h=denoise_h,
143
+ denoise_template=denoise_template,
144
+ denoise_search=denoise_search,
145
+ bilateral_d=bilateral_d,
146
+ bilateral_sigma_color=bilateral_sigma_color,
147
+ bilateral_sigma_space=bilateral_sigma_space,
148
+ unsharp_radius=unsharp_radius,
149
+ unsharp_percent=unsharp_percent,
150
+ unsharp_threshold=unsharp_threshold,
151
+ )
152
+
153
+ if roi is None:
154
+ result_bgr = _process_region(bgr, **kwargs)
155
+ print(f"処理範囲: 画像全体 ({w}×{h} px)")
156
+ else:
157
+ x1, y1, x2, y2 = (
158
+ max(0, roi[0]), max(0, roi[1]),
159
+ min(w, roi[2]), min(h, roi[3]),
160
+ )
161
+ result_bgr = bgr.copy()
162
+ result_bgr[y1:y2, x1:x2] = _process_region(bgr[y1:y2, x1:x2].copy(), **kwargs)
163
+ print(f"処理 ROI : x={x1}-{x2}, y={y1}-{y2} ({x2-x1}×{y2-y1} px)")
164
+
165
+ result_rgb = cv2.cvtColor(result_bgr, cv2.COLOR_BGR2RGB)
166
+ out = Image.fromarray(result_rgb)
167
+
168
+ if alpha is not None:
169
+ out = out.convert("RGBA")
170
+ out.putalpha(Image.fromarray(alpha))
171
+
172
+ dst.parent.mkdir(parents=True, exist_ok=True)
173
+ out.save(str(dst))
174
+ print(f"保存完了: {dst}")
175
+ return dst
176
+
177
+
178
+ def main() -> None:
179
+ parser = argparse.ArgumentParser(
180
+ description="画像鮮明化ツール(全体 / ROI 指定)",
181
+ formatter_class=argparse.RawDescriptionHelpFormatter,
182
+ epilog="""
183
+ パラメータ調整ガイド:
184
+ --denoise-h ノイズ除去強度。上げるとJPEGノイズが消えるが細部もぼける。
185
+ JPEGが荒い画像: 10〜15 / きれいな画像: 5 程度
186
+ --bilateral-d バイラテラルフィルタ近傍径。上げると広域を平滑化するが遅くなる。通常: 9
187
+ --sigma-color 色シグマ。上げると色差を無視して広く平滑化。通常: 60〜80
188
+ --sigma-space 座標シグマ。上げると遠くのピクセルまでぼかしが広がる。通常: 60〜80
189
+ --unsharp-radius アンシャープ半径。上げると太いエッジを強調。通常: 2
190
+ --unsharp-percent アンシャープ強度(%)。上げるほどシャープ。OCR前処理: 160〜200
191
+ --unsharp-threshold アンシャープ閾値。この輝度差以下のエッジは無視。ノイズ抑制: 2〜5
192
+ """,
193
+ )
194
+ parser.add_argument("input", type=Path, help="入力画像パス")
195
+ parser.add_argument(
196
+ "-o", "--output", type=Path, default=None,
197
+ help="出力画像パス(省略時: 入力ファイル名に _clarified を付加)",
198
+ )
199
+ parser.add_argument(
200
+ "--roi", nargs=4, type=int, metavar=("X1", "Y1", "X2", "Y2"),
201
+ default=None, help="処理対象領域 (ピクセル座標)。省略時は全体を処理",
202
+ )
203
+ parser.add_argument(
204
+ "--denoise-h", type=int, default=10,
205
+ help="ノイズ除去強度 (デフォルト: 10)。上げると強くノイズを消すが細部がぼける",
206
+ )
207
+ parser.add_argument(
208
+ "--bilateral-d", type=int, default=9,
209
+ help="バイラテラルフィルタの近傍径px (デフォルト: 9)。上げると広範囲平滑化・処理が遅くなる",
210
+ )
211
+ parser.add_argument(
212
+ "--sigma-color", type=float, default=60.0,
213
+ help="色空間のシグマ値 (デフォルト: 60)。上げると色差を無視して平滑化、塗り絵的な質感になる",
214
+ )
215
+ parser.add_argument(
216
+ "--sigma-space", type=float, default=60.0,
217
+ help="座標空間のシグマ値 (デフォルト: 60)。上げると広域にぼかしが広がる",
218
+ )
219
+ parser.add_argument(
220
+ "--unsharp-radius", type=int, default=2,
221
+ help="アンシャープマスクの半径px (デフォルト: 2)。上げると太いエッジを強調、下げると細かいテクスチャが強調",
222
+ )
223
+ parser.add_argument(
224
+ "--unsharp-percent", type=int, default=200,
225
+ help="アンシャープマスクの強度%% (デフォルト: 200)。上げるほどシャープ、ノイズも強調されるので注意",
226
+ )
227
+ parser.add_argument(
228
+ "--unsharp-threshold", type=int, default=2,
229
+ help="アンシャープマスクの閾値 (デフォルト: 2)。この輝度差以下のエッジは無視してノイズ抑制",
230
+ )
231
+ args = parser.parse_args()
232
+
233
+ src = args.input.resolve()
234
+ if not src.exists():
235
+ print(f"エラー: ファイルが見つかりません: {src}", file=sys.stderr)
236
+ sys.exit(1)
237
+
238
+ dst = (
239
+ args.output.resolve()
240
+ if args.output
241
+ else src.with_stem(src.stem + "_clarified")
242
+ )
243
+
244
+ clarify(
245
+ src=src,
246
+ dst=dst,
247
+ roi=tuple(args.roi) if args.roi else None,
248
+ denoise_h=args.denoise_h,
249
+ bilateral_d=args.bilateral_d,
250
+ bilateral_sigma_color=args.sigma_color,
251
+ bilateral_sigma_space=args.sigma_space,
252
+ unsharp_radius=args.unsharp_radius,
253
+ unsharp_percent=args.unsharp_percent,
254
+ unsharp_threshold=args.unsharp_threshold,
255
+ )
256
+
257
+
258
+ if __name__ == "__main__":
259
+ main()