如何在R或Python中半自动检测并量化叶片受损面积?
解决方案:R/Python 半自动叶片受损百分比计算
R 实现方案
基于你已使用的pliman包,结合imager和geometry包完成需求:
1. 单叶片识别
针对白色背景,通过灰度阈值分割分离叶片与背景,再检测连通区域实现单叶片识别:
library(pliman) library(imager) library(geometry) # 读取图像 img <- image_read("leaf_image.jpg") # 灰度化+二值化分割(根据图像调整阈值方向) seg <- segment(img, threshold = "otsu", invert = TRUE) # invert=TRUE适用于叶片比背景暗的场景 # 检测独立叶片对象(过滤小噪声) leaf_objects <- object_detect(seg, min_area = 100)
2. 凸包补全缺失边缘
用geometry::convhulln生成每个叶片的凸包,补全受损缺失的边缘:
# 为每个叶片生成凸包顶点 hulls <- lapply(leaf_objects$coords, function(coord) { ch <- convhulln(coord[, c("x", "y")]) coord[unique(as.vector(ch)), ] }) # 可视化验证凸包(可选) plot(img) lapply(hulls, function(h) lines(h$x, h$y, col = "red", lwd = 2))
3. 面积计算(受损区+补全后总面积)
通过HSV颜色空间提取红色受损区域,结合凸包面积计算百分比:
# 交互选择红色区域(更精准,推荐) red_mask <- select_color(img, color = "red", plot = TRUE) # 或手动设置HSV阈值(根据实际图像调整) # red_mask <- color_space(img, space = "hsv") %>% # filter(H < 10 | H > 350, S > 0.3, V > 0.2) %>% # image_mask() # 计算每个叶片的面积指标(假设1cm对应100像素,根据你的尺度调整) pixel_per_cm <- 100 results <- lapply(seq_along(leaf_objects$coords), function(i) { # 原始叶片面积 leaf_area <- object_area(leaf_objects$coords[[i]], pixel_per_cm = pixel_per_cm) # 凸包补全后的总面积 hull_area <- convhulln(leaf_objects$coords[[i]][, c("x", "y")], area = TRUE)$area / (pixel_per_cm^2) # 叶片内的受损区域面积 red_in_leaf <- image_crop(red_mask, leaf_objects$bbox[[i]]) damaged_area <- sum(red_in_leaf) / (pixel_per_cm^2) data.frame( leaf_id = i, original_leaf_area_cm2 = round(leaf_area, 2), convex_hull_total_cm2 = round(hull_area, 2), damaged_area_cm2 = round(damaged_area, 2), damage_percentage = round((damaged_area / hull_area) * 100, 2) ) }) # 合并结果 results_df <- do.call(rbind, results) print(results_df)
Python 实现方案
用OpenCV+SciPy实现,适合偏好Python的场景:
1. 单叶片识别
通过灰度阈值+轮廓检测分离独立叶片:
import cv2 import numpy as np from scipy.spatial import ConvexHull # 读取图像 img = cv2.imread("leaf_image.jpg") gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化分割(OTSU自动阈值,反转适用于叶片暗、背景白) _, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) # 去除噪声 kernel = np.ones((3,3), np.uint8) thresh = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2) # 提取叶片轮廓(过滤小噪声轮廓) contours, _ = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) leaf_contours = [cnt for cnt in contours if cv2.contourArea(cnt) > 100]
2. 凸包补全缺失边缘
用OpenCV内置的convexHull生成凸包:
# 生成每个叶片的凸包 hulls = [cv2.convexHull(cnt) for cnt in leaf_contours] # 可视化验证(可选) vis = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) cv2.drawContours(vis, leaf_contours, -1, (0,255,0), 2) cv2.drawContours(vis, hulls, -1, (255,0,0), 2) cv2.imshow("Leaves + Convex Hulls", vis) cv2.waitKey(0)
3. 面积计算
提取红色受损区域,计算各面积指标:
# HSV空间提取红色(覆盖红色的两个HSV区间) hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) lower_red1 = np.array([0, 50, 50]) upper_red1 = np.array([10, 255, 255]) lower_red2 = np.array([170, 50, 50]) upper_red2 = np.array([180, 255, 255]) mask1 = cv2.inRange(hsv, lower_red1, upper_red1) mask2 = cv2.inRange(hsv, lower_red2, upper_red2) red_mask = cv2.bitwise_or(mask1, mask2) red_mask = cv2.morphologyEx(red_mask, cv2.MORPH_OPEN, kernel, iterations=2) # 尺度参数:1cm对应像素数(根据你的参考尺度调整) pixel_per_cm = 100 pixel_per_cm2 = pixel_per_cm ** 2 # 计算每个叶片的结果 results = [] for i, (cnt, hull) in enumerate(zip(leaf_contours, hulls), 1): # 原始叶片面积 leaf_area = cv2.contourArea(cnt) / pixel_per_cm2 # 凸包总面积 hull_area = cv2.contourArea(hull) / pixel_per_cm2 # 生成叶片掩码,提取叶片内的受损区域 leaf_mask = np.zeros_like(gray) cv2.drawContours(leaf_mask, [cnt], 0, 255, -1) damaged_mask = cv2.bitwise_and(red_mask, leaf_mask) damaged_area = cv2.countNonZero(damaged_mask) / pixel_per_cm2 # 受损百分比 damage_pct = round((damaged_area / hull_area) * 100, 2) if hull_area > 0 else 0 results.append({ "leaf_id": i, "original_leaf_area_cm2": round(leaf_area, 2), "convex_hull_total_cm2": round(hull_area, 2), "damaged_area_cm2": round(damaged_area, 2), "damage_percentage": damage_pct }) # 输出结果 for res in results: print(res)
关键提示
- 阈值参数(灰度、HSV)需根据你的实际图像微调,交互选色工具(R的
select_color)能大幅提升准确率 - 若叶片与背景灰度差异小,可尝试Canny边缘检测结合轮廓提取
- 半自动场景下,可手动框选ROI(R的
select_roi、Python的cv2.selectROI)缩小分析范围,减少噪声干扰
内容的提问来源于stack exchange,提问作者M Sandoval
相关产品推荐
相关产品推荐

