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

- 阈值处理并聚焦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
相关产品推荐
相关产品推荐

