1
This commit is contained in:
@@ -1,278 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""RF-DETR 整图批量检测野鸡,生成候选标注(初标)
|
||||
|
||||
- 整图等比缩放至最长边 INPUT_SIZE(700)送检测,**不做任何裁剪**,坐标按比例映射回原图
|
||||
- 只保留 COCO bird 类;同目标重复框用 NMS 去重
|
||||
- 藏身点:图像分析(植被密度/地形边缘/光线)生成 class 1 疑似藏匿位置
|
||||
- 输出: YOLO txt (class 0 = 确认野鸡, class 1 = 疑似(低置信检测+藏身点))
|
||||
-> datasets/labels/pheasant_rfdetr/
|
||||
- 分析JSON -> datasets/analysis/pheasant_rfdetr/
|
||||
- 预览图 -> datasets/previews/pheasant_rfdetr/
|
||||
"""
|
||||
import argparse
|
||||
import base64
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import urllib.request
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
SRC_IMAGES = os.path.join(BASE_DIR, "datasets/images/pheasant")
|
||||
OUT_LABELS = os.path.join(BASE_DIR, "datasets/labels/pheasant_rfdetr")
|
||||
OUT_JSON = os.path.join(BASE_DIR, "datasets/analysis/pheasant_rfdetr")
|
||||
OUT_PREVIEW = os.path.join(BASE_DIR, "datasets/previews/pheasant_rfdetr")
|
||||
|
||||
ENDPOINT = "http://192.168.3.210:18080/v1/detection"
|
||||
MODEL = "rfdetr-xlarge"
|
||||
TIMEOUT = 300
|
||||
THRESHOLD = 0.08
|
||||
CONF_CONFIRMED = 0.2 # 置信度高于此为明确(class 0 红框),否则为疑似(class 1 蓝框)
|
||||
# RF-DETR 置信度分布:强目标 0.3-0.9,0.05-0.14 多为噪声尾;
|
||||
# THRESHOLD=0.08 砍掉纯噪声,CONF_CONFIRMED=0.2 只把强目标标红,宁多勿漏
|
||||
INPUT_SIZE = 700 # 整图等比缩放后的最长边,不裁剪
|
||||
NMS_IOU = 0.4
|
||||
WORKERS = 4
|
||||
KEEP_CLASSES = set() # 空 = 保留全部类别(RF-DETR 对小目标类别不稳定,定位一致即可)
|
||||
|
||||
|
||||
IMG_MIME = {".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png"}
|
||||
|
||||
|
||||
def detect(buf, mime):
|
||||
body = json.dumps({"model": MODEL, "image": f"data:{mime};base64,{base64.b64encode(buf.tobytes()).decode()}",
|
||||
"threshold": THRESHOLD}).encode()
|
||||
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
|
||||
req = urllib.request.Request(ENDPOINT, data=body,
|
||||
headers={"Content-Type": "application/json"}, method="POST")
|
||||
with opener.open(req, timeout=TIMEOUT) as r:
|
||||
resp = json.loads(r.read().decode())
|
||||
return resp.get("detections", [])
|
||||
|
||||
|
||||
def nms(boxes, iou_thr):
|
||||
"""boxes: [(x, y, w, h, conf)], 按置信度降序去重"""
|
||||
boxes = sorted(boxes, key=lambda b: -b[4])
|
||||
keep = []
|
||||
for b in boxes:
|
||||
overlap = False
|
||||
for k in keep:
|
||||
ix = min(b[0] + b[2], k[0] + k[2]) - max(b[0], k[0])
|
||||
iy = min(b[1] + b[3], k[1] + k[3]) - max(b[1], k[1])
|
||||
if ix <= 0 or iy <= 0:
|
||||
continue
|
||||
inter = ix * iy
|
||||
union = b[2] * b[3] + k[2] * k[3] - inter
|
||||
if inter / union > iou_thr:
|
||||
overlap = True
|
||||
break
|
||||
if not overlap:
|
||||
keep.append(b)
|
||||
return keep
|
||||
|
||||
|
||||
def find_hiding_spots(img, H, W, n_spots=3):
|
||||
"""图像分析疑似藏身点:植被密度/地形边缘(田埂、林田交界、沟渠)/光线调制。
|
||||
|
||||
野鸡习性:地面活动,藏身于茂密低矮遮蔽物,避开开阔裸地;
|
||||
强光场景偏好阴影遮蔽,弱光场景偏好相对亮且有遮蔽的区域。
|
||||
返回 [(x, y, w, h)] 像素坐标,框面积约占画面 2%~8%。
|
||||
"""
|
||||
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
|
||||
s, v = hsv[..., 1], hsv[..., 2]
|
||||
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY).astype(np.float32) / 255.0
|
||||
b = img[..., 0].astype(np.float32) / 255.0
|
||||
g = img[..., 1].astype(np.float32) / 255.0
|
||||
r = img[..., 2].astype(np.float32) / 255.0
|
||||
veg = np.clip(g - np.maximum(r, b), 0, 1)
|
||||
veg[s / 255.0 < 0.12] = 0
|
||||
tex = np.abs(gray - cv2.GaussianBlur(gray, (0, 0), 3))
|
||||
base = veg + tex
|
||||
grad = cv2.magnitude(cv2.Sobel(base, cv2.CV_32F, 1, 0, ksize=3),
|
||||
cv2.Sobel(base, cv2.CV_32F, 0, 1, ksize=3))
|
||||
light = v / 255.0
|
||||
mean_light = float(gray.mean())
|
||||
if mean_light > 0.55:
|
||||
light_w = 1.0 - np.abs(light - 0.25)
|
||||
else:
|
||||
light_w = 1.0 - np.abs(light - 0.6)
|
||||
cover = (veg * 0.55 + tex * 0.30 + grad * 0.15) * (0.4 + 0.6 * light_w)
|
||||
cover[light < 0.12] = 0
|
||||
cover[light > 0.93] *= 0.15
|
||||
cover = cv2.GaussianBlur(cover, (0, 0), 7)
|
||||
windows = []
|
||||
for area_frac in (0.02, 0.04, 0.07):
|
||||
for aspect in (1.0, 1.6, 2.5):
|
||||
bw = int(round((area_frac * aspect * H * W) ** 0.5))
|
||||
bh = int(round((area_frac * H * W / aspect) ** 0.5))
|
||||
bw, bh = min(bw, W), min(bh, H)
|
||||
windows.append((cv2.boxFilter(cover, -1, (bw, bh)), bw, bh))
|
||||
stack = np.stack([w[0] for w in windows])
|
||||
best = stack.max(axis=0)
|
||||
best_idx = stack.argmax(axis=0)
|
||||
floor = max(float(best.max()) * 0.35, 0.02)
|
||||
spots = []
|
||||
for _ in range(n_spots):
|
||||
idx = np.unravel_index(int(np.argmax(best)), best.shape)
|
||||
y, x = idx
|
||||
if best[y, x] < floor:
|
||||
break
|
||||
_, bw, bh = windows[int(best_idx[y, x])]
|
||||
x0 = max(0, min(x - bw // 2, W - bw))
|
||||
y0 = max(0, min(y - bh // 2, H - bh))
|
||||
spots.append((x0, y0, bw, bh))
|
||||
mx, my = max(bw, 64), max(bh, 64)
|
||||
best[max(0, y - my):min(H, y + my), max(0, x - mx):min(W, x + mx)] = -1
|
||||
return [(int(x), int(y), int(w), int(h)) for x, y, w, h in spots]
|
||||
|
||||
|
||||
def suppress_overlap(lines):
|
||||
"""class 1(疑似)框与任一 class 0(确认)框重叠时删除,只保留 class 0"""
|
||||
cls0 = [l for l in lines if l.startswith("0 ")]
|
||||
if not cls0:
|
||||
return lines
|
||||
boxes0 = [tuple(map(float, l.split())) for l in cls0]
|
||||
keep1 = []
|
||||
for l in lines:
|
||||
if l.startswith("0 "):
|
||||
continue
|
||||
c, cx, cy, bw, bh = map(float, l.split())
|
||||
x1, y1, x2, y2 = cx - bw / 2, cy - bh / 2, cx + bw / 2, cy + bh / 2
|
||||
overlap = False
|
||||
for kc, kx, ky, kw, kh in boxes0:
|
||||
ix = min(x2, kx + kw / 2) - max(x1, kx - kw / 2)
|
||||
iy = min(y2, ky + kh / 2) - max(y1, ky - kh / 2)
|
||||
if ix > 0 and iy > 0:
|
||||
overlap = True
|
||||
break
|
||||
if not overlap:
|
||||
keep1.append(l)
|
||||
return cls0 + keep1
|
||||
|
||||
|
||||
def process(name):
|
||||
path = os.path.join(SRC_IMAGES, name)
|
||||
img = cv2.imread(path)
|
||||
H, W = img.shape[:2]
|
||||
ext = os.path.splitext(path)[1].lower()
|
||||
mime = IMG_MIME.get(ext, "image/jpeg")
|
||||
jpeg_params = [cv2.IMWRITE_JPEG_QUALITY, 100] if ext in (".jpg", ".jpeg") else []
|
||||
# 整图等比缩放至最长边 INPUT_SIZE,不裁剪;检测坐标按比例映射回原图
|
||||
scale = min(1.0, INPUT_SIZE / max(H, W))
|
||||
if scale < 1.0:
|
||||
small = cv2.resize(img, (round(W * scale), round(H * scale)),
|
||||
interpolation=cv2.INTER_AREA)
|
||||
else:
|
||||
small = img
|
||||
ok, buf = cv2.imencode(ext, small, jpeg_params)
|
||||
boxes = []
|
||||
for d in detect(buf, mime):
|
||||
if not KEEP_CLASSES or d.get("class_name") in KEEP_CLASSES:
|
||||
boxes.append((d["x"] / scale, d["y"] / scale,
|
||||
d["width"] / scale, d["height"] / scale, d["confidence"]))
|
||||
# 野鸡目标 20-100px(近大远小);排除大物体误检(田地纹理)与细条误检
|
||||
boxes = [b for b in nms(boxes, NMS_IOU)
|
||||
if 4 <= b[3] <= H * 0.06 and 4 <= b[2] <= W * 0.1
|
||||
and 0.3 <= b[2] / b[3] <= 3.0]
|
||||
hiding = find_hiding_spots(img, H, W)
|
||||
return name, W, H, boxes, hiding
|
||||
|
||||
|
||||
def reclassify():
|
||||
"""不重新检测:按当前 CONF_CONFIRMED 从分析 JSON 重出 txt 标签与预览"""
|
||||
total_boxes = total_empty = 0
|
||||
for p in sorted(glob.glob(os.path.join(OUT_JSON, "*.json"))):
|
||||
j = json.load(open(p, encoding="utf-8"))
|
||||
W, H = j["size"]
|
||||
stem = os.path.splitext(os.path.basename(p))[0]
|
||||
lines = []
|
||||
for b in j["candidates"]:
|
||||
x, y, w, h, conf = b["x"], b["y"], b["width"], b["height"], b["confidence"]
|
||||
cx, cy, bw, bh = (x + w / 2) / W, (y + h / 2) / H, w / W, h / H
|
||||
cls = 0 if conf >= CONF_CONFIRMED else 1
|
||||
lines.append(f"{cls} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}")
|
||||
for b in j.get("hiding_spots", []):
|
||||
x, y, w, h = b["x"], b["y"], b["width"], b["height"]
|
||||
cx, cy, bw, bh = (x + w / 2) / W, (y + h / 2) / H, w / W, h / H
|
||||
lines.append(f"1 {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}")
|
||||
lines = suppress_overlap(lines)
|
||||
with open(os.path.join(OUT_LABELS, f"{stem}.txt"), "w", encoding="utf-8") as f:
|
||||
f.write("\n".join(lines) + ("\n" if lines else ""))
|
||||
draw_preview(os.path.join(SRC_IMAGES, j["image"]), lines,
|
||||
os.path.join(OUT_PREVIEW, f"{stem}.jpg"))
|
||||
total_boxes += len(lines)
|
||||
if not lines:
|
||||
total_empty += 1
|
||||
print(f"重分类完成: {total_boxes} 框, 空图 {total_empty} 张", flush=True)
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description="RF-DETR 切块放大批量检测野鸡候选框")
|
||||
ap.add_argument("--names", type=str, default="", help="只处理指定编号,逗号分隔")
|
||||
ap.add_argument("--reclassify", action="store_true",
|
||||
help="不重新检测,按当前 CONF_CONFIRMED 从分析JSON重出标签与预览")
|
||||
args = ap.parse_args()
|
||||
|
||||
if args.reclassify:
|
||||
reclassify()
|
||||
return
|
||||
|
||||
for d in (OUT_LABELS, OUT_JSON, OUT_PREVIEW):
|
||||
os.makedirs(d, exist_ok=True)
|
||||
names = sorted(p.name for p in os.scandir(SRC_IMAGES)
|
||||
if p.name.startswith("pheasant_") and p.name.endswith(".jpg"))
|
||||
if args.names:
|
||||
want = {f"pheasant_{n}.jpg" for n in args.names.split(",")}
|
||||
names = [n for n in names if n in want]
|
||||
|
||||
total_boxes = total_empty = 0
|
||||
with ThreadPoolExecutor(max_workers=WORKERS) as pool:
|
||||
futures = [pool.submit(process, n) for n in names]
|
||||
for fut in as_completed(futures):
|
||||
name, W, H, boxes, hiding = fut.result()
|
||||
stem = os.path.splitext(name)[0]
|
||||
lines = []
|
||||
for x, y, w, h, conf in boxes:
|
||||
cx, cy, bw, bh = (x + w / 2) / W, (y + h / 2) / H, w / W, h / H
|
||||
cls = 0 if conf >= CONF_CONFIRMED else 1
|
||||
lines.append(f"{cls} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}")
|
||||
for x, y, w, h in hiding:
|
||||
cx, cy, bw, bh = (x + w / 2) / W, (y + h / 2) / H, w / W, h / H
|
||||
lines.append(f"1 {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}")
|
||||
lines = suppress_overlap(lines)
|
||||
with open(os.path.join(OUT_JSON, f"{stem}.json"), "w", encoding="utf-8") as f:
|
||||
json.dump({"image": name, "size": [W, H],
|
||||
"candidates": [{"x": b[0], "y": b[1], "width": b[2],
|
||||
"height": b[3], "confidence": b[4]} for b in boxes],
|
||||
"hiding_spots": [{"x": b[0], "y": b[1], "width": b[2],
|
||||
"height": b[3]} for b in hiding]},
|
||||
f, ensure_ascii=False, indent=1)
|
||||
with open(os.path.join(OUT_LABELS, f"{stem}.txt"), "w", encoding="utf-8") as f:
|
||||
f.write("\n".join(lines) + ("\n" if lines else ""))
|
||||
draw_preview(os.path.join(SRC_IMAGES, name), lines,
|
||||
os.path.join(OUT_PREVIEW, f"{stem}.jpg"))
|
||||
total_boxes += len(lines)
|
||||
if not lines:
|
||||
total_empty += 1
|
||||
print(f"{name}: 候选{len(lines)}", flush=True)
|
||||
|
||||
print(f"完成: {len(names)} 张, 候选框 {total_boxes}, 无候选 {total_empty} 张", flush=True)
|
||||
|
||||
|
||||
def draw_preview(img_path, lines, out_path):
|
||||
img = cv2.imread(img_path)
|
||||
h, w = img.shape[:2]
|
||||
for line in lines:
|
||||
cls, cx, cy, bw, bh = map(float, line.split())
|
||||
x1, y1 = int((cx - bw / 2) * w), int((cy - bh / 2) * h)
|
||||
x2, y2 = int((cx + bw / 2) * w), int((cy + bh / 2) * h)
|
||||
color = (0, 0, 255) if cls == 0 else (255, 0, 0)
|
||||
cv2.rectangle(img, (x1, y1), (x2, y2), color, 2)
|
||||
cv2.imwrite(out_path, img)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,139 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""dump TFLite 模型前 N 个 operator 的结构与常量(perm/reshape shape)"""
|
||||
import struct
|
||||
import sys
|
||||
|
||||
TENSOR_TYPE = {0: "FLOAT32", 1: "FLOAT16", 2: "INT32", 3: "UINT8", 4: "INT64",
|
||||
5: "STRING", 6: "BOOL", 7: "INT16", 8: "COMPLEX64", 9: "INT8",
|
||||
10: "FLOAT64", 11: "COMPLEX128", 12: "UINT64", 13: "RESOURCE",
|
||||
14: "VARIANT", 15: "UINT32", 16: "UINT16", 17: "INT4",
|
||||
18: "BFLOAT16", 19: "FLOAT8_E4M3FN", 20: "FLOAT8_E4M3FNUZ",
|
||||
21: "FLOAT8_E5M2", 22: "FLOAT8_E5M2FNUZ"}
|
||||
BUILTIN = {0: "ADD", 1: "AVERAGE_POOL_2D", 2: "CONCATENATION", 3: "CONV_2D",
|
||||
4: "DEPTHWISE_CONV_2D", 5: "DEPTH_TO_SPACE", 6: "DEQUANTIZE",
|
||||
7: "EMBEDDING_LOOKUP", 8: "FLOOR", 9: "FULLY_CONNECTED",
|
||||
10: "HASHTABLE_LOOKUP", 11: "L2_NORMALIZATION", 12: "L2_POOL_2D",
|
||||
13: "LOCAL_RESPONSE_NORMALIZATION", 14: "LOGISTIC", 15: "LSH_PROJECTION",
|
||||
16: "LSTM", 17: "MAX_POOL_2D", 18: "MUL", 19: "RELU", 20: "RELU_N1_TO_1",
|
||||
21: "RELU6", 22: "RESHAPE", 23: "RESIZE_BILINEAR", 24: "RNN",
|
||||
25: "SOFTMAX", 26: "SPACE_TO_DEPTH", 27: "SVDF", 28: "TANH",
|
||||
29: "CONCAT_EMBEDDINGS", 30: "SKIP_GRAM", 31: "CALL", 32: "CUSTOM",
|
||||
33: "EMBEDDING_LOOKUP_SPARSE", 34: "PAD", 35: "UNIDIRECTIONAL_SEQUENCE_RNN",
|
||||
36: "GATHER", 37: "BATCH_TO_SPACE_ND", 38: "SPACE_TO_BATCH_ND",
|
||||
39: "TRANSPOSE", 40: "MEAN", 41: "SUB", 42: "DIV", 43: "SQUEEZE",
|
||||
44: "UNIDIRECTIONAL_SEQUENCE_LSTM", 45: "STRIDED_SLICE",
|
||||
46: "BIDIRECTIONAL_SEQUENCE_RNN", 47: "EXP", 48: "TOPK_V2",
|
||||
49: "SPLIT", 50: "LOG_SOFTMAX", 51: "DELEGATE", 52: "BIDIRECTIONAL_SEQUENCE_LSTM",
|
||||
53: "CAST", 54: "PREDICT", 55: "CONCATENATION_RELU", 56: "ARG_MAX",
|
||||
57: "MINIMUM", 58: "LESS", 59: "NEG", 60: "PADV2", 61: "GREATER",
|
||||
62: "GREATER_EQUAL", 63: "LESS_EQUAL", 64: "SELECT", 65: "SLICE",
|
||||
66: "SIN", 67: "TRANSPOSE_CONV", 68: "SPARSE_TO_DENSE", 69: "TILE",
|
||||
70: "EXPAND_DIMS", 71: "EQUAL", 72: "NOT_EQUAL", 73: "LOG",
|
||||
74: "SUM", 75: "SQRT", 76: "RSQRT", 77: "SHAPE", 78: "POW", 79: "ARG_MIN",
|
||||
80: "FAKE_QUANT", 81: "REDUCE_PROD", 82: "REDUCE_MAX", 83: "PACK",
|
||||
84: "LOGICAL_OR", 85: "LOGICAL_AND", 86: "LOGICAL_NOT", 87: "UNPACK",
|
||||
88: "REDUCE_MIN", 89: "FLOOR_DIV", 90: "REDUCE_ANY", 91: "SQUARE",
|
||||
92: "ZEROS_LIKE", 93: "FILL", 94: "FLOOR_MOD", 95: "RANGE",
|
||||
96: "RESIZE_NEAREST_NEIGHBOR", 97: "LEAKY_RELU", 98: "SQUARED_DIFFERENCE",
|
||||
99: "MIRROR_PAD", 100: "ABS", 101: "SPLIT_V", 102: "UNIQUE",
|
||||
103: "CEIL", 104: "REVERSE_V2", 105: "ADD_N", 106: "GATHER_ND",
|
||||
107: "COS", 108: "WHERE", 109: "RANK", 110: "ELU", 111: "REVERSE_SEQUENCE",
|
||||
112: "MATRIX_DIAG", 113: "QUANTIZE", 114: "MATRIX_SET_DIAG", 115: "ROUND",
|
||||
116: "HARD_SWISH", 117: "IF", 118: "WHILE", 119: "NON_MAX_SUPPRESSION_V4",
|
||||
120: "NON_MAX_SUPPRESSION_V5", 121: "SCATTER_ND", 122: "SELECT_V2",
|
||||
123: "DENSIFY", 124: "SEGMENT_SUM", 125: "BATCH_MATMUL", 126: "PLACEHOLDER_FOR_GREATER_OP_CODES",
|
||||
127: "CUMSUM", 128: "CALL_ONCE", 129: "BROADCAST_TO", 130: "RFFT2D",
|
||||
131: "CONV_3D", 132: "IMAG", 133: "REAL", 134: "COMPLEX_ABS", 135: "HASHTABLE",
|
||||
136: "HASHTABLE_FIND", 137: "HASHTABLE_IMPORT", 138: "HASHTABLE_SIZE",
|
||||
139: "REDUCE_ALL", 140: "CONV_3D_TRANSPOSE", 141: "VAR_HANDLE",
|
||||
142: "READ_VARIABLE", 143: "ASSIGN_VARIABLE", 144: "BROADCAST_ARGS",
|
||||
145: "RANDOM_STANDARD_NORMAL", 146: "BUCKETIZE", 147: "RANDOM_UNIFORM",
|
||||
148: "MULTINOMIAL", 149: "GELU", 150: "DYNAMIC_UPDATE_SLICE",
|
||||
151: "IRFFT2D", 152: "EXP", 153: "PRELU", 154: "MAXIMUM", 155: "ARG_MAX",
|
||||
156: "ARG_MIN", 157: "GELU", 158: "DYNAMIC_UPDATE_SLICE", 159: "RELU_0_TO_1",
|
||||
160: "REDUCE_PROD", 161: "RELU_0_TO_1", 162: "REDUCE_PROD"}
|
||||
|
||||
|
||||
class FB:
|
||||
def __init__(self, data):
|
||||
self.d = data
|
||||
|
||||
def u32(self, p): return struct.unpack_from("<I", self.d, p)[0]
|
||||
def i32(self, p): return struct.unpack_from("<i", self.d, p)[0]
|
||||
def u16(self, p): return struct.unpack_from("<H", self.d, p)[0]
|
||||
|
||||
def field(self, t, i):
|
||||
vto = self.i32(t); vt = t - vto; vs = self.u16(vt)
|
||||
off = vt + 4 + 2 * i
|
||||
if off + 2 > vt + vs: return None
|
||||
f = self.u16(off)
|
||||
return None if f == 0 else t + f
|
||||
|
||||
def deref(self, p): return p + self.u32(p) if p is not None else None
|
||||
|
||||
def vec(self, p):
|
||||
p = self.deref(p)
|
||||
if p is None: return None
|
||||
return p + 4, self.u32(p)
|
||||
|
||||
def vtab(self, p):
|
||||
s, n = self.vec(p)
|
||||
return [s + i * 4 + self.u32(s + i * 4) for i in range(n)]
|
||||
|
||||
def ivec(self, p):
|
||||
s, n = self.vec(p)
|
||||
return [self.i32(s + 4 * i) for i in range(n)]
|
||||
|
||||
|
||||
def main(path, n_ops=8):
|
||||
fb = FB(open(path, "rb").read())
|
||||
root = fb.u32(0)
|
||||
model = root
|
||||
opcodes = fb.vtab(fb.field(model, 1))
|
||||
subgraphs = fb.vtab(fb.field(model, 2))
|
||||
sg = subgraphs[0]
|
||||
tensors = fb.vtab(fb.field(sg, 0))
|
||||
ops = fb.vtab(fb.field(sg, 3))
|
||||
buffers = fb.vtab(fb.field(model, 4))
|
||||
|
||||
def tdesc(i):
|
||||
t = tensors[i]
|
||||
shape = fb.ivec(fb.field(t, 0))
|
||||
tt = fb.field(t, 1)
|
||||
ttype = TENSOR_TYPE.get(fb.d[tt], fb.d[tt]) if tt else "?"
|
||||
buf = fb.field(t, 2)
|
||||
bi = fb.u32(buf) if buf else 0
|
||||
name = fb.deref(fb.field(t, 3))
|
||||
if name: name = fb.d[name + 4:name + 4 + fb.u32(name)].decode("utf-8", "replace")
|
||||
return i, name, shape, ttype, bi
|
||||
|
||||
def opcode_of(op):
|
||||
idx_f = fb.field(op, 0) # opcode_index 是内联标量
|
||||
idx = fb.u32(idx_f) if idx_f else 0
|
||||
oc = opcodes[idx]
|
||||
bc_f = fb.field(oc, 1)
|
||||
bc = fb.i32(bc_f) if bc_f else 0
|
||||
return BUILTIN.get(bc, bc)
|
||||
|
||||
for oi, op in enumerate(ops[:n_ops]):
|
||||
ins = fb.ivec(fb.field(op, 1))
|
||||
outs = fb.ivec(fb.field(op, 2))
|
||||
print(f"op{oi} {opcode_of(op)}")
|
||||
for i in ins:
|
||||
ti, name, shape, ttype, bi = tdesc(i)
|
||||
extra = ""
|
||||
if ttype in ("INT32", "UINT8", "INT64", "INT16", "UINT16", "INT8") and bi and bi < len(buffers):
|
||||
data = buffers[bi]
|
||||
if data:
|
||||
start = fb.field(data, 0)
|
||||
size = fb.u32(start) if start else 0
|
||||
if 0 < size <= 64:
|
||||
extra = f" const={list(fb.d[start + 4:start + 4 + size])}"
|
||||
print(f" in {name} {shape} {ttype}{extra}")
|
||||
for i in outs:
|
||||
ti, name, shape, ttype, bi = tdesc(i)
|
||||
print(f" out {name} {shape} {ttype}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv[1], int(sys.argv[2]) if len(sys.argv) > 2 else 8)
|
||||
@@ -1,48 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""从 RF-DETR 候选标注整理 YOLO 训练集(class 0 确认野鸡 + class 1 疑似/藏身点, 80/20 拆分)"""
|
||||
import os
|
||||
import random
|
||||
import shutil
|
||||
|
||||
BASE = os.path.dirname(os.path.abspath(__file__))
|
||||
IMG_SRC = os.path.join(BASE, "datasets/images/pheasant")
|
||||
LBL_SRC = os.path.join(BASE, "datasets/labels/pheasant_rfdetr")
|
||||
OUT = os.path.join(BASE, "datasets/yolo")
|
||||
|
||||
random.seed(42)
|
||||
|
||||
stems = sorted(f[:-4] for f in os.listdir(LBL_SRC) if f.endswith(".txt"))
|
||||
kept, dropped = [], []
|
||||
for s in stems:
|
||||
src_lbl = os.path.join(LBL_SRC, s + ".txt")
|
||||
with open(src_lbl, encoding="utf-8") as f:
|
||||
lines = [l for l in f.read().splitlines() if l]
|
||||
if not lines:
|
||||
dropped.append(s)
|
||||
continue
|
||||
kept.append((s, lines))
|
||||
|
||||
random.shuffle(kept)
|
||||
n_val = max(1, round(len(kept) * 0.2))
|
||||
val, train = kept[:n_val], kept[n_val:]
|
||||
|
||||
for split, items in (("train", train), ("val", val)):
|
||||
for d in (os.path.join(OUT, "images", split), os.path.join(OUT, "labels", split)):
|
||||
os.makedirs(d, exist_ok=True)
|
||||
for s, lines in items:
|
||||
shutil.copy(os.path.join(IMG_SRC, s + ".jpg"), os.path.join(OUT, "images", split, s + ".jpg"))
|
||||
with open(os.path.join(OUT, "labels", split, s + ".txt"), "w", encoding="utf-8") as f:
|
||||
f.write("\n".join(lines) + "\n")
|
||||
|
||||
yaml = """path: /opt/pheasant_data/datasets/yolo
|
||||
train: images/train
|
||||
val: images/val
|
||||
names:
|
||||
0: pheasant
|
||||
1: suspect
|
||||
"""
|
||||
with open(os.path.join(OUT, "dataset.yaml"), "w", encoding="utf-8") as f:
|
||||
f.write(yaml)
|
||||
|
||||
print(f"train: {len(train)} 张, val: {len(val)} 张, 空标注被剔除: {len(dropped)} 张")
|
||||
print("剔除:", ", ".join(dropped) if dropped else "无")
|
||||
@@ -1,21 +0,0 @@
|
||||
from ultralytics import YOLO
|
||||
import os
|
||||
|
||||
BASE = os.path.dirname(os.path.abspath(__file__))
|
||||
YOLO_DIR = os.path.join(BASE, "datasets/yolo")
|
||||
data = os.path.join(YOLO_DIR, "dataset_local.yaml")
|
||||
with open(data, "w", encoding="utf-8") as f:
|
||||
f.write(f"""path: {YOLO_DIR}
|
||||
train: images/train
|
||||
val: images/val
|
||||
names:
|
||||
0: pheasant
|
||||
1: suspect
|
||||
""")
|
||||
|
||||
model = YOLO("yolov8n.pt")
|
||||
model.train(data=data, imgsz=1024, epochs=150, patience=30, batch=8,
|
||||
device="mps", workers=4,
|
||||
# project 必须绝对路径,相对路径会被拼到默认 runs/detect 下造成双层嵌套
|
||||
project=os.path.join(BASE, "runs/pheasant"), name="yolov8n_1024",
|
||||
exist_ok=True, plots=True)
|
||||
@@ -1,18 +0,0 @@
|
||||
from ultralytics import YOLO
|
||||
import os
|
||||
|
||||
# 训练分辨率与端侧推理对齐:tflite 导出/真机推理均为 704x704。
|
||||
# 训练 1280 会让模型对"清晰大目标"自信,推理时目标变小置信度崩坏(误报根源)。
|
||||
# multi_scale=0.5 随机缩放输入 0.5~1.5x,进一步抗尺度漂移。
|
||||
# 训练完成后导出 tflite(imgsz=704,与训练一致)。
|
||||
os.chdir("/opt/pheasant_data")
|
||||
model = YOLO("yolov8n.pt")
|
||||
model.train(data="datasets/yolo/dataset.yaml", imgsz=704, epochs=150,
|
||||
patience=30, batch=16, device=0, workers=4,
|
||||
# project 必须绝对路径,相对路径会被拼到默认 runs/detect 下造成双层嵌套
|
||||
project="/opt/pheasant_data/runs/pheasant", name="yolov8n_704",
|
||||
exist_ok=True, plots=True, multi_scale=0.5)
|
||||
|
||||
# 导出 tflite:输入固定 [1,3,704,704],与 flutter_app/assets/model.tflite 一致
|
||||
model.export(format="tflite", imgsz=704)
|
||||
print("EXPORT_DONE: /opt/pheasant_data/runs/pheasant/yolov8n_704/weights/best.tflite")
|
||||
Reference in New Issue
Block a user