cv-data-lesson 0.1.0a1__py3-none-any.whl
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.
- cv_data_lesson-0.1.0a1.dist-info/METADATA +257 -0
- cv_data_lesson-0.1.0a1.dist-info/RECORD +14 -0
- cv_data_lesson-0.1.0a1.dist-info/WHEEL +5 -0
- cv_data_lesson-0.1.0a1.dist-info/licenses/LICENSE +21 -0
- cv_data_lesson-0.1.0a1.dist-info/top_level.txt +1 -0
- cvlesson/__init__.py +4 -0
- cvlesson/audit.py +192 -0
- cvlesson/datasets.py +2347 -0
- cvlesson/display.py +165 -0
- cvlesson/generation.py +260 -0
- cvlesson/loras.py +324 -0
- cvlesson/models.py +385 -0
- cvlesson/ocr.py +274 -0
- cvlesson/vision.py +287 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cv-data-lesson
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: 高校生向け画像データとコンピュータビジョン学習ライブラリ
|
|
5
|
+
Author: CV Data project contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/708san/CV_data
|
|
8
|
+
Project-URL: Documentation, https://github.com/708san/CV_data#readme
|
|
9
|
+
Project-URL: Artifacts, https://github.com/708san/CV_data_artifacts
|
|
10
|
+
Keywords: education,computer-vision,dataset,colab,japanese
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Natural Language :: Japanese
|
|
16
|
+
Classifier: Development Status :: 3 - Alpha
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Requires-Python: <3.14,>=3.11
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Provides-Extra: test
|
|
22
|
+
Requires-Dist: pandas<4,>=1.5; extra == "test"
|
|
23
|
+
Requires-Dist: Pillow<14,>=9; extra == "test"
|
|
24
|
+
Provides-Extra: dataframe
|
|
25
|
+
Requires-Dist: pandas<4,>=1.5; extra == "dataframe"
|
|
26
|
+
Provides-Extra: display
|
|
27
|
+
Requires-Dist: numpy<3,>=1.23; extra == "display"
|
|
28
|
+
Requires-Dist: Pillow<14,>=9; extra == "display"
|
|
29
|
+
Requires-Dist: pandas<4,>=1.5; extra == "display"
|
|
30
|
+
Requires-Dist: matplotlib<4,>=3.6; extra == "display"
|
|
31
|
+
Requires-Dist: japanize-matplotlib<2,>=1.1.3; extra == "display"
|
|
32
|
+
Provides-Extra: torch
|
|
33
|
+
Requires-Dist: numpy<3,>=1.23; extra == "torch"
|
|
34
|
+
Requires-Dist: Pillow<14,>=9; extra == "torch"
|
|
35
|
+
Requires-Dist: torch<3,>=2.6; extra == "torch"
|
|
36
|
+
Requires-Dist: torchvision<1,>=0.21; extra == "torch"
|
|
37
|
+
Provides-Extra: ocr
|
|
38
|
+
Requires-Dist: paddleocr==3.1.0; extra == "ocr"
|
|
39
|
+
Requires-Dist: paddlex[ocr]<3.2.0,>=3.1.0; extra == "ocr"
|
|
40
|
+
Requires-Dist: langchain<1,>=0.2; extra == "ocr"
|
|
41
|
+
Requires-Dist: requests<2.33,>=2.32.2; extra == "ocr"
|
|
42
|
+
Requires-Dist: huggingface_hub<3,>=0.23; extra == "ocr"
|
|
43
|
+
Requires-Dist: Pillow<14,>=9; extra == "ocr"
|
|
44
|
+
Provides-Extra: vision
|
|
45
|
+
Requires-Dist: numpy<3,>=1.23; extra == "vision"
|
|
46
|
+
Requires-Dist: Pillow<14,>=9; extra == "vision"
|
|
47
|
+
Requires-Dist: transformers<6,>=4.41; extra == "vision"
|
|
48
|
+
Requires-Dist: huggingface_hub<3,>=0.23; extra == "vision"
|
|
49
|
+
Provides-Extra: diffusion
|
|
50
|
+
Requires-Dist: Pillow<14,>=9; extra == "diffusion"
|
|
51
|
+
Requires-Dist: diffusers[torch]<1,>=0.30; extra == "diffusion"
|
|
52
|
+
Requires-Dist: transformers<6,>=4.41; extra == "diffusion"
|
|
53
|
+
Requires-Dist: huggingface_hub<3,>=0.23; extra == "diffusion"
|
|
54
|
+
Requires-Dist: peft<1,>=0.21.2; extra == "diffusion"
|
|
55
|
+
Dynamic: license-file
|
|
56
|
+
|
|
57
|
+
# CV Data Lesson — 高校生向け画像データ・CV学習ライブラリ
|
|
58
|
+
|
|
59
|
+
画像データの取得と、分類・回帰・OCR・物体検出・領域分割・画像生成を簡潔に試すためのPythonライブラリです。データの利用条件や形式を日本語で確認できます。
|
|
60
|
+
|
|
61
|
+
**初回公開候補は`0.1.0a1`(Alpha)です。現時点ではPyPI未公開です。** コードはMITライセンスです。データとモデルは個別の利用条件で提供され、画像・マスク・LoRA重みはPyPIに含めません。データは固定Releaseから、モデルは配布元から、利用者が機能を明示的に使うときに取得します。既存の[CV_sample_data](https://github.com/708san/CV_sample_data)は読み取り専用です。
|
|
62
|
+
|
|
63
|
+
## インストール
|
|
64
|
+
|
|
65
|
+
PyPIで公開された後は、機能に応じて次のように導入できます。`display`を使う場合の例です。
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
python -m pip install --pre 'cv-data-lesson[display]'
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**このalpha版はまだ公開されていないため、現時点では上のコマンドでインストールできません。** リポジトリから開発する場合は、ルートフォルダで次を実行します。
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
python -m pip install -e '.[display]'
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
詳細は[まず試す](docs/quickstart.md)と[インストール案内](docs/installation.md)を参照してください。初回PyPI公開の準備状況は[PyPI公開準備](docs/pypi-release.md)にまとめています。
|
|
78
|
+
|
|
79
|
+
PythonまたはGoogle Colabで、次のように操作します。
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from cvlesson import datasets
|
|
83
|
+
|
|
84
|
+
print(datasets.list_available())
|
|
85
|
+
|
|
86
|
+
# 課題と対象者から選ぶ(生徒向けには承認済みデータだけ)
|
|
87
|
+
print(datasets.list_tasks(audience="student"))
|
|
88
|
+
choices = datasets.list_available(task="classification", audience="student")
|
|
89
|
+
print(choices)
|
|
90
|
+
|
|
91
|
+
# 使うIDをchoicesから選ぶ。データ本体はget()を呼んだ時だけ取得
|
|
92
|
+
dataset_id = choices[0]["id"]
|
|
93
|
+
print(datasets.info(dataset_id))
|
|
94
|
+
data = datasets.get(dataset_id)
|
|
95
|
+
print(data.root) # 保存先
|
|
96
|
+
print(data.labels_csv) # 正解ラベル
|
|
97
|
+
print(data.train_dir) # 学習用画像
|
|
98
|
+
print(data.test_dir) # テスト用画像
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
分類データのラベルと分割はDataFrameで確認できます。
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
data = datasets.get(dataset_id)
|
|
105
|
+
df = data.to_df() # pip install -e '.[dataframe]'
|
|
106
|
+
print(df[["label_ja", "split"]].head())
|
|
107
|
+
print(data.splits) # train / val / test
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
分類データはPyTorchのDatasetにも変換できます。画像は必要なときだけ読み込むため、全画像を最初からメモリへ載せません。
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
train_data = data.to_torch_dataset(split="train", image_size=(64, 64)) # pip install -e '.[torch]'
|
|
114
|
+
画像, ラベル番号 = train_data[0]
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
キャッシュの容量は確認でき、使い終わったデータは削除できます。既定の保存先はリポジトリの外です。削除後は次回の取得時に再ダウンロードします。
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
print(datasets.cache_info()) # 登録済みデータの保存量を確認
|
|
121
|
+
result = datasets.clear_cache(dataset_id) # 指定データだけ削除
|
|
122
|
+
print(result["removed_bytes"])
|
|
123
|
+
datasets.clear_cache() # 登録済みデータのキャッシュをまとめて削除
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
文化祭アイコンは教師向けの権利確認中プレビューです。通常の一覧には出ず、明示指定した場合だけ取得できます。改変・商用利用・再配布の許可は確認できていません。
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
icons = datasets.get("festival_icons", allow_preview=True)
|
|
130
|
+
print(icons.images_dir)
|
|
131
|
+
print(icons.metadata_jsonl) # LoRA学習用の画像説明文
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`datasets.get()`は固定Releaseを取得してZIPを検証し、ローカルに展開します。保存先の変更は`cache_dir`で指定できます。元の公式testは変更せず、DataFrameではtrainから再現可能なvalを作ります。画像表示と日本語ラベル表示には `pip install -e '.[display]'` が必要です。このextraにはサンプル一覧に必要なpandasも含みます。LoRA学習そのものやモデル重みの再配布は、このライブラリの対象外です。
|
|
135
|
+
|
|
136
|
+
検出・OCR・領域分割の結果は`cvlesson.display.show()`で元画像に重ねて表示でき、`cvlesson.display.save()`で保存できます。合成画像はPillow Imageとして返るため、表示以外の処理にも渡せます。
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from cvlesson import display, vision
|
|
140
|
+
|
|
141
|
+
print(vision.available_devices()) # GPUの利用可否を確認
|
|
142
|
+
detector = vision.load_detector(device="cpu") # GPUを使う場合は確認後にdevice="cuda:0"
|
|
143
|
+
image_path = "street.jpg"
|
|
144
|
+
detections = detector.predict(image_path)
|
|
145
|
+
display.show(image_path, detections)
|
|
146
|
+
display.save(image_path, "results/detections.png", detections)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
日本語OCRは必要なときだけ`ocr` extraと環境に合うPaddlePaddleを導入して使います。`ocr.load()`が必要なモジュールを内部で読み込むため、利用者が`import paddle`を書く必要はありません。モデルは明示的な`ocr.load()`時に取得し、CPUを既定にします。GPUを使う場合は利用可否を確認して明示指定します。導入方法と返却形式は[OCRの使い方](docs/ocr.md)をご覧ください。
|
|
150
|
+
|
|
151
|
+
物体検出・領域分割は`from cvlesson import vision`から使います。PyTorchを環境に合わせて導入した後、`vision` extraを追加します。固定版のDETR(COCO 80カテゴリ検出)とSAM(枠・点からのマスク生成)を利用できます。機能範囲・容量・限界・実環境の検証状況は[検出と領域分割の使い方](docs/vision.md)を確認してください。
|
|
152
|
+
|
|
153
|
+
Colabでモデルを試す場合は、[DETR・SAMのGPU確認Notebook](https://colab.research.google.com/github/708san/CV_data/blob/main/notebooks/colab/vision_smoke_test.ipynb)を開いてT4ランタイムで実行します。OCRは依存環境を分け、[日本語OCRのCPU確認Notebook](https://colab.research.google.com/github/708san/CV_data/blob/main/notebooks/colab/ocr_smoke_test.ipynb)を新しいCPUランタイムで実行します。OCRとDETR/SAMの合成画像による推論はColabで確認済みです。実データでのモデル精度は別途検証が必要です。
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from cvlesson import vision
|
|
157
|
+
|
|
158
|
+
detector = vision.load_detector() # CPUが既定
|
|
159
|
+
objects = detector.predict("street.jpg", threshold=0.6)
|
|
160
|
+
for item in objects:
|
|
161
|
+
print(item.label, item.box)
|
|
162
|
+
|
|
163
|
+
# 枠から対象の形をマスクにする(SAM自体は名前を付けない)
|
|
164
|
+
segmenter = vision.load_segmenter()
|
|
165
|
+
if objects:
|
|
166
|
+
mask = segmenter.predict("street.jpg", box=objects[0].box)
|
|
167
|
+
print(mask.mask.shape)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## データの説明
|
|
171
|
+
|
|
172
|
+
| データ | 内容 | 取得元 | 利用条件 |
|
|
173
|
+
| --- | --- | --- | --- |
|
|
174
|
+
| [トマトの葉](docs/datasets/tomato_leaves.md) | トマトの葉7,209枚、5種類のラベル | `tomato-v1.2.0` | 元データ:CC0 1.0 |
|
|
175
|
+
| [FruitSeg30](docs/datasets/fruitseg30.md) | 果物29クラス、画像・マスク1,914組 | `fruitseg30-v0.1.0` | CC BY 4.0、出典・変更表示が必要 |
|
|
176
|
+
| [Road Traversing Knowledge](docs/datasets/road_traversing_knowledge.md) | 道路環境の13画素クラス、画像・マスク701組 | `road-traversing-knowledge-v0.1.0` | CC BY 4.0、出典・変更表示が必要 |
|
|
177
|
+
| [LeFood-Set](docs/datasets/lefood_set.md) | 食前・食後の料理画像524組、実測重量と見た目評価 | `lefood-set-v0.1.0` | CC BY 4.0、出典・変更表示が必要 |
|
|
178
|
+
| [Fresh and Rotten Fruits](docs/datasets/fresh_rotten_fruits.md) | 果物8種類のfresh/rotten画像3,193枚、16クラス | `fresh-rotten-fruits-v0.1.0` | CC BY 4.0、出典・変更表示が必要。食品安全の判定には使わない |
|
|
179
|
+
| [RDD2020 Japan](docs/datasets/rdd2020_japan.md) | 道路損傷4種類、画像7,900枚・bbox 16,469個 | `rdd2020-japan-v0.1.0` | CC BY-NC 3.0、非商用教育利用のみ。出典・変更表示が必要 |
|
|
180
|
+
| [観葉植物の種類](docs/datasets/indoor_plant_varieties.md) | 観葉植物7種類、重複除外後1,162枚 | `indoor-plant-varieties-v0.1.0` | CC BY 4.0、出典・変更表示が必要 |
|
|
181
|
+
| [廃棄物品](docs/datasets/bangladeshi_ewaste_image.md) | 廃棄物品12種類、画像2,157枚・位置注釈 | `bangladeshi-ewaste-v0.1.0` | CC BY 4.0、実際の分別判断には使わない |
|
|
182
|
+
| [GeoSetEnnea](docs/datasets/geoset_ennea.md) | 製図用具9種類、画像136枚・位置注釈1,188件 | `geoset-ennea-v0.1.0` | CC BY 4.0、出典・変更表示が必要 |
|
|
183
|
+
| [FabricSpotDefect](docs/datasets/fabricspotdefect.md) | 衣類の汚れなど1クラス、元画像1,014枚・位置注釈3,288件 | `fabricspotdefect-v0.1.0` | CC BY 4.0、出典・変更表示が必要。着用可否・安全性の判定には使わない |
|
|
184
|
+
| [Orange Fruit](docs/datasets/orange_fruit.md) | オレンジの見た目5カテゴリ、画像15,000枚 | `orange-fruit-v0.1.0` | CC BY 4.0、出典・変更表示が必要。鮮度・食用可否・食品安全・病気の判定には使わない |
|
|
185
|
+
| [日本語レシートOCR(詳細版)](docs/datasets/japanese_receipts_detailed.md) | レシート画像100枚、領域ラベル2,005件・文字転記1,802件 | `japanese_receipts_detailed-v0.1.0` | CC BY-NC 4.0、非商用のみ。個人情報の黒塗りは一部 |
|
|
186
|
+
| [屋内に残ったごみの位置](docs/datasets/indoor_waste_image.md) | 屋内ごみ4種類、4,000画像・6,968枠 | `indoor-waste-image-v0.1.0` | CC BY 4.0、地域の分別は別に確認 |
|
|
187
|
+
| [家庭ごみの素材分類(TrashNet)](docs/datasets/trashnet.md) | 6種類、2,527枚 | `trashnet-v0.1.0` | MIT。白背景で撮影。地域の分別可否は判定しない |
|
|
188
|
+
| [食品包装OCR(ViFoodLabel)](docs/datasets/vifoodlabel.md) | 9項目の正解付き600枚 | `vifoodlabel-v0.1.0` | CC BY-NC 4.0、非商用のみ。項目別評価用 |
|
|
189
|
+
| [電力量計の数字認識](docs/datasets/energy_meter_digits.md) | 0〜9の切り出し977枚 | `energy-meter-digits-v0.1.0` | CC0 1.0。全体の検針値は読まない |
|
|
190
|
+
| [空画像と翌日の天気](docs/datasets/sky_weather_next_day.md) | 全天空画像1,000枚、晴れ・曇り・雨と翌日の気象値 | `sky-weather-next-day-v0.1.0` | CC BY 4.0、Open-Meteo無料API条件は非商用。Coloradoの1地点・ERA5再解析値 |
|
|
191
|
+
| [水辺のごみ位置](docs/datasets/wastesegcluster.md) | 水辺ごみ4種類、1,947画像・3,900枠 | `wastesegcluster-v0.1.0` | CC BY 4.0、ダッカ周辺の画像 |
|
|
192
|
+
| [火・煙の教材画像](docs/datasets/fire_recognition_image.md) | 4カテゴリ、元画像1,112枚 | `fire-recognition-image-v0.1.0` | CC BY 4.0、「安全」判断には使わない |
|
|
193
|
+
| [青果の見た目の段階](docs/datasets/agrifreshnet.md) | 24カテゴリ、重複除外後14,084枚 | `agrifreshnet-v0.1.0` | CC BY 4.0、食用可否の判定には使わない |
|
|
194
|
+
| [道路の穴の領域分割](docs/datasets/brazil_road_potholes.md) | ブラジルの道路画像2,235枚・穴マスク2,235枚 | `brazil-road-potholes-v0.1.0` | CC BY 4.0、現地・現在の道路安全性は判定しない |
|
|
195
|
+
| [CLICK Trash Dataset](docs/datasets/click_trash.md) | 屋外ごみ1,126枚・位置注釈2,213件 | `click-trash-v0.1.0` | CC BY 4.0、インド撮影。日本の地域や分別ルールを代表しない |
|
|
196
|
+
| [歩道上の物体候補](docs/datasets/obstacles_assistance.md) | 4種類、画像1,250枚・bbox 1,627件 | `obstacles-assistance-v0.1.0` | CC BY 4.0。歩道の通行障害や安全の判定には使わない |
|
|
197
|
+
| [文化祭アイコン](docs/datasets/festival_icons.md) | アイコン168枚、画像と説明文 | `festival-v1.0.0` | 公開許可あり。改変・商用利用・再配布は要確認 |
|
|
198
|
+
|
|
199
|
+
トマトの葉と文化祭アイコンは固定Releaseを全量監査済みです。実測値は[トマトの監査レポート](reports/tomato-leaves-v1.2.0.json)と[文化祭アイコンの監査レポート](reports/festival-icons-v1.0.0.json)に保存しています。これは権利の承認を意味しません。
|
|
200
|
+
|
|
201
|
+
生徒向けに取得可能なデータは23件です。空画像と翌日の天気データを追加しました。取り下げたデータの出典・監査記録・固定Releaseは履歴として保持します。
|
|
202
|
+
|
|
203
|
+
Recyclable Waste v0.1.1の整形済みRelease assetは公開されていますが、包装上の肖像写真や商品表示を含む第三者権利審査が未完了のため、生徒向けAPIから外しています([審査記録](docs/datasets/recyclable_waste.md))。
|
|
204
|
+
|
|
205
|
+
観葉植物データは元画像1,172枚を全量デコードし、完全一致重複10枚を除外しました。GPSを含むEXIF情報を取り除き、長辺512px以下にそろえた1,162枚を取得できます。7種類の画像分類や、撮影条件による予測の違いを調べられます。使い方の例として植物画像の整理や観察記録を作れますが、有毒性・食用・病気・育て方の判定はできません。
|
|
206
|
+
|
|
207
|
+
FruitSeg30は全量監査後、寸法が一致しないGuavaクラスを除いた29クラス1,914組を整備し、固定Releaseを公開しました。GitHub mainブランチでは取得APIを利用できます。PyPI配布版への反映はパッケージ更新時に行います。`to_torch_dataset()`は画像と濃淡マスクを返します。
|
|
208
|
+
|
|
209
|
+
Road Traversing Knowledgeは701組の画像・画素IDマスクを含む領域分割データです。`data.to_torch_dataset()`は各画素を0〜12の整数クラスIDとして保持し、マスクを`torch.long`の高さ×幅テンソルで返します。背景を含め13クラスの出力を使います。公式test splitはありません。ブラジルの道路画像を日本の通学路や安全性の判定に使わないでください。
|
|
210
|
+
|
|
211
|
+
Brazil Road Potholesは2,235組の道路画像・二値マスクを含みます。`data.to_torch_dataset()`は元の穴領域を0〜1の濃淡マスクとして返します。同一区間の画像をsplit間で共有しないよう区間単位で分けています。ブラジルの古い道路画像で、現在の日本の道路や安全性には一般化できません。
|
|
212
|
+
|
|
213
|
+
CLICK Trashは屋外のごみ画像1,126枚と位置注釈2,213件を含みます。`datasets.get("click_trash")`で清掃活動の場所を画像上に示す試作へ使えます。インドの画像なので、日本の地域で使う前に現地画像でも確認してください。分別や危険性の判定には使いません。
|
|
214
|
+
|
|
215
|
+
Obstacles Avoidance Assistanceは段差・柵・穴・ポールを囲むbboxを収録します。`datasets.get("obstacles_assistance")`で歩道上の物体候補を記録する地図を試作できます。写った物体が通行の妨げかは、通路幅・経路・利用者の状況を含めて人が確認してください。実際の移動支援には単独で使わず、地域の画像と当事者の確認が必要です。
|
|
216
|
+
|
|
217
|
+
許諾済みデータの原本・派生データ・manifestは、コードとは分離した[CV_data_artifacts](https://github.com/708san/CV_data_artifacts)で管理します。PyPIとこのリポジトリには画像・LoRA・学習済み重みを収録しません。データを追加する際は、出典・ライセンス・固定版・SHA-256・変換履歴・監査結果をそろえてから、データ専用リポジトリのRelease assetとして公開します。
|
|
218
|
+
|
|
219
|
+
Kuzushiji-MNISTはCC BY-SA 4.0の表示を確認しましたが、公式配布物の固定版とSHA-256を確定できないため保留中です。取得元が動的な状態では固定版取得の安全条件を満たせません。
|
|
220
|
+
|
|
221
|
+
`datasets.list_tasks(audience="student")`は、タスクとデータID、データごとの内容・形式・使用例を返します。使用例は入口の一例です。画像分類・検出・領域分割・OCR・回帰などの能力を基に、生徒が別の題材や問いへ応用できます。たとえば、画像カテゴリの比較、物体の位置記録、撮影条件の影響、画像と数値の関係などを調べられます。Orange Fruitは約113 MBです。FabricSpotDefectは約744 MBで、展開後も約744 MBのキャッシュ容量が必要です。RDD2020 Japanは約642 MBで、展開後は約646 MB必要です。RDD2020 JapanはCC BY-NC 3.0のため非商用利用に限られます。データのラベルが示さないこと(鮮度・食用可否・道路の安全性など)は確定できません。教師がプレビューを含めて確認する場合は`datasets.list_tasks(audience="teacher", include_preview=True)`を使います。未審査または未公開の候補は一覧に出しません。
|
|
222
|
+
|
|
223
|
+
LeFood-Setは`datasets.get("lefood_set")`で食前・食後画像524組を取得し、`to_df()`で実測重量や見た目評価を確認できます。日本の学校給食を代表するデータではなく、健康・栄養判断には使いません。
|
|
224
|
+
|
|
225
|
+
画像生成ではSDXLと4種類のLoRAを使えます。テキストからの画像生成や写真のスタイル変換ができ、LoRAごとに絵柄の特徴が異なります。アイコン、挿絵、ゲーム素材、風景の表現などは使用例です。目的に合わせて題材やプロンプトを考えられます。`generation.load(lora="pixel_art_sdxl", device="cuda")`のように明示して読み込みます。重みは再配布せず、固定版から初回利用時に取得します。[Colabで4件を試す](https://colab.research.google.com/github/708san/CV_data/blob/main/notebooks/colab/generation_smoke_test.ipynb)。ライセンス、学習元データ、能力・限界は[画像生成ガイド](docs/image-generation.md)を確認してください。LoRA学習素材は別管理で、権利確認中の文化祭アイコンは生徒向けに公開していません。
|
|
226
|
+
|
|
227
|
+
## AIにプログラムを改造してもらうとき
|
|
228
|
+
|
|
229
|
+
[AIに相談してコードを変更する方法](docs/ai-coding-guide.md)をご覧ください。使える関数名、データの形、依頼文の例を記載しています。
|
|
230
|
+
|
|
231
|
+
CV分類の生徒向けNotebookは、LLM講座の基準Notebookとは分けて[こちら](notebooks/cv/classification_v1.ipynb)に置いています。
|
|
232
|
+
|
|
233
|
+
## 開発・テスト
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
PYTHONPATH=src python -m unittest discover -s tests -v
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
開発計画は[こちら](docs/development-plan.md)です。実際のデータ取得はネットワークと容量を必要とするため、通常の自動テストでは行いません。
|
|
240
|
+
|
|
241
|
+
## 出典と注意
|
|
242
|
+
|
|
243
|
+
- 元の公開データ:<https://github.com/708san/CV_sample_data>
|
|
244
|
+
- 使用するタグ・SHA-256はライブラリ内で固定しています。
|
|
245
|
+
- **このライブラリのコードはMITライセンスです。データはデータごとに利用条件が異なります。** コードのライセンスは配布元画像の利用を許可するものではありません。詳しくは[LICENSE](LICENSE)と各データの説明をご覧ください。
|
|
246
|
+
- 元画像の保管場所は配布元が正本です。`CV_data`は取得のみを担当します。
|
|
247
|
+
|
|
248
|
+
## 配布データの検査
|
|
249
|
+
|
|
250
|
+
公開済みの固定Releaseから画像を取得し、ラベル・画像ファイルの整合を検査できます。通常のテストではダウンロードしません。
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
python -m pip install -e '.[display]'
|
|
254
|
+
python -m cvlesson.audit festival_icons --check-images --check-similar --report report.json
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
トマトの葉は `festival_icons` を `tomato_leaves` に変更します。GitHub Actionsの手動ワークフローからも実行できます。[詳しい手順](docs/phase1-release-audit.md)をご覧ください。画像の検査は権利の審査とは別です。
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
cv_data_lesson-0.1.0a1.dist-info/licenses/LICENSE,sha256=Yy_YG01Myh0TGPfC9MoDSUhwrtth4WnIDrSBI3y24Ic,1085
|
|
2
|
+
cvlesson/__init__.py,sha256=DRmuG9lVhgmbZ1xPd6BEdM-jSfYMjSJyqpX2TJw8eqM,251
|
|
3
|
+
cvlesson/audit.py,sha256=du6uGvnKK82kCgoKAvWQS58jfXQgkoWhchx6NliB9SA,8721
|
|
4
|
+
cvlesson/datasets.py,sha256=Eoa4LbGmBnwlTQ7MrFKWvUiP-fjsglAC7QZdADZlfbE,142778
|
|
5
|
+
cvlesson/display.py,sha256=aVdieZngVDWwHMouKD0T_DELQLECkK02HnIUSqrS5rs,7173
|
|
6
|
+
cvlesson/generation.py,sha256=iWKJltRyJb9RNYaTDlQc1hhc8WlDzZZWofUnCma8eEo,10844
|
|
7
|
+
cvlesson/loras.py,sha256=B_-K_xwNQkDsobWd2ZZPOyo0VsJJ_3k75PepT5m2GCk,14593
|
|
8
|
+
cvlesson/models.py,sha256=S77vQGSA4s3oIb0VDSKar_Eb7geFN1liiGx81rPZlnU,16838
|
|
9
|
+
cvlesson/ocr.py,sha256=2RhI_wwNIoRi6cpVj5rQjjdKFSO9k7MFavUEXMYFDWs,11225
|
|
10
|
+
cvlesson/vision.py,sha256=GVrLEKjl5PAPd6N7rAOrybyQ9mM_Asxlz7QHexeuc_8,12269
|
|
11
|
+
cv_data_lesson-0.1.0a1.dist-info/METADATA,sha256=cBKraYRdTXqc-cP-IXClryqYxSCO4atakI2oQTtjVLw,22360
|
|
12
|
+
cv_data_lesson-0.1.0a1.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
13
|
+
cv_data_lesson-0.1.0a1.dist-info/top_level.txt,sha256=P2t78lgS1yXzqhdv6P2LhJphO3M-wCZ3fB_hU3T3yf0,9
|
|
14
|
+
cv_data_lesson-0.1.0a1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CV Data project contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cvlesson
|
cvlesson/__init__.py
ADDED
cvlesson/audit.py
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""固定Releaseを読み取り専用で検査する。通常のテストではダウンロードしない。"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import csv
|
|
6
|
+
import hashlib
|
|
7
|
+
import json
|
|
8
|
+
import sys
|
|
9
|
+
from collections import Counter
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from . import datasets
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def inspect_release(
|
|
18
|
+
dataset_id: str,
|
|
19
|
+
*,
|
|
20
|
+
cache_dir: str | Path | None = None,
|
|
21
|
+
allow_download: bool = True,
|
|
22
|
+
check_images: bool = False,
|
|
23
|
+
check_similar: bool = False,
|
|
24
|
+
) -> dict[str, Any]:
|
|
25
|
+
"""固定Releaseの整合性を検査する。画像・ラベルには変更を加えない。"""
|
|
26
|
+
data = datasets.get(dataset_id, cache_dir=cache_dir, allow_download=allow_download)
|
|
27
|
+
source = datasets._source(dataset_id)
|
|
28
|
+
with data.labels_csv.open(encoding="utf-8-sig", newline="") as stream:
|
|
29
|
+
rows = list(csv.DictReader(stream))
|
|
30
|
+
|
|
31
|
+
image_paths = [data.root / row["image"] for row in rows]
|
|
32
|
+
result: dict[str, Any] = {
|
|
33
|
+
"dataset_id": dataset_id,
|
|
34
|
+
"name_ja": source.name_ja,
|
|
35
|
+
"kind": data.kind,
|
|
36
|
+
"release_tag": data.version,
|
|
37
|
+
"source_url": data.source_url,
|
|
38
|
+
"archive_sha256": source.sha256,
|
|
39
|
+
"expected_images": source.image_count,
|
|
40
|
+
"raw_image_count": len(rows),
|
|
41
|
+
"usable_image_count": len(rows),
|
|
42
|
+
"observed_images": len(rows),
|
|
43
|
+
"metadata_consistency": "passed",
|
|
44
|
+
"image_decode": "not_checked",
|
|
45
|
+
"image_decode_checked": 0,
|
|
46
|
+
"image_decode_failed": 0,
|
|
47
|
+
"image_decode_failures": [],
|
|
48
|
+
"duplicate_candidate_count": 0,
|
|
49
|
+
"label_mismatch_count": 0,
|
|
50
|
+
"similar_image_check": "not_performed",
|
|
51
|
+
"similar_image_candidate_count": 0,
|
|
52
|
+
"similar_image_method": "not_performed",
|
|
53
|
+
"similar_image_pairs": [],
|
|
54
|
+
"excluded_data": [],
|
|
55
|
+
"split_method": (
|
|
56
|
+
"official_test_preserved; deterministic_stratified_train_val"
|
|
57
|
+
if data.kind == "classification" else "not_applicable_lora_training_material"
|
|
58
|
+
),
|
|
59
|
+
"split_seed": 42 if data.kind == "classification" else None,
|
|
60
|
+
"leakage_risks": [
|
|
61
|
+
(
|
|
62
|
+
"group_idが元データにないため、同一個体・撮影系列の分離は保証していません。"
|
|
63
|
+
if data.kind == "classification" else "LoRA素材のため分類用splitは作成していません。"
|
|
64
|
+
),
|
|
65
|
+
(
|
|
66
|
+
"類似画像候補を平均ハッシュで抽出しました。候補は目視確認が必要です。"
|
|
67
|
+
if check_similar else "類似画像の自動判定は実施していません。"
|
|
68
|
+
),
|
|
69
|
+
],
|
|
70
|
+
"validated_at": datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z"),
|
|
71
|
+
"status": "passed",
|
|
72
|
+
"rights_note": data.rights_note,
|
|
73
|
+
"rights_review": "not_performed",
|
|
74
|
+
}
|
|
75
|
+
if data.kind == "classification":
|
|
76
|
+
result["classes"] = dict(sorted(Counter(row["label"] for row in rows).items()))
|
|
77
|
+
result["source_splits"] = dict(sorted(Counter(row["split"] for row in rows).items()))
|
|
78
|
+
effective_splits = datasets._effective_splits(rows, val_fraction=0.1, seed=42)
|
|
79
|
+
result["splits"] = dict(sorted(Counter(effective_splits).items()))
|
|
80
|
+
else:
|
|
81
|
+
result["captions_present"] = sum(bool(row["caption"].strip()) for row in rows)
|
|
82
|
+
|
|
83
|
+
if check_images:
|
|
84
|
+
try:
|
|
85
|
+
from PIL import Image
|
|
86
|
+
except ImportError as exc:
|
|
87
|
+
raise datasets.DatasetError(
|
|
88
|
+
"画像検査にはPillowが必要です。"
|
|
89
|
+
"python -m pip install 'cv-data-lesson[display]' を実行してください。"
|
|
90
|
+
) from exc
|
|
91
|
+
bad: list[str] = []
|
|
92
|
+
hashes: dict[str, list[dict[str, str]]] = {}
|
|
93
|
+
perceptual: list[tuple[int, str, dict[str, str]]] = []
|
|
94
|
+
for row, path in zip(rows, image_paths):
|
|
95
|
+
try:
|
|
96
|
+
with Image.open(path) as image:
|
|
97
|
+
image.verify()
|
|
98
|
+
with Image.open(path) as image:
|
|
99
|
+
image.load()
|
|
100
|
+
if image.width <= 0 or image.height <= 0:
|
|
101
|
+
raise ValueError("画像の大きさが不正です")
|
|
102
|
+
average_hash = _average_hash(image) if check_similar else None
|
|
103
|
+
digest = hashlib.sha256(path.read_bytes()).hexdigest()
|
|
104
|
+
hashes.setdefault(digest, []).append(row)
|
|
105
|
+
if average_hash is not None:
|
|
106
|
+
perceptual.append((average_hash, digest, row))
|
|
107
|
+
except (OSError, ValueError, SyntaxError, Image.DecompressionBombError):
|
|
108
|
+
if len(bad) < 10:
|
|
109
|
+
bad.append(path.relative_to(data.root).as_posix())
|
|
110
|
+
result["image_decode_failed"] += 1
|
|
111
|
+
result["image_decode_checked"] += 1
|
|
112
|
+
result["image_decode"] = "failed" if result["image_decode_failed"] else "passed"
|
|
113
|
+
result["image_decode_failures"] = bad
|
|
114
|
+
result["usable_image_count"] = len(rows) - result["image_decode_failed"]
|
|
115
|
+
result["excluded_data"] = [
|
|
116
|
+
{"image": image, "reason": "image_decode_failed"}
|
|
117
|
+
for image in bad
|
|
118
|
+
]
|
|
119
|
+
duplicate_groups = [items for items in hashes.values() if len(items) > 1]
|
|
120
|
+
result["duplicate_candidate_count"] = sum(len(items) - 1 for items in duplicate_groups)
|
|
121
|
+
if data.kind == "classification":
|
|
122
|
+
mismatches = [items for items in duplicate_groups if len({item["label"] for item in items}) > 1]
|
|
123
|
+
result["label_mismatch_count"] = sum(len(items) for items in mismatches)
|
|
124
|
+
result["duplicate_groups"] = [
|
|
125
|
+
{"sha256": digest, "images": [item["image"] for item in items]}
|
|
126
|
+
for digest, items in hashes.items() if len(items) > 1
|
|
127
|
+
][:20]
|
|
128
|
+
if check_similar:
|
|
129
|
+
candidates: list[dict[str, Any]] = []
|
|
130
|
+
for index, (left_hash, left_digest, left_row) in enumerate(perceptual):
|
|
131
|
+
for right_hash, right_digest, right_row in perceptual[index + 1:]:
|
|
132
|
+
if left_digest == right_digest:
|
|
133
|
+
continue
|
|
134
|
+
distance = (left_hash ^ right_hash).bit_count()
|
|
135
|
+
if distance <= 4:
|
|
136
|
+
candidates.append({
|
|
137
|
+
"hamming_distance": distance,
|
|
138
|
+
"images": [left_row["image"], right_row["image"]],
|
|
139
|
+
})
|
|
140
|
+
result["similar_image_check"] = "performed"
|
|
141
|
+
result["similar_image_method"] = "8x8 average hash; hamming_distance <= 4; review_only"
|
|
142
|
+
result["similar_image_candidate_count"] = len(candidates)
|
|
143
|
+
result["similar_image_pairs"] = candidates[:20]
|
|
144
|
+
if result["image_decode_failed"]:
|
|
145
|
+
result["status"] = "failed"
|
|
146
|
+
return result
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _average_hash(image: Any) -> int:
|
|
150
|
+
"""Pillow画像からレビュー用の軽量な平均ハッシュを作る。"""
|
|
151
|
+
gray = image.convert("L").resize((8, 8))
|
|
152
|
+
if hasattr(gray, "get_flattened_data"):
|
|
153
|
+
values = list(gray.get_flattened_data())
|
|
154
|
+
else:
|
|
155
|
+
values = list(gray.getdata())
|
|
156
|
+
average = sum(values) / len(values)
|
|
157
|
+
result = 0
|
|
158
|
+
for value in values:
|
|
159
|
+
result = (result << 1) | int(value >= average)
|
|
160
|
+
return result
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def main(argv: list[str] | None = None) -> int:
|
|
164
|
+
parser = argparse.ArgumentParser(description="固定Releaseの実ファイルとラベルを検査します。")
|
|
165
|
+
parser.add_argument("dataset_id", choices=("festival_icons", "tomato_leaves"))
|
|
166
|
+
parser.add_argument("--cache-dir", type=Path)
|
|
167
|
+
parser.add_argument("--no-download", action="store_true")
|
|
168
|
+
parser.add_argument("--check-images", action="store_true")
|
|
169
|
+
parser.add_argument("--check-similar", action="store_true")
|
|
170
|
+
parser.add_argument("--report", type=Path)
|
|
171
|
+
args = parser.parse_args(argv)
|
|
172
|
+
try:
|
|
173
|
+
report = inspect_release(
|
|
174
|
+
args.dataset_id, cache_dir=args.cache_dir,
|
|
175
|
+
allow_download=not args.no_download, check_images=args.check_images,
|
|
176
|
+
check_similar=args.check_similar,
|
|
177
|
+
)
|
|
178
|
+
except (datasets.DatasetError, OSError, ValueError, ImportError) as exc:
|
|
179
|
+
report = {
|
|
180
|
+
"dataset_id": args.dataset_id, "status": "failed",
|
|
181
|
+
"error": str(exc), "rights_review": "not_performed",
|
|
182
|
+
}
|
|
183
|
+
content = json.dumps(report, ensure_ascii=False, indent=2) + "\n"
|
|
184
|
+
if args.report is not None:
|
|
185
|
+
args.report.parent.mkdir(parents=True, exist_ok=True)
|
|
186
|
+
args.report.write_text(content, encoding="utf-8")
|
|
187
|
+
sys.stdout.write(content)
|
|
188
|
+
return 0 if report["status"] == "passed" else 1
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
if __name__ == "__main__":
|
|
192
|
+
raise SystemExit(main())
|