def ensure_dir(path):"""若目錄不存在則創建"""if not os.path.exists(path): #判斷路徑是否存在os.makedirs(path) #創建路徑def read_and_resize(img_path, size):"""讀取并縮放圖像到指定尺寸,返回 numpy 數組"""img = Image.open(img_path).convert("RGB") #Image.open 讀取圖片文件 .convert將數據改為所需的格式img = img.resize(size, Image.BILINEAR) #.resize(size, Image.BILINEAR) 縮放圖片的大小 保證格式相同return np.array(img) #np.array 將數據的格式改為數組
#插值(interpolation):縮放圖像時需要“估算”新像素值的方法。常見方式有:最近鄰 (NEAREST),雙線性插值 (BILINEAR),雙三次插值 (BICUBIC)def merge_instance_masks(mask_folder, size):"""將一個病例下的所有 instance-mask 合并成一張二值 mask"""final = np.zeros(size[::-1], dtype=np.uint8) # (H, W) 創建空的掩膜for fname in os.listdir(mask_folder): #os.listdir(...) 遍歷文件 讀取所有的mask圖片if not fname.lower().endswith((".png", ".jpg", ".tif")): #跳過非圖片文件 .endswith判斷文件格式continuem = Image.open(os.path.join(mask_folder, fname)).convert("L") #.convert 將數據改為單通道數據 黑白圖片m = m.resize(size, Image.NEAREST) # 保持 label 不插值m = np.array(m)m = (m > 0).astype(np.uint8) # 轉成 0/1 二值化 (m > 0)布爾判斷 生成一個和m形同形狀的數組 并比較比0大的就是1 其他的就是0final = np.maximum(final, m) #將符合條件的masks合并 # 像素級取最大值return final * 255 #最后輸出的值為255\0 方便保存 # 保存成 0/255def process_one_case(case_dir, out_img_dir, out_mask_dir, size):"""處理單個病例,輸出 image.png & mask.png"""# 1. 讀取原圖(每個 images 文件夾只含一張)img_folder = os.path.join(case_dir, "images")#拼接路徑,得到某病例的“images”文件夾路徑img_name = os.listdir(img_folder)[0]img = read_and_resize(os.path.join(img_folder, img_name), size)# 2. 合并 maskmask_folder = os.path.join(case_dir, "masks")mask = merge_instance_masks(mask_folder, size)# 3. 生成保存路徑case_id = os.path.basename(case_dir)img_out_path = os.path.join(out_img_dir, f"{case_id}.png")mask_out_path = os.path.join(out_mask_dir, f"{case_id}.png")# 4. 保存Image.fromarray(img).save(img_out_path)Image.fromarray(mask).save(mask_out_path)def main():ensure_dir(OUT_IMG_DIR)ensure_dir(OUT_MASK_DIR)case_dirs = [os.path.join(SRC_ROOT, d) for d in os.listdir(SRC_ROOT)if os.path.isdir(os.path.join(SRC_ROOT, d))]for c in tqdm(case_dirs, desc="Processing cases"):process_one_case(c, OUT_IMG_DIR, OUT_MASK_DIR, TARGET_SIZE)print(f"? 處理完成!{len(case_dirs)} 張圖像已保存到 {OUT_IMG_DIR} / {OUT_MASK_DIR}")if __name__ == "__main__":main()
這是比較完整的掩膜合并代碼
掩膜(mask), 通俗易懂來說就是對圖片中重要內容做的標簽,一般的掩膜都是二值型的,背景的像素是0,而掩膜的像素是255。
為什么要進行掩膜合并:模型在處理文件時,一般是一張圖片只能對應一個mask,但是有些圖片中的特征比較多,需要多個mask標記,這些mask又是單獨的文件,所以要將他們合并,方便后面模型處理
掩膜合并的過程簡單來說就是:先確認文件的路徑存在,再將圖片提取出來,將圖片的格式通過插值的方法統一格式像素大小方便后面進行處理。提取出mask,進行與圖片相同的處理步驟后,通過二值化的方法提取出mask,最后在進行合并。
簡潔的二值化掩膜合并代碼
def merge_instance_masks(mask_folder, size):final_mask = np.zeros(size[::-1], dtype=np.uint8)for fname in os.listdir(mask_folder):if not fname.endswith('.png'):continuemask = Image.open(os.path.join(mask_folder, fname)).convert('L')mask = mask.resize(size, Image.NEAREST)mask = np.array(mask)mask = (mask > 0).astype(np.uint8)final_mask = np.maximum(final_mask, mask)return final_mask * 255
?
掩膜合并寫代碼的思路框架
-
明確掩膜格式和組織結構
-
是多張單通道小掩膜文件,還是一張帶類別標簽的整圖?
-
是灰度圖、RGB圖還是三維數組(如
.npy
,.nii
)? -
是實例掩膜(每個對象單獨一個文件)還是語義掩膜(每個像素分類標簽)?
-
-
確定目標掩膜格式
-
是要把多個實例掩膜合成一張二值掩膜?
-
還是轉換成語義分割標簽圖?
-
還是保留多通道結構?(例如多類別多通道)
-
-
選擇對應處理方法
-
對文件夾內的多個二值掩膜做像素級“或”運算(np.maximum)
-
對語義標簽圖做像素級直接使用
-
對多通道數組用數組操作疊加/轉換
-
-
寫對應的代碼模塊,保證流程:讀掩膜→處理→保存
其他mask類型的掩膜合并代碼?
語義分割標簽圖(單文件,類別標簽編碼)?
def process_semantic_mask(mask_path, size):mask = Image.open(mask_path)mask = mask.resize(size, Image.NEAREST)mask = np.array(mask)# 假設原始mask是類別編碼,如0-background,1-腫瘤,2-器官# 這里直接返回,不合并return mask
多類別掩膜存為多通道 npy 文件,轉換為單通道標簽圖?
def multi_channel_npy_to_label(mask_npy_path, size):mask_3d = np.load(mask_npy_path) # shape: (C, H, W)mask_3d_resized = np.zeros((mask_3d.shape[0], size[1], size[0]), dtype=np.uint8)for i in range(mask_3d.shape[0]):channel = cv2.resize(mask_3d[i], size, interpolation=cv2.INTER_NEAREST)mask_3d_resized[i] = channel# 逐像素最大類別索引作為標簽label_mask = np.argmax(mask_3d_resized, axis=0).astype(np.uint8)return label_mask
掩膜合并中常用的函數方法?
-
Image.open()
讀取圖片 -
img.resize()
縮放圖片 -
np.array()
轉成數組 -
np.maximum()
進行掩膜合并 -
Image.fromarray()
+.save()
保存圖片 -
os.listdir()
遍歷目錄 -
os.path.join()
拼接路徑