fix fish weight calculation by using correct parameters. output video

This commit is contained in:
zaiun xu
2026-04-14 20:55:15 +08:00
parent 8497d0eb1d
commit af67f61b63
520 changed files with 4450241 additions and 337 deletions

0
FishMeasure/.labelCloud.log Executable file → Normal file
View File

2
FishMeasure/README.md Executable file → Normal file
View File

@@ -1,7 +1,5 @@
# 3D 鱼类重量测量系统
> **仓库瘦身说明**:为便于部署到 `FishServer`,已删除历史推理目录(如 `output_weight_estimator/`、`output-yolo-sam/`)、训练数据集 `datasets/`、`project_jiuzhou01/` 前端工程及部分实验子项目数据。运行时仍需保留 **`sam_vit_h_4b8939.pth`**、`runs/train/.../best.pt`、`weight_estimator/runs/dgcnn_20260312_171043/best.pt` 等权重;详见仓库根目录 [`README.md`](../README.md)。
基于 ZED 立体相机的 3D 鱼类重量自动测量项目。通过深度图检测、点云处理、模板匹配和体积计算,实现鱼类的非接触式重量估算。
## 项目概述

0
FishMeasure/dataset/zed_reader.py Executable file → Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

View File

@@ -0,0 +1 @@
0 0.465612 0.844255 0.186298 0.259972

View File

@@ -0,0 +1 @@
0 0.465278 0.841880 0.186966 0.259972

View File

@@ -0,0 +1 @@
0 0.467949 0.842474 0.184295 0.256410

View File

@@ -0,0 +1 @@
0 0.465278 0.842474 0.185630 0.254036

View File

@@ -0,0 +1 @@
0 0.466279 0.838913 0.186298 0.256410

View File

@@ -0,0 +1 @@
0 0.465612 0.841287 0.186298 0.256410

View File

@@ -0,0 +1 @@
0 0.465278 0.844255 0.185630 0.257597

View File

@@ -0,0 +1 @@
0 0.466279 0.841287 0.183627 0.254036

View File

@@ -0,0 +1 @@
0 0.464276 0.840693 0.180956 0.252849

View File

@@ -0,0 +1 @@
0 0.465946 0.843661 0.186966 0.254036

View File

@@ -0,0 +1 @@
0 0.505709 0.935482 0.190371 0.129036

View File

@@ -0,0 +1 @@
0 0.504340 0.935066 0.187634 0.121083

View File

@@ -0,0 +1 @@
0 0.506010 0.935066 0.186966 0.123457

View File

@@ -0,0 +1 @@
0 0.506273 0.937856 0.188827 0.124288

View File

@@ -0,0 +1 @@
0 0.505330 0.937856 0.185607 0.124288

View File

@@ -0,0 +1 @@
0 0.505342 0.934473 0.190972 0.127018

View File

@@ -0,0 +1 @@
0 0.506010 0.934473 0.188301 0.115147

View File

@@ -0,0 +1 @@
0 0.505008 0.936254 0.187634 0.116334

View File

@@ -0,0 +1 @@
0 0.505398 0.937856 0.187079 0.124288

View File

@@ -0,0 +1 @@
0 0.507032 0.934888 0.186339 0.130223

View File

@@ -0,0 +1 @@
0 0.505283 0.939043 0.184177 0.121914

View File

@@ -0,0 +1 @@
0 0.505008 0.936254 0.186298 0.118708

View File

@@ -0,0 +1 @@
0 0.506677 0.937441 0.186966 0.121083

View File

@@ -0,0 +1 @@
0 0.505342 0.936254 0.182959 0.125831

View File

@@ -0,0 +1 @@
0 0.506415 0.937263 0.185105 0.125475

View File

@@ -0,0 +1 @@
0 0.505676 0.935066 0.187634 0.125831

View File

@@ -0,0 +1 @@
0 0.507011 0.936847 0.184963 0.124644

View File

