feat: 简化版增加Otsu阈值分割
输出改为左右对比图(网格划线 vs Otsu分割)
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 203 KiB After Width: | Height: | Size: 242 KiB |
+67
-13
@@ -1,10 +1,10 @@
|
|||||||
"""
|
"""
|
||||||
cDNA微阵列图像处理 —— 简化版网格划分
|
cDNA微阵列图像处理 —— 简化版
|
||||||
======================================
|
======================================
|
||||||
|
|
||||||
D:\ProgramData\anaconda3\envs\my_env\python.exe src/cDNA_gridding_simple.py
|
D:\ProgramData\anaconda3\envs\my_env\python.exe src/cDNA_gridding_simple.py
|
||||||
|
|
||||||
算法步骤:
|
算法步骤(划线):
|
||||||
|
|
||||||
1. 彩色图 → 灰度图
|
1. 彩色图 → 灰度图
|
||||||
2. 横轴投影:对每一列的所有像素灰度值求和 → 得到一条曲线
|
2. 横轴投影:对每一列的所有像素灰度值求和 → 得到一条曲线
|
||||||
@@ -17,6 +17,15 @@ D:\ProgramData\anaconda3\envs\my_env\python.exe src/cDNA_gridding_simple.py
|
|||||||
- 等于 0 的地方 = 斑点与空隙的分界线(过零点)
|
- 等于 0 的地方 = 斑点与空隙的分界线(过零点)
|
||||||
6. 配对相邻的过零点(离开斑点 + 进入下一个斑点),
|
6. 配对相邻的过零点(离开斑点 + 进入下一个斑点),
|
||||||
中点就是空隙的中心 = 划线位置
|
中点就是空隙的中心 = 划线位置
|
||||||
|
|
||||||
|
算法步骤(Otsu阈值分割):
|
||||||
|
|
||||||
|
1. 遍历所有可能的 T (0~255)
|
||||||
|
2. 计算前景(>T)的方差 + 背景(≤T)的方差 → 类内方差
|
||||||
|
3. 计算前景均值 vs 背景均值的差距 → 类间方差
|
||||||
|
4. 选 T 使 类内方差最小 / 类间方差最大
|
||||||
|
5. 相当于在灰度直方图上找一个"谷底",两座山之间的最低处就是最佳分割线。
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@@ -154,6 +163,45 @@ def draw_grid_lines(gray: np.ndarray, pct: float = 0.10):
|
|||||||
return x_lines, y_lines
|
return x_lines, y_lines
|
||||||
|
|
||||||
|
|
||||||
|
def otsu_threshold(gray: np.ndarray) -> tuple[np.ndarray, float]:
|
||||||
|
"""
|
||||||
|
Otsu 自动阈值分割。
|
||||||
|
|
||||||
|
原理:
|
||||||
|
遍历灰度值 0~255,对每个候选 T:
|
||||||
|
- 将像素分为两组:前景(>T) 和 背景(≤T)
|
||||||
|
- 计算两组各自的权重 w 和方差 σ²
|
||||||
|
- 类内方差 = w_bg*σ²_bg + w_fg*σ²_fg
|
||||||
|
选使类内方差最小的 T = 最佳分割线。
|
||||||
|
"""
|
||||||
|
best_T = 0
|
||||||
|
best_cost = float('inf')
|
||||||
|
total = gray.size
|
||||||
|
|
||||||
|
for T in range(1, 255):
|
||||||
|
# 背景(灰度 ≤ T)
|
||||||
|
bg = gray[gray <= T]
|
||||||
|
w_bg = len(bg) / total
|
||||||
|
# 前景(灰度 > T)
|
||||||
|
fg = gray[gray > T]
|
||||||
|
w_fg = len(fg) / total
|
||||||
|
|
||||||
|
if w_bg == 0 or w_fg == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
var_bg = np.var(bg)
|
||||||
|
var_fg = np.var(fg)
|
||||||
|
# 类内方差 = 加权平均
|
||||||
|
cost = w_bg * var_bg + w_fg * var_fg
|
||||||
|
|
||||||
|
if cost < best_cost:
|
||||||
|
best_cost = cost
|
||||||
|
best_T = T
|
||||||
|
|
||||||
|
binary = (gray > best_T).astype(np.uint8)
|
||||||
|
return binary, best_T
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||||
|
|
||||||
@@ -162,24 +210,30 @@ def main():
|
|||||||
# 原图是 RGBA(红绿蓝+透明通道),取前三个通道转为灰度
|
# 原图是 RGBA(红绿蓝+透明通道),取前三个通道转为灰度
|
||||||
gray = (color.rgb2gray(img[:, :, :3]) * 255).astype(np.uint8)
|
gray = (color.rgb2gray(img[:, :, :3]) * 255).astype(np.uint8)
|
||||||
|
|
||||||
# ---- 执行网格检测 ----
|
# ---- 1. 网格划线 ----
|
||||||
x_lines, y_lines = draw_grid_lines(gray)
|
x_lines, y_lines = draw_grid_lines(gray)
|
||||||
print(f"检测到 {len(x_lines)} 条纵线, {len(y_lines)} 条横线")
|
print(f"检测到 {len(x_lines)} 条纵线, {len(y_lines)} 条横线")
|
||||||
|
|
||||||
# ---- 在原图上划线并保存 ----
|
# ---- 2. Otsu 阈值分割 ----
|
||||||
fig, ax = plt.subplots(figsize=(8, 8))
|
binary, T_otsu = otsu_threshold(gray)
|
||||||
ax.imshow(gray, cmap='gray')
|
print(f"Otsu 最佳阈值: {T_otsu}")
|
||||||
|
|
||||||
# 画纵向分割线(竖线)
|
# ---- 3. 输出:左右对比(划线 vs 分割)----
|
||||||
|
fig, axes = plt.subplots(1, 2, figsize=(14, 7))
|
||||||
|
|
||||||
|
# 左:网格划线
|
||||||
|
axes[0].imshow(gray, cmap='gray')
|
||||||
for x in x_lines:
|
for x in x_lines:
|
||||||
ax.axvline(x=x, color='lime', linewidth=0.5)
|
axes[0].axvline(x=x, color='lime', linewidth=0.5)
|
||||||
|
|
||||||
# 画横向分割线(横线)
|
|
||||||
for y in y_lines:
|
for y in y_lines:
|
||||||
ax.axhline(y=y, color='lime', linewidth=0.5)
|
axes[0].axhline(y=y, color='lime', linewidth=0.5)
|
||||||
|
axes[0].set_title(f'网格划分 ({len(x_lines)}×{len(y_lines)})', fontsize=13)
|
||||||
|
axes[0].axis('off')
|
||||||
|
|
||||||
ax.set_title(f'cDNA微阵列网格划分 ({len(x_lines)}×{len(y_lines)})', fontsize=14)
|
# 右:Otsu 分割结果
|
||||||
ax.axis('off')
|
axes[1].imshow(binary, cmap='gray')
|
||||||
|
axes[1].set_title(f'Otsu 阈值分割 (T={T_otsu})', fontsize=13)
|
||||||
|
axes[1].axis('off')
|
||||||
|
|
||||||
out_path = os.path.join(OUTPUT_DIR, 'gridding_simple.png')
|
out_path = os.path.join(OUTPUT_DIR, 'gridding_simple.png')
|
||||||
fig.savefig(out_path, dpi=150, bbox_inches='tight')
|
fig.savefig(out_path, dpi=150, bbox_inches='tight')
|
||||||
|
|||||||
Reference in New Issue
Block a user