Spaces:
Running
Running
| import numpy as np | |
| from skimage.filters import threshold_otsu | |
| from skimage.morphology import remove_small_objects | |
| from skimage.feature import hog | |
| from skimage.measure import moments_hu, label | |
| from skimage.morphology import skeletonize, binary_dilation, disk | |
| from scipy.ndimage import convolve | |
| from skimage.morphology import binary_opening | |
| def _prune_spurs(skel: np.ndarray, iters: int = 2) -> np.ndarray: | |
| """ | |
| 迭代剪掉骨架上长度很短的端点分支(spur)。 | |
| iters 表示最多向内剪掉几步(像素)。推荐 1~3。 | |
| """ | |
| s = skel.copy().astype(bool) | |
| # 用 3x3 邻域统计端点:中心权重10,其它1;“10+1=11”即1个邻居的端点 | |
| K = np.array([[1,1,1], | |
| [1,10,1], | |
| [1,1,1]], dtype=np.uint8) | |
| for _ in range(iters): | |
| nb = convolve(s.astype(np.uint8), K, mode="constant", cval=0) | |
| endpoints = (nb == 11) # 只有 1 个邻居 | |
| # 只剪 endpoints,不动分叉/主干 | |
| s = s & ~endpoints | |
| return s | |
| def _ensure_ink_true(bw_bool: np.ndarray) -> np.ndarray: | |
| bw = bw_bool.astype(bool) | |
| if bw.mean() > 0.5: | |
| bw = ~bw | |
| return bw | |
| def stroke_normalize(bw: np.ndarray, target_px: int = 2) -> np.ndarray: | |
| if bw.dtype != bool: | |
| bw = (bw > 0) | |
| if bw.mean() > 0.5: | |
| bw = ~bw | |
| skel = skeletonize(bw) | |
| skel = _prune_spurs(skel, iters=2) # ← 新增:剪短刺,去笔锋小尖 | |
| if target_px <= 1: | |
| return skel.astype(np.float32) | |
| rad = max(1, int(round(target_px/2))) | |
| thick = binary_dilation(skel, disk(rad)) | |
| #thick = binary_opening(thick, disk(1))#optional | |
| return (thick & bw).astype(np.float32) | |
| def to_64_gray(imgPIL): | |
| return np.array(imgPIL, dtype=np.uint8) | |
| def binarize(gray64, min_size: int = 4, keep_largest: bool = False): | |
| if gray64.ndim == 3: | |
| gray64 = gray64[..., 0] | |
| g = gray64.astype(float) | |
| if g.max() > 1: | |
| g /= 255.0 | |
| t = threshold_otsu(g) | |
| bw = (g <= t) | |
| bw = remove_small_objects(bw.astype(bool), min_size=min_size).astype(bool) | |
| if keep_largest: | |
| lab = label(bw) | |
| if lab.max() > 0: | |
| areas = np.bincount(lab.ravel()); areas[0] = 0 | |
| bw = (lab == areas.argmax()) | |
| return bw | |
| def proj_features(bw): | |
| # normalization | |
| hp = bw.sum(axis=0); vp = bw.sum(axis=1) | |
| if hp.max()>0: hp = hp/hp.max() | |
| if vp.max()>0: vp = vp/vp.max() | |
| # fix dimension | |
| def pool(v, m=32): | |
| idx = np.linspace(0, len(v), m+1, endpoint=True).astype(int) | |
| return np.array([v[idx[i]:idx[i+1]].mean() for i in range(m)], dtype=np.float32) | |
| return np.concatenate([pool(hp), pool(vp)]) | |
| def feat_vec(bw): | |
| hu = moments_hu(bw).astype(np.float32) | |
| hu = np.sign(hu)*np.log1p(np.abs(hu)) | |
| hogv = hog(bw, orientations=9, pixels_per_cell=(8,8), cells_per_block=(2,2), | |
| block_norm='L2-Hys', feature_vector=True).astype(np.float32) | |
| proj = proj_features(bw).astype(np.float32) | |
| v = np.concatenate([hu, hogv, proj]).astype(np.float32) | |
| n = np.linalg.norm(v) + 1e-8 | |
| return v / n | |
| def cosine_sim(a, B): | |
| return (B @ a) / (np.linalg.norm(a)+1e-8) / (np.linalg.norm(B,axis=1)+1e-8) | |