@@ -0,0 +1 @@
0 0.507011 0.936847 0.186298 0.124644

View File

@@ -0,0 +1 @@
0 0.507413 0.937263 0.187103 0.125475

View File

@@ -0,0 +1 @@
0 0.504930 0.936669 0.186142 0.126662

View File

@@ -0,0 +1 @@
0 0.506010 0.936847 0.185630 0.117521

View File

@@ -0,0 +1 @@
0 0.504830 0.937856 0.189949 0.124288

View File

@@ -0,0 +1 @@
0 0.507180 0.937856 0.187971 0.124288

View File

@@ -0,0 +1 @@
0 0.481862 0.954475 0.185412 0.091049

View File

@@ -0,0 +1 @@
0 0.479464 0.953288 0.183287 0.093424

View File

@@ -0,0 +1 @@
0 0.480728 0.953882 0.188486 0.092236

View File

@@ -0,0 +1 @@
0 0.478966 0.954060 0.186298 0.090218

Binary file not shown.

View File

@@ -0,0 +1 @@
0 0.463942 0.843067 0.184295 0.255223

View File

@@ -0,0 +1 @@
0 0.466613 0.843661 0.182959 0.261159

View File

@@ -0,0 +1 @@
0 0.465946 0.839506 0.185630 0.255223

View File

@@ -0,0 +1 @@
0 0.464610 0.843661 0.186966 0.256410

View File

@@ -0,0 +1 @@
0 0.505756 0.936669 0.189129 0.126662

View File

@@ -0,0 +1 @@
0 0.506010 0.935066 0.181624 0.123457

View File

@@ -0,0 +1 @@
0 0.505452 0.936669 0.187186 0.126662

View File

@@ -0,0 +1 @@
0 0.506484 0.936669 0.185243 0.126662

View File

@@ -0,0 +1 @@
0 0.506677 0.933879 0.185630 0.116334

View File

@@ -0,0 +1,6 @@
path: /home/ubuntu/projects/FishMeasure/detect_refbox/dataset
train: images/train
val: images/val
nc: 1
names:
0: ref

View File

