You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用PlantCV以像素为单位测量幼苗主根长度?

幼苗主根生长跟踪的PlantCV优化方案

问题背景

我正尝试使用PlantCV跟踪幼苗生长,目前先针对单株幼苗调试,之后再扩展至多株幼苗的生长跟踪。经过图像处理后已去除图像中的所有噪声,仅保留单株幼苗的根系区域(已刻意排除叶片,仅关注根系生长)。当前使用plantcv.segment_sort分离主根与侧根以仅测量主根生长,但该方法易出错,效果不够理想。

相关图像:

  • 原始图像:原始图像
  • 阈值处理并聚焦ROI后的图像:阈值处理后ROI图像
  • 含侧根的ROI示例:含侧根的ROI示例
  • 分割后的图像:分割后的图像

当前处理代码:

import os
import cv2
import numpy as np
import pandas as pd
import plantcv.plantcv as pcv


pcv.params.debug = "plot"
IMAGE_DIR = "TestPhotos"  # Folder containing sequential images of seedlings
OUTPUT_DIR = "results"
RESULTS_CSV = os.path.join(OUTPUT_DIR, "growth_data.csv")

# Create output folder
os.makedirs(OUTPUT_DIR, exist_ok=True)

# Initialize a results list
results = []

for img_name in sorted(os.listdir(IMAGE_DIR)):
    if not img_name.lower().endswith(('.jpg', '.png', '.jpeg', '.tif')):
        continue

    # Load image
    img_path = os.path.join(IMAGE_DIR, img_name)
    img, path, filename = pcv.readimage(img_path)

    # Convert to HSV and extract the green channel to isolate plants
    hsv = pcv.rgb2gray_hsv(img, 'v')

    # Otsu thresholding
    bin_mask = pcv.threshold.otsu(gray_img=hsv, object_type="light")
    
    # Clean up small noise
    cleaned_mask = pcv.fill(bin_img=bin_mask, size=40)
    # cleaned_mask = pcv.erode(gray_img=cleaned_mask, ksize=2, i=2)
    
    # Define ROI
    rect_roi = pcv.roi.rectangle(img=img, x=1350, y=1010, h=150, w=150)
    filtered_mask = pcv.roi.filter(mask=cleaned_mask, roi=rect_roi, roi_type='cutto')
    cv2.imwrite("roi_image.png", filtered_mask)
    
    # Binary mask of roots
    mask = filtered_mask.copy()

    # Define structuring element
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (13,13))

    # Dilate Image
    mask_closed = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
    mask_dilated = cv2.dilate(mask_closed, kernel, iterations=1)
    
    # Skeletonize image
    skeleton = pcv.morphology.skeletonize(mask=mask_dilated)
    pruned_skeleton, segmented_img, segment_objects = pcv.morphology.prune(skel_img=skeleton, size=30)
    
    # Analyze the skeleton for length
    pcv.params.sample_label = "plant"
    segmented_image,segment_objects = pcv.morphology.segment_skeleton(pruned_skeleton)
    leaf_obj, other_obj = pcv.morphology.segment_sort(skel_img=pruned_skeleton, objects=segment_objects)
    labeled_img = pcv.morphology.segment_path_length(segmented_img=segmented_image, objects=other_obj)
    
    path_lengths = pcv.outputs.observations['plant']['segment_path_length']['value']
    print(sum(path_lengths))
    break

优化方向

1. 替换segment_sort,基于主根形态特征筛选

segment_sort默认基于分支长度和角度区分“叶片”和其他结构,对根系场景适配性差。可直接从骨架分支中筛选主根:

  • 主根通常是最长的骨架分支,且方向相对单一(垂直向下)
  • 提取所有分支的长度和方向,筛选长度最大且方向符合预期的分支作为主根:
# 替代segment_sort的逻辑
# 获取所有分支的长度
segment_lengths = pcv.outputs.observations['plant']['segment_path_length']['value']
# 找到最长分支的索引
main_root_idx = np.argmax(segment_lengths)
main_root_obj = [segment_objects[main_root_idx]]
# 仅分析主根
labeled_img = pcv.morphology.segment_path_length(segmented_img=segmented_image, objects=main_root_obj)

2. 优化骨架预处理,减少分支干扰

当前形态学操作可能过度膨胀,导致侧根与主根连接处模糊,影响分支分割:

  • 调整膨胀/闭运算的核大小:把(13,13)缩小到(7,7)或(9,9),避免主根与侧根过度融合
  • 调整剪枝参数:pcv.morphology.prune的size参数可适当减小(比如15-20),保留主根的同时修剪过短的侧根杂枝

3. 基于ROI定位主根起点

如果主根的起点(胚轴位置)在ROI内固定,可先定位起点,再提取从起点出发的最长路径:

# 假设主根起点在ROI的上方区域(根据实际图像调整坐标)
start_point = (1425, 1010)
# 从起点出发追踪最长骨架路径
main_root_skeleton = pcv.morphology.trace_skeleton(skel_img=pruned_skeleton, start_point=start_point)

4. 结合距离变换筛选主根

主根宽度通常比侧根大,可通过距离变换提取宽区域对应的骨架:

# 对二值掩码做距离变换
dist_transform = cv2.distanceTransform(mask, cv2.DIST_L2, 5)
# 设定阈值,保留距离大的区域(主根区域)
main_root_mask = dist_transform > 5  # 阈值根据实际图像调整
# 用主根掩码过滤骨架
filtered_skeleton = cv2.bitwise_and(skeleton, skeleton, mask=main_root_mask.astype(np.uint8))
# 再对过滤后的骨架进行分支分析

内容的提问来源于stack exchange,提问作者Calvin Yong

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 02:24:55