391 lines
16 KiB
Python
391 lines
16 KiB
Python
# 去除过曝光像素
|
||
# 导入必要的库
|
||
|
||
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) |