@@ -0,0 +1,317 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
从递归目录中的 LabelMe 风格 JSON 构建 YOLO 检测数据集(仅参考物 ref
- 仅使用同时满足存在对应图像、JSON 内至少有一个可转换的 ref 矩形框 的样本。
- 图像与 JSON 同目录,或通过 imagePath 解析;若仅有 imageData 则解码写出。
- 输出扁平唯一文件名(相对路径转 __避免不同子目录同名帧冲突。
"""
from __future__ import annotations
import argparse
import base64
import io
import json
import random
import shutil
import sys
from pathlib import Path
from typing import Any, Dict, List, Optional, Set, Tuple
IMG_EXTS = {".jpg", ".jpeg", ".png", ".bmp", ".tif", ".tiff"}
# 归一到单一类别 refclass 0
REF_LABELS: Set[str] = {"ref", "reference", "refbox", "参考", "参考物"}
def parse_args() -> argparse.Namespace:
p = argparse.ArgumentParser(description="准备 ref 框 YOLO 数据集")
p.add_argument(
"--source",
type=Path,
default=Path("/home/ubuntu/data/fish/2016-1-22-last_images"),
help="含递归子目录与 JSON 的根目录",
)
p.add_argument(
"--out",
type=Path,
default=None,
help="输出数据集根目录(默认:本仓库 detect_refbox/dataset",
)
p.add_argument("--val-ratio", type=float, default=0.2, help="验证集比例")
p.add_argument("--seed", type=int, default=42, help="划分随机种子")
p.add_argument(
"--copy-images",
action="store_true",
help="复制图像;默认硬链接(同盘失败时回退复制)",
)
return p.parse_args()
def repo_root() -> Path:
return Path(__file__).resolve().parents[1]
def norm_bbox_yolo(
x1: float, y1: float, x2: float, y2: float, w: int, h: int
) -> Tuple[float, float, float, float]:
x_min, y_min = min(x1, x2), min(y1, y2)
x_max, y_max = max(x1, x2), max(y1, y2)
bw = max(0.0, x_max - x_min)
bh = max(0.0, y_max - y_min)
cx = x_min + bw / 2.0
cy = y_min + bh / 2.0
if w <= 0 or h <= 0:
raise ValueError("invalid image size")
return cx / w, cy / h, bw / w, bh / h
def load_json(path: Path) -> Optional[Dict[str, Any]]:
for enc in ("utf-8", "gbk", "gb2312", "latin-1"):
try:
with open(path, "r", encoding=enc) as f:
return json.load(f)
except (UnicodeDecodeError, json.JSONDecodeError):
continue
return None
def find_image_path(json_path: Path, data: Dict[str, Any]) -> Optional[Path]:
ip = (data.get("imagePath") or "").strip()
if ip:
cand = (json_path.parent / ip).resolve()
if cand.exists() and cand.suffix.lower() in IMG_EXTS:
return cand
name = Path(ip).name
for ext in IMG_EXTS:
c2 = json_path.parent / f"{Path(name).stem}{ext}"
if c2.exists():
return c2
stem = json_path.stem
for ext in IMG_EXTS:
c = json_path.parent / f"{stem}{ext}"
if c.exists():
return c
return None
def image_from_image_data(data: Dict[str, Any]) -> Optional[bytes]:
raw = data.get("imageData")
if not raw or not isinstance(raw, str):
return None
try:
return base64.b64decode(raw)
except Exception:
return None
def shapes_to_yolo_lines(
data: Dict[str, Any], img_w: int, img_h: int
) -> List[str]:
shapes = data.get("shapes") or data.get("annotations") or []
if not isinstance(shapes, list):
return []
lines: List[str] = []
for shp in shapes:
st = shp.get("shape_type")
if st not in (None, "rectangle", "bbox", "box"):
continue
label = str(shp.get("label", "")).strip()
if label not in REF_LABELS:
continue
pts = shp.get("points")
if not pts or len(pts) < 2:
continue
(x1, y1), (x2, y2) = pts[0], pts[1]
try:
xc, yc, bw, bh = norm_bbox_yolo(
float(x1), float(y1), float(x2), float(y2), img_w, img_h
)
except Exception:
continue
if bw <= 0 or bh <= 0:
continue
lines.append(f"0 {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}")
return lines
def ensure_image_size(
data: Dict[str, Any], img_path: Optional[Path]
) -> Tuple[int, int]:
w = int(data.get("imageWidth", 0) or 0)
h = int(data.get("imageHeight", 0) or 0)
if w > 0 and h > 0:
return w, h
if img_path and img_path.exists():
try:
from PIL import Image
with Image.open(img_path) as im:
return im.size
except Exception:
pass
blob = image_from_image_data(data)
if blob:
try:
from PIL import Image
with Image.open(io.BytesIO(blob)) as im:
return im.size
except Exception:
pass
return 0, 0
def unique_stem(json_path: Path, source_root: Path) -> str:
rel = json_path.parent.relative_to(source_root)
prefix = rel.as_posix().replace("/", "__")
return f"{prefix}__{json_path.stem}"
def write_dataset_yaml(out_root: Path) -> Path:
yaml_path = out_root / "refbox.yaml"
# Ultralyticspath 为数据集根train/val 为相对 path 的图像目录
text = (
f"path: {out_root.resolve()}\n"
"train: images/train\n"
"val: images/val\n"
"nc: 1\n"
"names:\n"
" 0: ref\n"
)
yaml_path.write_text(text, encoding="utf-8")
return yaml_path
def main() -> int:
args = parse_args()
source = args.source.expanduser().resolve()
if not source.is_dir():
print(f"[错误] 数据目录不存在: {source}", file=sys.stderr)
return 1
out_root = (
args.out.expanduser().resolve()
if args.out
else repo_root() / "detect_refbox" / "dataset"
)
img_train = out_root / "images" / "train"
img_val = out_root / "images" / "val"
lbl_train = out_root / "labels" / "train"
lbl_val = out_root / "labels" / "val"
for d in (img_train, img_val, lbl_train, lbl_val):
d.mkdir(parents=True, exist_ok=True)
records: List[Tuple[Path, Path, List[str], str]] = []
# (json_path, src_image_path, yolo_lines, unique_stem)
json_files = sorted(source.rglob("*.json"))
skipped = 0
for jp in json_files:
data = load_json(jp)
if not data:
skipped += 1
continue
img_path = find_image_path(jp, data)
if not img_path:
blob = image_from_image_data(data)
if not blob:
skipped += 1
continue
ext = Path((data.get("imagePath") or "img.png")).suffix.lower()
if ext not in IMG_EXTS:
ext = ".png"
stem = unique_stem(jp, source)
tmp_img = out_root / "_tmp_decode" / f"{stem}{ext}"
tmp_img.parent.mkdir(parents=True, exist_ok=True)
tmp_img.write_bytes(blob)
img_path = tmp_img
iw, ih = ensure_image_size(data, img_path)
if iw <= 0 or ih <= 0:
skipped += 1
continue
lines = shapes_to_yolo_lines(data, iw, ih)
if not lines:
skipped += 1
continue
stem = unique_stem(jp, source)
ext = img_path.suffix.lower()
if ext not in IMG_EXTS:
ext = ".png"
records.append((jp, img_path, lines, stem + ext))
if not records:
print("[错误] 没有可用样本(需 JSON + 图像 + ref 矩形)", file=sys.stderr)
return 1
rng = random.Random(args.seed)
rng.shuffle(records)
n_val = int(round(len(records) * args.val_ratio))
n_val = max(1, n_val) if len(records) >= 2 else 0
n_val = min(n_val, len(records) - 1) if len(records) >= 2 else 0
# 仅 1 张:训练与验证共用同一张(写入两个目录),避免 YOLO 无 val
single_dup = len(records) == 1
val_set = set(range(len(records) - n_val, len(records))) if not single_dup else set()
n_tr = 0
n_va = 0
def materialize(
src_img: Path, lines: List[str], fname: str, is_val: bool
) -> bool:
nonlocal n_tr, n_va, skipped
idir = img_val if is_val else img_train
ldir = lbl_val if is_val else lbl_train
dst_img = idir / fname
stem = Path(fname).stem
dst_lbl = ldir / f"{stem}.txt"
dst_img.parent.mkdir(parents=True, exist_ok=True)
try:
if args.copy_images:
shutil.copy2(src_img, dst_img)
else:
if dst_img.exists():
dst_img.unlink()
try:
dst_img.hardlink_to(src_img)
except OSError:
shutil.copy2(src_img, dst_img)
except Exception as e:
print(f"[跳过] 复制图像失败 {src_img}: {e}", file=sys.stderr)
skipped += 1
return False
dst_lbl.write_text("\n".join(lines) + "\n", encoding="utf-8")
if is_val:
n_va += 1
else:
n_tr += 1
return True
for i, (_, src_img, lines, fname) in enumerate(records):
if single_dup:
materialize(src_img, lines, fname, is_val=False)
materialize(src_img, lines, fname, is_val=True)
break
is_val = i in val_set
materialize(src_img, lines, fname, is_val=is_val)
yaml_path = write_dataset_yaml(out_root)
tmp = out_root / "_tmp_decode"
if tmp.exists():
shutil.rmtree(tmp, ignore_errors=True)
print(
f"完成: 训练 {n_tr} / 验证 {n_va}(跳过 {skipped} 个 JSON\n"
f"数据集: {out_root}\n"
f"YAML: {yaml_path}"
)
return 0
if __name__ == "__main__":
raise SystemExit(main())

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Some files were not shown because too many files have changed in this diff Show More