文章目录
-
- [1. 任务目标](#1. 任务目标)
- [2. 数据特性分析](#2. 数据特性分析)
- [3. 整体算法流程](#3. 整体算法流程)
- [4. 算法流程详细介绍](#4. 算法流程详细介绍)
-
- [4.1 图像数据读取](#4.1 图像数据读取)
- [4.2 图像预处理](#4.2 图像预处理)
-
- [4.2.1 去噪](#4.2.1 去噪)
- [4.2.2 对比度增强](#4.2.2 对比度增强)
- [4.3 图像分割(OTSU 算法)](#4.3 图像分割(OTSU 算法))
-
- [4.3.1 原理简介](#4.3.1 原理简介)
- [4.3.2 代码实现与轮廓提取](#4.3.2 代码实现与轮廓提取)
- [5. 后处理](#5. 后处理)
-
- [5.1 处理红绿重叠区域](#5.1 处理红绿重叠区域)
- [5.2 计算面积比](#5.2 计算面积比)
- [6. 完整代码](#6. 完整代码)
- [7. 总结与展望](#7. 总结与展望)
1. 任务目标
给定一张细胞图像,其背景为黑色,并包含红色与绿色两种目标区域,如下图所示(示意图)。核心目标是计算图像中红色区域与绿色区域的面积之比 。

任务分析:本质上,这是一个图像分割任务。需要通过图像处理算法,分别得到红色区域和绿色区域的二值掩码(Mask)。然后,对掩码中的前景像素(通常值为 255)进行计数,该数值即代表了对应区域的像素面积。最后,计算两个计数值的比值,即可得到所需的面积比。
2. 数据特性分析
- RGB 三通道图像:原始图为彩色图。分割时需要分别处理红色和绿色通道,或进行颜色空间转换。
- 固定分辨率 :为保证算法处理的一致性,将所有输入图像缩放至
1024x1024的标准尺寸。 - 亮度差异:观察发现,绿色区域通常较亮,而红色区域偏暗。这表明在对两者进行分割时,可能需要采用不同的预处理参数或策略。
- 区域重叠 :红色与绿色区域存在部分重叠。这带来了一个关键问题:重叠部分的像素应归属于红色区域还是绿色区域? 必须在后处理中明确规则。
- 小区域与噪声:图像中可能存在微小的红色或绿色斑点,它们可能是噪声,也可能是真实的微小细胞结构。是否保留它们会影响面积计算的精度,需要通过滤波等手段进行权衡处理。
3. 整体算法流程
整个处理流程可以系统性地划分为三个核心阶段,如下图所示:

三个核心阶段包括预处理、图像分割、后处理,整个流程的常用算法总结如下:

4. 算法流程详细介绍
4.1 图像数据读取
使用 cv2.imdecode 来避免中文路径可能带来的问题,并直接分离出红、绿通道数据。
import cv2
import numpy as np
from pathlib import Path
import matplotlib.pyplot as plt
def image_read(source_image_path: str | Path) -> np.ndarray:
"""安全读取中文路径的图像,支持单通道、三通道(BGR)、四通道(BGRA)"""
source_path = Path(source_image_path)
raw_data = np.fromfile(source_path, dtype=np.uint8)
# 使用 imdecode 读取,避免中文路径问题
return cv2.imdecode(raw_data, cv2.IMREAD_UNCHANGED)
def main():
# 1. 数据读取
path = Path(r"your_image_path.tif") # 请替换为你的图像路径
image_raw = image_read(path) # 读取为BGR格式
image = cv2.resize(image_raw, (1024, 1024)) # 尺寸标准化
# 分离通道 (OpenCV 默认顺序为 BGR)
image_blue = image[:, :, 0]
image_green = image[:, :, 1] # 绿色通道
image_red = image[:, :, 2] # 红色通道
代码说明:
image_read函数通过先读取二进制数据再解码的方式,可以解决中文文件路径,cv2.imread当路径中有中文字符时会报错。cv2.resize确保输入后续算法的图像尺寸统一。- 由于 OpenCV 以 BGR 顺序加载图像,红色和绿色通道实际是
image[:, :, 2]和image[:, :, 1]。
读取后的示意图如下:

4.2 图像预处理
预处理旨在提升图像质量,为分割步骤提供更清晰的输入。我们对红、绿通道并行处理。
4.2.1 去噪
先使用中值滤波 去除椒盐噪声,再利用高斯滤波平滑随机噪声。
-
中值滤波:取滑动窗口内所有像素灰度值的中值,能有效去除孤立的亮点或暗点。
-
高斯滤波:对窗口内像素进行加权平均,权重服从二维高斯分布,中心像素权重最高。能平滑图像并保留边缘。
2.1 中值滤波 (去除椒盐噪声)
kernel_median = 5 image_green = cv2.medianBlur(image_green, kernel_median) image_red = cv2.medianBlur(image_red, kernel_median) # 2.2 高斯滤波 (平滑随机噪声) kernel_gaussian = (9, 9) sigma = 2 image_green_filtered = cv2.GaussianBlur(image_green, kernel_gaussian, sigma) image_red_filtered = cv2.GaussianBlur(image_red, kernel_gaussian, sigma)
4.2.2 对比度增强
为了将前景(细胞区域)与背景更明显地分离开,我们增强图像对比度。
-
绿色通道:使用**直方图均衡化(CLAHE)**。它对图像局部区域进行均衡化,能有效增强细节的同时避免过度放大噪声。
-
红色通道 :红色区域通常较暗,我们使用自适应直方图均衡化(CLAHE) 的改进版本,通过限制对比度来防止噪声被过度增强。
2.3 对比度增强
# 绿色通道使用自适应直方图均衡化 (CLAHE) clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8)) image_green_enhanced = clahe.apply(image_green_filtered) # 红色通道使用限制对比度的自适应直方图均衡化 clahe_red = cv2.createCLAHE(clipLimit=1.5, tileGridSize=(8,8)) # 更低的clipLimit防止过增强 image_red_enhanced = clahe_red.apply(image_red_filtered)

预处理后,红绿通道的对比度得到显著提升,前景与背景的区分更为明显。
4.3 图像分割(OTSU 算法)
分割是核心步骤,我们将使用经典的 OTSU(大津)算法自动寻找最佳阈值。
4.3.1 原理简介
OTSU 算法是一种自动确定图像二值化阈值的算法。其原理是寻找一个阈值 T,使得该阈值将图像像素分为前景和背景两类后,两类之间的类间方差最大。类间方差越大,说明两类差别越大,分割效果越好。
4.3.2 代码实现与轮廓提取
# 3.1 OTSU 自动阈值分割
# 绿色通道分割
thresh_green, binary_green = cv2.threshold(image_green_enhanced, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# 红色通道分割
thresh_red, binary_red = cv2.threshold(image_red_enhanced, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# 3.2 提取并绘制轮廓
# 寻找轮廓
contours_green, _ = cv2.findContours(binary_green, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours_red, _ = cv2.findContours(binary_red, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 在原图上绘制轮廓以便可视化评估
image_with_contours = image.copy()
cv2.drawContours(image_with_contours, contours_green, -1, (0, 255, 0), 1) # 绿色轮廓
cv2.drawContours(image_with_contours, contours_red, -1, (0, 0, 255), 1) # 红色轮廓

左图展示了 OTSU 算法找到的轮廓叠加在原图上的效果,中、右图分别为红绿区域的二值掩码。可以看到,大部分区域被成功分割,但重叠部分被同时标记为红和绿。
5. 后处理
5.1 处理红绿重叠区域
直接分割得到的两个掩码存在重叠部分。我们需要制定规则来确定重叠像素的归属。这里采用一种直观的规则:比较重叠区域在对应原始通道中的灰度强度,谁"更强"就属于谁。
#后处理
#1、处理重叠区域:强度优先规则
# 计算掩码的交集区域
intersection = binary_image_red & binary_image_green
# 创建处理后的掩码副本
processed_red_mask = binary_image_red.copy()
processed_green_mask = binary_image_green.copy()
# 向量化比较
# 在交集区域,红色强度 < 绿色强度的位置
red_weaker = intersection & (image_red < image_green)
# 在交集区域,绿色强度 < 红色强度的位置
green_weaker = intersection & (image_green < image_red)
# 将较弱方的掩码置0
processed_red_mask[red_weaker != 0 ] = 0
processed_green_mask[green_weaker != 0 ] = 0

处理后,重叠区域的像素被唯一地分配给了红色或绿色掩码,两者的轮廓线是互斥的,不再有交集
5.2 计算面积比
面积计算简化为统计最终掩码中前景像素(值为 255)的数量。
#2、计算红色区域和绿色区域的面积比
number_green = np.sum(processed_green_mask != 0)
number_red = np.sum(processed_red_mask != 0)
area_ratio = number_red / number_green
print(area_ratio)
6. 完整代码
以下是将上述所有步骤整合后的完整可执行代码。
from pathlib import Path
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 设置中文显示
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
def image_read(source_image_path: str | Path) -> np.ndarray:
"""安全读取中文路径的图像,支持单通道、三通道(BGR)、四通道(BGRA)"""
# 二进制读取 + OpenCV解码
source_path = Path(source_image_path)
raw_data = np.fromfile(source_path, dtype=np.uint8)
return cv2.imdecode(raw_data, cv2.IMREAD_UNCHANGED) # 保留原始通道信息
def main():
#---------------------------数据读取
path = Path(r"H:\datasets_new\datasets_no_rules\images\01623_1.tif")
image_raw = image_read(path) #BGR ,原始图像
image = cv2.resize(image_raw, (1024, 1024))#标准化尺寸
image_blue = image[:, :, 0] #蓝色通道数据
image_green = image[:, :, 1]#绿色通道数据
image_red = image[:, :, 2] #红色通道数据
plt.figure(1, figsize=(20, 10), dpi=100)
plt.subplot(131)
plt.title("Original Image", fontsize=30)
plt.imshow( cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) #plot显示的是RGB格式的数据,需要转换一下
plt.axis('off')
plt.subplot(132)
plt.title("Green Image", fontsize=30)
plt.imshow(image_green,cmap = 'gray')
plt.axis('off')
plt.subplot(133)
plt.imshow(image_red,cmap = 'gray')
plt.title("Red Image", fontsize=30)
plt.axis('off')
#plt.subplots_adjust(left=0, right=1, top=1, bottom=0)
plt.tight_layout()
plt.show()
#---------------------------图像预处理
#1、图像滤波、平滑
#中值滤波
image_green = cv2.medianBlur(image_green, 5)
image_red = cv2.medianBlur(image_red, 5)
# -----------------------------------#高斯滤波
kernel_size = (9, 9) # 滤波器尺寸
sigma = 2 # 标准差
image_green_filtered = cv2.GaussianBlur(image_green, kernel_size, sigma)
image_red_filtered = cv2.GaussianBlur(image_red, kernel_size, sigma)
#2、对比度增强
image_green_stretch = cv2.equalizeHist(image_green_filtered )#直方图均衡化
clahe = cv2.createCLAHE(clipLimit=3, tileGridSize=(8, 8)) #自适应直方图均衡化
image_red_stretch = clahe.apply(image_red_filtered)
plt.figure(2, figsize=(20, 10), dpi=100)
plt.subplot(121)
plt.imshow(image_red,cmap='gray')
plt.title("增强前", fontsize=30)
plt.axis('off')
plt.subplot(122)
plt.imshow(image_red_stretch, cmap='gray')
plt.title("增强后", fontsize=30)
plt.axis('off')
#plt.tight_layout()
#图像分割,OTSU
threshold_green, binary_image_green = cv2.threshold(image_green_stretch, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
threshold_red, binary_image_red = cv2.threshold(image_red_stretch, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
#画轮廓,展示用,方便评估是否分割到位
# 提取二值化图像的轮廓
contours_green, hierarchy_green = cv2.findContours(
binary_image_green,
cv2.RETR_TREE, # 检测所有轮廓并建立完整的层级关系(推荐)
cv2.CHAIN_APPROX_SIMPLE # 简化轮廓点
)
contours_red, hierarchy_red = cv2.findContours(
binary_image_red,
cv2.RETR_TREE, # 检测所有轮廓并建立完整的层级关系(推荐)
cv2.CHAIN_APPROX_SIMPLE # 简化轮廓点
)
image_result =image.copy()
cv2.drawContours(image_result, contours_green, -1, (255, 255, 255), 1) #
cv2.drawContours(image_result, contours_red, -1, (255, 255, 0), 1) #
plt.figure(4, figsize=(20, 10), dpi=100)
plt.subplot(131)
plt.imshow(cv2.cvtColor(image_result, cv2.COLOR_BGR2RGB))
plt.title("原图+轮廓线", fontsize=30)
plt.axis('off')
plt.subplot(132)
plt.imshow(binary_image_red, cmap='gray')
plt.title("红色二值图", fontsize=30)
plt.axis('off')
plt.subplot(133)
plt.imshow(binary_image_green, cmap='gray')
plt.title("绿色二值图", fontsize=30)
plt.axis('off')
plt.tight_layout()
#后处理
#1、处理重叠部分,谁大就是谁
# 计算掩码的交集区域
intersection = binary_image_red & binary_image_green
# 创建处理后的掩码副本
processed_red_mask = binary_image_red.copy()
processed_green_mask = binary_image_green.copy()
# 向量化比较
# 在交集区域,红色强度 < 绿色强度的位置
red_weaker = intersection & (image_red < image_green)
# 在交集区域,绿色强度 < 红色强度的位置
green_weaker = intersection & (image_green < image_red)
# 将较弱方的掩码置0
processed_red_mask[red_weaker != 0 ] = 0
processed_green_mask[green_weaker != 0 ] = 0
# 提取二值化图像的轮廓
contours_green_target, hierarchy_green_target = cv2.findContours(
processed_green_mask,
cv2.RETR_TREE, # 检测所有轮廓并建立完整的层级关系(推荐)
cv2.CHAIN_APPROX_SIMPLE # 简化轮廓点
)
contours_red_target, hierarchy_red_target = cv2.findContours(
processed_red_mask,
cv2.RETR_TREE, # 检测所有轮廓并建立完整的层级关系(推荐)
cv2.CHAIN_APPROX_SIMPLE # 简化轮廓点
)
image_result_target = image.copy()
cv2.drawContours(image_result_target, contours_green_target, -1, (255, 255, 255), 1) #
cv2.drawContours(image_result_target, contours_red_target, -1, (255, 255, 0), 1) #
plt.figure(5, figsize=(20, 10), dpi=100)
plt.subplot(121)
plt.imshow(cv2.cvtColor(image_result, cv2.COLOR_BGR2RGB))
plt.title("处理重叠区域前的分割效果", fontsize=30)
plt.axis('off')
plt.subplot(122)
plt.imshow(cv2.cvtColor(image_result_target, cv2.COLOR_BGR2RGB))
plt.title("处理重叠区域后的分割效果", fontsize=30)
plt.axis('off')
plt.show()
#2、计算红色区域和绿色区域的面积比
number_green = np.sum(processed_green_mask != 0)
number_red = np.sum(processed_red_mask != 0)
area_ratio = number_red / number_green
print(area_ratio)
if __name__ == '__main__':
main()
7. 总结与展望
本文详细阐述了一个完整的细胞图像红绿区域分割与面积比计算流程。通过预处理(去噪、增强) 提升图像质量,利用 OTSU 算法 进行自动阈值分割,并制定了明确的规则处理重叠区域,最终通过像素计数得到面积比。
可能的改进方向:
- 分割算法:对于更复杂的图像,OTSU 可能不够鲁棒。可以尝试基于边缘的分割(如 Canny)、区域生长、分水岭算法,或采用深度学习分割模型(如 U-Net)。
- 重叠处理规则:本文采用"强度优先"规则。在某些应用中,可能需要根据先验知识采用其他规则,如"红色优先"或"绿色优先"。
- 小区域过滤:在实际应用中,可能需要在后处理中加入面积滤波,去除像素数过少的噪声点。
- 评估指标:若有专家标注的真值(Ground Truth),可使用交并比(IoU)、Dice 系数等指标定量评估分割精度。
此流程只是一个基础而完整的解决方案,可根据具体图像的实际情况调整参数和步骤,以满足不同的精度和鲁棒性要求。
> 推荐一个很通俗易懂的人工智能教程: 人工智能教程