文章目录
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/d24045cbece9998a69cf485140cfa3d6.png)
使用Numba索引图像中每个像素值的最小最大坐标(CPU + 多线程)
输入图像 | 时耗 | |
---|---|---|
(1)多线程(每一个像素值遍历一次图像) | ZYX=(2182, 431, 548) | 80.41秒 |
(2)Numba-CPU(每一个像素值遍历一次图像) | ZYX=(2182, 431, 548) | 81.68秒 |
(3)Numba-CPU(只遍历一次图像并匹配像素值) | ZYX=(2182, 431, 548) | 1.56秒 |
一、测试样本 —— 创建样本mask,具有 N 个唯一像素值,每个值有 M 个坐标。
import numpy as np
def create_mask(shape, num_values):
"""创建一个 uint16 类型的 mask,并为每个像素值设置多个坐标点。
:param shape: mask 数组的形状 (z, y, x)
:param num_values: 不同的像素值数量
:return: mask 数组
"""
# 初始化 mask 数组
mask = np.zeros(shape, dtype=np.uint16)
# 遍历每个像素值并设置多个坐标点
for value in range(1, num_values + 1):
# 生成随机坐标
num_coords = int(shape[0] * shape[1] * shape[2] / num_values * 0.1) # 每个像素值设置N个坐标点
for _ in range(num_coords):
z = np.random.randint(0, shape[0])
y = np.random.randint(0, shape[1])
x = np.random.randint(0, shape[2])
mask[z, y, x] = value
return mask
if __name__ == '__main__':
shape = (1024, 1024, 1024) # 设置 mask 的大小
num_values = 619 # 不同的像素值数量
mask = create_mask(shape, num_values)
print(f"mask 数组的形状: {mask.shape}")
print(f"mask 数组的唯一像素值: {np.unique(mask)}")
二、加速方法
(1)多线程加速 —— 每次提取一个像素值,然后遍历图像,匹配并判断其与初始化坐标的关系。
import numpy as np
import tifffile
import concurrent.futures
import pandas as pd
import time
def calculate_coordinates(target_value, array):
"""在满足条件的N个坐标中,提取最小x/y/z值。
备注1:x/y/z可以不在同一个坐标中。
备注2:由于需要使用np.argwhere进行坐标索引,故有时耗问题。
"""
start_time = time.time()
mask = (array == target_value) # 创建布尔掩码
coordinates = np.argwhere(mask) # 查找True值的坐标
min_coords = np.min(coordinates, axis=0) # 获取所有列中每一列的最小值 # 坐标: [z, y, x]
max_coords = np.max(coordinates, axis=0) # 获取所有列中每一列的最大值 # 坐标: [z, y, x]
value = [target_value, *min_coords[::-1], *max_coords[::-1]] # 坐标: [gray, min_x, min_y, min_z, max_x, max_y, max_z]
print(f"gray={target_value}", f"最小坐标={min_coords[::-1]}", f"最大坐标:{max_coords[::-1]}", time.time()-start_time)
return value
def main(image_path, output_path):
start_time = time.time()
image = tifffile.imread(image_path) # 读取图像
unique_values = np.unique(image) # 获取图像中的像素唯一值
sorted_unique_values = np.sort(unique_values) # 排序
box3DMap = []
flag = 1 # 是否加速
if flag == 1:
with concurrent.futures.ThreadPoolExecutor(max_workers=os.cpu_count()) as executor:
futures = [executor.submit(calculate_coordinates, value, image) for value in sorted_unique_values[1:]]
for future in concurrent.futures.as_completed(futures):
result = future.result()
box3DMap.append(result)
else: # 不加速版本
for ii in range(1, len(sorted_unique_values)):
start_time = time.time()
target_value = ii # 指定要查找的灰度值
coordinates = np.argwhere(image == target_value) # 提取满足条件的坐标
min_coords = np.min(coordinates, axis=0) # 获取所有列中每一列的最小值 # 坐标: [z, y, x]
max_coords = np.max(coordinates, axis=0) # 获取所有列中每一列的最大值 # 坐标: [z, y, x]
temp = [ii, *min_coords, *max_coords]
print(f"gray={ii}", f"最小坐标={min_coords}", f"最大坐标:{max_coords}")
print(f"共用时:{time.time() - start_time}")
box3DMap.append(temp)
df = pd.DataFrame(box3DMap, columns=["Gray", "Min_X", "Min_Y", "Min_Z", "Max_X", "Max_Y", "Max_Z"])
df.to_excel(output_path, index=False, engine='openpyxl')
print(f'Data saved to={output_path}')
print(f"计算共用时={time.time()-start_time} 秒")
if __name__ == '__main__':
image_path = r"F:\annotation.tif"
output_path = r"F:\output.xlsx"
main(image_path, output_path)
(2)Numba-CPU加速 —— 每次提取一个像素值,然后遍历图像,匹配并判断其与初始化坐标的关系。
import numpy as np
import tifffile
import time
from numba import njit
@njit
def calculate_coordinates(num_values, array):
"""计算每个唯一值的最小和最大坐标。
num_values: 排序后的唯一像素值数组
array: 输入图像
返回值:每个像素值的最小和最大坐标
"""
shape = array.shape
num_values_size = num_values.size
box3DMap = np.empty((num_values_size, 7), dtype=np.int64) # 初始化结果数组
for idx in range(num_values_size):
target_value = num_values[idx]
min_coords = np.array([shape[0], shape[1], shape[2]], dtype=np.int64) # 初始化为图像的最大坐标值
max_coords = np.array([-1, -1, -1], dtype=np.int64) # 初始化为图像的最小坐标值
for i in range(shape[0]):
for j in range(shape[1]):
for k in range(shape[2]):
if array[i, j, k] == target_value:
min_coords[0] = min(min_coords[0], i)
min_coords[1] = min(min_coords[1], j)
min_coords[2] = min(min_coords[2], k)
max_coords[0] = max(max_coords[0], i)
max_coords[1] = max(max_coords[1], j)
max_coords[2] = max(max_coords[2], k)
coors = [target_value, min_coords[2], min_coords[1], min_coords[0], max_coords[2], max_coords[1], max_coords[0]]
print(coors)
box3DMap[idx] = coors
return box3DMap
def main(image_path, output_path):
image = tifffile.imread(image_path) # 读取图像
unique_values = np.unique(image) # 获取图像中的像素唯一值
sorted_unique_values = np.sort(unique_values[unique_values != 0]) # 排序并排除0
print(f"唯一像素值数量(排除0):{len(sorted_unique_values)}")
# Numba 加速
start_time = time.time()
box3DMap = calculate_coordinates(sorted_unique_values, image)
print(f"计算共用时:{time.time() - start_time}")
# 保存到EXCEL中
import pandas as pd
df = pd.DataFrame(box3DMap, columns=["Gray", "Min_X", "Min_Y", "Min_Z", "Max_X", "Max_Y", "Max_Z"])
df.to_excel(output_path, index=False, engine='openpyxl')
print(f'Data saved to={output_path}')
if __name__ == '__main__':
image_path = r'F:\annotation.tif'
output_path = r'F:\output.xls'
main(image_path, output_path)
(3)Numba-CPU加速 —— 只遍历一次图像,匹配并判断其与初始化坐标的关系。
import numpy as np
import tifffile
import time
from numba import njit
@njit
def calculate_coordinates(num_values, array):
"""计算每个唯一值的最小和最大坐标。
num_values: 排序后的唯一像素值数组(排除0)
array: 输入图像
返回值:每个像素值的最小和最大坐标
"""
shape = array.shape
num_values_size = num_values.size
box3DMap = np.empty((num_values_size, 7), dtype=np.int64)
# 随机初始化:每个像素值的最小和最大坐标
min_coords = np.empty((num_values_size, 3), dtype=np.int64)
max_coords = np.empty((num_values_size, 3), dtype=np.int64)
# 用最大坐标初始化 min_coords,用最小坐标初始化 max_coords
for i in range(num_values_size):
min_coords[i, 0] = shape[0]
min_coords[i, 1] = shape[1]
min_coords[i, 2] = shape[2]
max_coords[i, 0] = -1
max_coords[i, 1] = -1
max_coords[i, 2] = -1
# 为每个唯一值设置索引
value_to_index = np.zeros(100000, dtype=np.int64) # 假设最大像素值为255
for i in range(num_values_size):
value_to_index[num_values[i]] = i
print("开始遍历图像,计算坐标...")
# 遍历图像的所有坐标点
for i in range(shape[0]):
for j in range(shape[1]):
for k in range(shape[2]):
pixel_value = array[i, j, k]
if pixel_value == 0:
continue
idx = value_to_index[pixel_value]
if idx >= 0:
min_coords[idx, 0] = min(min_coords[idx, 0], i)
min_coords[idx, 1] = min(min_coords[idx, 1], j)
min_coords[idx, 2] = min(min_coords[idx, 2], k)
max_coords[idx, 0] = max(max_coords[idx, 0], i)
max_coords[idx, 1] = max(max_coords[idx, 1], j)
max_coords[idx, 2] = max(max_coords[idx, 2], k)
# 组合最小和最大坐标
for idx in range(num_values_size):
coors = [num_values[idx],
min_coords[idx, 2], min_coords[idx, 1], min_coords[idx, 0],
max_coords[idx, 2], max_coords[idx, 1], max_coords[idx, 0]]
box3DMap[idx] = coors
return box3DMap
def main(image_path, output_path):
print(f"开始读取图像:{image_path}")
image = tifffile.imread(image_path) # 读取图像
unique_values = np.unique(image) # 获取图像中的像素唯一值
sorted_unique_values = np.sort(unique_values[unique_values != 0]) # 排序并排除0
print(f"图像读取完成。唯一像素值数量(排除0):{len(sorted_unique_values)}")
# Numba加速
start_time = time.time()
print(f"开始计算坐标...")
box3DMap = calculate_coordinates(sorted_unique_values, image)
print(f"坐标计算完成,结果条目数:{box3DMap.shape[0]}")
print(f"计算共用时:{time.time() - start_time}")
# 保存到EXCEL中
import pandas as pd
df = pd.DataFrame(box3DMap, columns=["Gray", "Min_X", "Min_Y", "Min_Z", "Max_X", "Max_Y", "Max_Z"])
df.to_excel(output_path, index=False, engine='openpyxl')
print(f"数据已保存到={output_path}")
if __name__ == '__main__':
# 示例路径,修改为实际文件路径
image_path = r'F:\annotation.tif'
output_path = r'F:\output.xls'
main(image_path, output_path)