初始提交

This commit is contained in:
2026-02-25 09:42:51 +08:00
parent c25276c481
commit d84d886f35
182 changed files with 18438 additions and 0 deletions

391
shape_spectral.py Normal file
View File

@ -0,0 +1,391 @@
# 去除过曝光像素
# 导入必要的库
import matplotlib
matplotlib.use('TkAgg')
from plantcv import plantcv as pcv
import get_glcm
import os
import cv2
import numpy as np
from plantcv.plantcv._helpers import _iterate_analysis, _cv2_findcontours, _object_composition, _grayscale_to_rgb, \
_scale_size
from plantcv.plantcv import outputs, within_frame
from plantcv.plantcv import params
from plantcv.plantcv._debug import _debug
from outputs2dataframe import process_plantcv_outputs
def size(img, labeled_mask, n_labels=1, label=None):
"""A function that analyzes the shape and size of objects and outputs data.
Inputs:
img = RGB or grayscale image data for plotting
labeled_mask = Labeled mask of objects (32-bit).
n_labels = Total number expected individual objects (default = 1).
label = Optional label parameter, modifies the variable name of
observations recorded (default = pcv.params.sample_label).
Returns:
analysis_image = Diagnostic image showing measurements.
:param img: numpy.ndarray
:param labeled_mask: numpy.ndarray
:param n_labels: int
:param label: str
:return analysis_image: numpy.ndarray
"""
# Set lable to params.sample_label if None
if label is None:
label = params.sample_label
img = _iterate_analysis(img=img, labeled_mask=labeled_mask, n_labels=n_labels, label=label, function=_analyze_size)
# Debugging
_debug(visual=img, filename=os.path.join(params.debug_outdir, str(params.device) + '_shapes.png'))
return img
def _analyze_size(img, mask, label):
"""Analyze the size and shape of individual objects."""
params.device += 1
# Initialize analysis output values
area = 0
hull_area = 0
solidity = 0
perimeter = 0
width = 0
height = 0
caliper_length = 0
longest_path = 0
cmx = 0
cmy = 0
hull_vertices = 0
circularity = 0
shape_factor = 0
aspect_ratio = 0
moments = {}
# Check if the object is touching image boundaries (QC)
in_bounds = within_frame(mask=mask, label=label)
# Convert grayscale images to color
img = _grayscale_to_rgb(img)
# Plot image
plt_img = np.copy(img)
# Find contours
cnt, cnt_str = _cv2_findcontours(bin_img=mask)
# Consolidate contours
obj = _object_composition(contours=cnt, hierarchy=cnt_str)
contour_list = obj.squeeze().tolist()
# Analyze shape properties if the object is large enough
if len(obj) > 5:
# Convex Hull
hull = cv2.convexHull(obj)
# Number of convex hull vertices
hull_vertices = len(hull)
# Convex Hull area
hull_area = cv2.contourArea(hull)
# Moments
m = cv2.moments(mask, binaryImage=True)
# Area
area = m['m00']
# Solidity
solidity = area / hull_area if hull_area != 0 else 1
# Perimeter
perimeter = cv2.arcLength(obj, closed=True)
# Bounding rectangle
x, y, width, height = cv2.boundingRect(obj)
# Centroid/Center of Mass
cmx = m['m10'] / m['m00']
cmy = m['m01'] / m['m00']
# Aspect ratio
aspect_ratio = width / float(height) if height != 0 else 0
# Circularity (4π * area / perimeter^2)
if perimeter > 0:
circularity = (4 * np.pi * area) / (perimeter ** 2)
# Shape Factor (perimeter^2 / (4π * area))
if area > 0:
shape_factor = (perimeter ** 2) / (4 * np.pi * area)
# Hu Moments (seven invariant moments)
# hu_moments = cv2.HuMoments(m).flatten()
# Store Hu Moments in the moments dictionary
# for i, hu_moment in enumerate(hu_moments):
# moments[f"hu_moment_{i + 1}"] = hu_moment
# Store outputs
outputs.add_metadata(term="image_height", datatype=int, value=np.shape(img)[0])
outputs.add_metadata(term="image_width", datatype=int, value=np.shape(img)[1])
outputs.add_observation(sample=label, variable='area', trait='area',
method='plantcv.plantcv.analyze.size', scale=params.unit, datatype=int,
value=_scale_size(area, "area"), label=params.unit)
outputs.add_observation(sample=label, variable='convex_hull_area', trait='convex hull area',
method='plantcv.plantcv.analyze.size', scale=params.unit, datatype=int,
value=_scale_size(hull_area, "area"), label=params.unit)
outputs.add_observation(sample=label, variable='solidity', trait='solidity',
method='plantcv.plantcv.analyze.size', scale='none', datatype=float,
value=solidity, label='none')
outputs.add_observation(sample=label, variable='perimeter', trait='perimeter',
method='plantcv.plantcv.analyze.size', scale=params.unit, datatype=int,
value=_scale_size(perimeter), label=params.unit)
outputs.add_observation(sample=label, variable='width', trait='width',
method='plantcv.plantcv.analyze.size', scale=params.unit, datatype=int,
value=_scale_size(width), label=params.unit)
outputs.add_observation(sample=label, variable='height', trait='height',
method='plantcv.plantcv.analyze.size', scale=params.unit, datatype=int,
value=_scale_size(height), label=params.unit)
outputs.add_observation(sample=label, variable='longest_path', trait='longest path',
method='plantcv.plantcv.analyze.size', scale=params.unit, datatype=int,
value=_scale_size(float(longest_path)), label=params.unit)
outputs.add_observation(sample=label, variable='center_of_mass', trait='center of mass',
method='plantcv.plantcv.analyze.size', scale='none', datatype=tuple,
value=(cmx, cmy), label=("x", "y"))
outputs.add_observation(sample=label, variable='convex_hull_vertices', trait='convex hull vertices',
method='plantcv.plantcv.analyze.size', scale='none', datatype=int,
value=hull_vertices, label='none')
outputs.add_observation(sample=label, variable='object_in_frame', trait='object in frame',
method='plantcv.plantcv.analyze.size', scale='none', datatype=bool,
value=in_bounds, label='none')
# Add new features: Circularity, Shape Factor, Aspect Ratio, and Hu Moments
outputs.add_observation(sample=label, variable='circularity', trait='circularity',
method='plantcv.plantcv.analyze.size', scale='none', datatype=float,
value=circularity, label='none')
outputs.add_observation(sample=label, variable='shape_factor', trait='shape_factor',
method='plantcv.plantcv.analyze.size', scale='none', datatype=float,
value=shape_factor, label='none')
outputs.add_observation(sample=label, variable='aspect_ratio', trait='aspect_ratio',
method='plantcv.plantcv.analyze.size', scale='none', datatype=float,
value=aspect_ratio, label='none')
# Store Hu Moments from moments dictionary
# for moment_name, moment_value in moments.items():
# outputs.add_observation(sample=label, variable=moment_name, trait=moment_name,
# method='plantcv.plantcv.analyze.size', scale='none', datatype=float,
# value=moment_value, label='none')
# Store the contour object in outputs
outputs.add_observation(sample=label, variable='contour', trait='contour',
method='plantcv.plantcv.analyze.size', scale='none', datatype=list,
value=contour_list, label='none')
return plt_img
def _longest_axis(height, width, hull, cmx, cmy):
"""
Calculate the line through center of mass and point on the convex hull that is furthest away
:param height: int
:param width: int
:param hull: numpy.ndarray
:param cmx: int
:param cmy: int
:return caliper_length: int
"""
background = np.zeros((height, width, 3), np.uint8)
background1 = np.zeros((height, width), np.uint8)
background2 = np.zeros((height, width), np.uint8)
# Longest Axis: line through center of mass and point on the convex hull that is furthest away
cv2.circle(background, (int(cmx), int(cmy)), 4, (255, 255, 255), -1)
center_p = cv2.cvtColor(background, cv2.COLOR_BGR2GRAY)
_, centerp_binary = cv2.threshold(center_p, 0, 255, cv2.THRESH_BINARY)
centerpoint, _ = _cv2_findcontours(bin_img=centerp_binary)
dist = []
vhull = np.vstack(hull)
for i, c in enumerate(vhull):
xy = tuple(int(ci) for ci in c)
pptest = cv2.pointPolygonTest(centerpoint[0], xy, measureDist=True)
dist.append(pptest)
abs_dist = np.absolute(dist)
max_i = np.argmax(abs_dist)
caliper_max_x, caliper_max_y = list(tuple(vhull[max_i]))
caliper_mid_x, caliper_mid_y = [int(cmx), int(cmy)]
xdiff = float(caliper_max_x - caliper_mid_x)
ydiff = float(caliper_max_y - caliper_mid_y)
# Set default values
slope = 1
if xdiff != 0:
slope = float(ydiff / xdiff)
b_line = caliper_mid_y - (slope * caliper_mid_x)
if slope != 0:
xintercept = int(-b_line / slope)
xintercept1 = int((height - b_line) / slope)
if 0 <= xintercept <= width and 0 <= xintercept1 <= width:
cv2.line(background1, (xintercept1, height), (xintercept, 0), (255), params.line_thickness)
elif xintercept < 0 or xintercept > width or xintercept1 < 0 or xintercept1 > width:
yintercept = int(b_line)
yintercept1 = int((slope * width) + b_line)
cv2.line(background1, (0, yintercept), (width, yintercept1), (255), 5)
else:
cv2.line(background1, (width, caliper_mid_y), (0, caliper_mid_y), (255), params.line_thickness)
_, line_binary = cv2.threshold(background1, 0, 255, cv2.THRESH_BINARY)
cv2.drawContours(background2, [hull], -1, (255), -1)
_, hullp_binary = cv2.threshold(background2, 0, 255, cv2.THRESH_BINARY)
caliper = cv2.multiply(line_binary, hullp_binary)
caliper_y, caliper_x = np.array(caliper.nonzero())
caliper_matrix = np.vstack((caliper_x, caliper_y))
caliper_transpose = np.transpose(caliper_matrix)
caliper_length = len(caliper_transpose)
caliper_transpose1 = np.lexsort((caliper_y, caliper_x))
caliper_transpose2 = [(caliper_x[i], caliper_y[i]) for i in caliper_transpose1]
caliper_transpose = np.array(caliper_transpose2)
return caliper_length, caliper_transpose
def process_image_glcm(img, nbit=64, mi=0, ma=255, slide_window=3, step=[2], angle=[0]):
# 如果只有单波段图像,调整为多波段格式
if len(img.shape) == 2:
img = np.expand_dims(img, axis=0)
bands, h, w = img.shape
print(f"Image has {bands} bands.")
# 初始化存储所有波段特征的列表
all_band_features = []
for b in range(bands):
print(f"Processing band {b + 1}...")
# 获取当前波段并归一化到 0-255
band = img[b]
band = np.uint8(255.0 * (band - np.min(band)) / (np.max(band) - np.min(band)))
# 计算 GLCM
glcm = get_glcm.calcu_glcm(band, mi, ma, nbit, slide_window, step, angle)
# 计算当前波段的 GLCM 特征
features = []
for i in range(glcm.shape[2]):
for j in range(glcm.shape[3]):
glcm_cut = glcm[:, :, i, j, :, :]
features.append(get_glcm.calcu_glcm_mean(glcm_cut, nbit))
features.append(get_glcm.calcu_glcm_variance(glcm_cut, nbit))
features.append(get_glcm.calcu_glcm_homogeneity(glcm_cut, nbit))
features.append(get_glcm.calcu_glcm_contrast(glcm_cut, nbit))
features.append(get_glcm.calcu_glcm_dissimilarity(glcm_cut, nbit))
features.append(get_glcm.calcu_glcm_entropy(glcm_cut, nbit))
features.append(get_glcm.calcu_glcm_energy(glcm_cut, nbit))
features.append(get_glcm.calcu_glcm_correlation(glcm_cut, nbit))
features.append(get_glcm.calcu_glcm_Auto_correlation(glcm_cut, nbit))
# 堆叠当前波段的所有特征
features = np.array(features)
all_band_features.append(features)
# 将所有波段的特征堆叠
all_band_features = np.vstack(all_band_features)
return all_band_features
def process_images(full_bil_path, mask, outdir='None', debug="None"):
"""
处理光谱图像和掩膜文件,进行分析并保存结果。
Parameters:
full_bil_path (str): 光谱图像文件路径,包含 .bil 文件。
mask (numpy.ndarray): 16位标签掩膜图像每个对象的像素值对应其标签ID。
outdir (str): 输出结果文件夹路径。
debug (str): 调试模式('plot''print',默认为 'print')。
Returns:
combined_data: 分析结果的数据列表
"""
# 定义选项类
class options:
def __init__(self):
self.image = full_bil_path # 输入的光谱图像路径
self.debug = debug # 设置调试模式:'plot' 或 'print'
self.writeimg = False # 是否保存图片
self.result = outdir # 输出结果文件路径
self.outdir = outdir # 输出文件夹路径
# 初始化参数
args = options()
# 设置 PlantCV 的全局参数
pcv.params.debug = args.debug
pcv.params.dpi = 100 # 设置 DPI
pcv.params.text_size = 1 # 文本大小
pcv.params.text_thickness = 1 # 文本粗细
# 读取光谱图像ENVI 格式)
spectral_array = pcv.readimage(filename=args.image, mode='envi')
#过曝区域掩膜
bath_100 = spectral_array.array_data[:, :, 100]
bath_over = pcv.threshold.binary(gray_img=bath_100, threshold=11000, object_type='dark')
# mask已经是16位标签掩膜直接使用
# 确保掩膜是正确的数据类型
if mask.dtype != np.uint16:
mask = mask.astype(np.uint16)
# 获取标签数量和标签值
unique_labels = np.unique(mask)
# 移除背景标签通常是0
unique_labels = unique_labels[unique_labels != 0]
num = len(unique_labels)
print(f"Number of objects in mask: {num}")
# 转换过曝掩膜为二值格式0和1
bath_over_binary = (bath_over > 0).astype(np.uint16)
# 创建用于形状分析的标签掩膜(去除过曝区域的版本)
# labeled_mask = mask * bath_over_binary
labeled_mask = mask
# 获取剩余的有效标签
valid_labels = np.unique(labeled_mask)
valid_labels = valid_labels[valid_labels != 0]
num_valid = len(valid_labels)
print(f"Number of objects after removing overexposed regions: {num_valid}")
# 分析每个对象的形状和大小
# 使用二值图像用于可视化(将标签掩膜转换为显示格式)
binary_img = (mask > 0).astype(np.uint8) * 255
shape_img = size(img=binary_img, labeled_mask=labeled_mask, n_labels=num_valid)
# 分析光谱反射率
# 应用过曝掩膜
labeled_spectral_mask = mask * bath_over_binary
spectral_hist = pcv.analyze.spectral_reflectance(
hsi=spectral_array,
labeled_mask=labeled_spectral_mask,
n_labels=num_valid,
label=None
)
observations = pcv.outputs.observations
# 将结果转换为列表
combined_data = process_plantcv_outputs(observations)
return combined_data
# # 示例:批量处理指定路径下的光谱图像和掩膜文件
# bil_path = r'D:\WQ\test\Traindata-05'
# mask_path = r'D:\WQ\test\mask'
# outdir = r"D:\WQ\test" # 输出文件夹路径
#
# process_images(bil_path, mask_path, outdir)