摘要:本文深入讲解图像色彩空间的原理与应用,详细介绍RGB、HSV、LAB、YCrCb等常用色彩空间的特点和转换方法。文章通过大量综合性代码示例,演示如何利用不同色彩空间进行颜色检测、图像分割和色彩校正等操作,并介绍如何使用GPT-5.4辅助编写色彩处理代码。由于国内无法访问OpenAI官网,因此使用国内镜像站可以注册使用GPT-5.4最新模型。注册入口:AIGCBAR镜像站(https://chat.aigc.bar/list/#/register?inviter=0L54LA)。如果涉及到调用API,则加上API站注册入口API独立站(https://api.aigc.bar/register?aff=UP4F)。请广大读者遵守法律法规,切勿翻墙访问境外网站,使用国内合法镜像站即可满足学习需求。
6.1 色彩空间基础理论
6.1.1 色彩空间的定义与分类
色彩空间(Color Space)是用数学方法描述颜色的坐标系,它定义了颜色的表示方式和颜色之间的关系。不同的色彩空间适用于不同的应用场景,理解各种色彩空间的特点对于图像处理至关重要。色彩空间可以分为以下几类:加色模型(如RGB)、减色模型(如CMYK)、感知模型(如HSV、HSL)、设备无关模型(如LAB、XYZ)。
RGB色彩空间是最常用的加色模型,它通过红、绿、蓝三种基色的叠加来表示颜色。RGB色彩空间与显示设备的物理特性密切相关,不同的设备可能有不同的RGB色彩空间标准,如sRGB、Adobe RGB、ProPhoto RGB等。RGB色彩空间的优点是直观、易于理解和实现,缺点是与人类对颜色的感知不完全一致,且受亮度影响较大。
HSV色彩空间将颜色表示为色相(Hue)、饱和度(Saturation)和明度(Value)三个分量。这种表示方式更接近人类对颜色的感知,色相表示颜色的类型(如红色、蓝色),饱和度表示颜色的纯度,明度表示颜色的明暗程度。HSV色彩空间在颜色检测和分割任务中非常有用,因为可以通过设置色相范围来选择特定颜色,而不受亮度变化的影响。
LAB色彩空间是一种设备无关的色彩空间,由国际照明委员会(CIE)定义。L分量表示亮度,a分量表示从绿色到红色的变化,b分量表示从蓝色到黄色的变化。LAB色彩空间的特点是感知均匀性,即颜色数值的变化与人眼感知的颜色变化成正比。这种特性使得LAB色彩空间在颜色差异计算、颜色迁移等任务中非常有用。
6.1.2 色彩空间比较
以下表格对常用色彩空间进行了详细比较。
| 色彩空间 | 分量 | 设备相关性 | 感知均匀性 | 主要应用 |
|---|---|---|---|---|
| RGB | R, G, B | 设备相关 | 非均匀 | 图像显示、存储 |
| HSV | H, S, V | 设备相关 | 较均匀 | 颜色检测、分割 |
| HSL | H, S, L | 设备相关 | 较均匀 | 图形设计 |
| LAB | L, a, b | 设备无关 | 均匀 | 颜色分析、迁移 |
| YCrCb | Y, Cr, Cb | 设备相关 | 非均匀 | 视频压缩 |
| CMYK | C, M, Y, K | 设备相关 | 非均匀 | 印刷 |
| XYZ | X, Y, Z | 设备无关 | 非均匀 | 色彩科学 |
6.2 RGB色彩空间详解
6.2.1 RGB色彩空间原理
RGB色彩空间基于人眼的三种视锥细胞对红、绿、蓝光的敏感特性。在数字图像中,每个像素由R、G、B三个分量组成,每个分量通常使用8位表示,取值范围为0-255。RGB色彩空间可以表示约1677万种颜色,足以满足大多数应用需求。
RGB色彩空间的主要变体包括:sRGB(标准RGB,大多数显示器和相机使用的色彩空间)、Adobe RGB(色域更广,适合专业摄影)、ProPhoto RGB(色域最广,适合高端图像处理)。不同RGB色彩空间之间的转换需要考虑色彩配置文件。
以下代码展示了RGB色彩空间的各种操作。
python
"""
RGB色彩空间操作详解
演示RGB通道的各种处理方法
兼容Python 3.13
"""
import cv2
import numpy as np
from typing import Tuple, List, Optional, Dict, Any
from numpy.typing import NDArray
import matplotlib.pyplot as plt
class RGBColorSpace:
"""
RGB色彩空间操作类
"""
def __init__(self, image: NDArray):
"""
初始化RGB色彩空间处理器
参数:
image: 输入图像(BGR格式,OpenCV默认)
"""
self.image_bgr = image
self.image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) if len(image.shape) == 3 else cv2.cvtColor(image, cv2.COLOR_GRAY2RGB)
self.height, self.width = self.image_rgb.shape[:2]
def get_channels(self) -> Tuple[NDArray, NDArray, NDArray]:
"""
分离RGB通道
返回:
(R, G, B) 通道数组
"""
r = self.image_rgb[:, :, 0].copy()
g = self.image_rgb[:, :, 1].copy()
b = self.image_rgb[:, :, 2].copy()
return r, g, b
def merge_channels(self, r: NDArray, g: NDArray, b: NDArray) -> NDArray:
"""
合并RGB通道
参数:
r: 红色通道
g: 绿色通道
b: 蓝色通道
返回:
RGB图像
"""
return np.stack([r, g, b], axis=-1)
def get_channel_statistics(self) -> Dict[str, Dict[str, float]]:
"""
获取各通道统计信息
返回:
统计信息字典
"""
r, g, b = self.get_channels()
return {
'R': {
'mean': float(np.mean(r)),
'std': float(np.std(r)),
'min': float(np.min(r)),
'max': float(np.max(r)),
'median': float(np.median(r))
},
'G': {
'mean': float(np.mean(g)),
'std': float(np.std(g)),
'min': float(np.min(g)),
'max': float(np.max(g)),
'median': float(np.median(g))
},
'B': {
'mean': float(np.mean(b)),
'std': float(np.std(b)),
'min': float(np.min(b)),
'max': float(np.max(b)),
'median': float(np.median(b))
}
}
def adjust_channel(self,
channel: str,
factor: float,
offset: float = 0) -> NDArray:
"""
调整单个通道
参数:
channel: 通道名称 ('R', 'G', 'B')
factor: 乘法因子
offset: 偏移量
返回:
调整后的图像
"""
r, g, b = self.get_channels()
if channel == 'R':
r = np.clip(r * factor + offset, 0, 255).astype(np.uint8)
elif channel == 'G':
g = np.clip(g * factor + offset, 0, 255).astype(np.uint8)
elif channel == 'B':
b = np.clip(b * factor + offset, 0, 255).astype(np.uint8)
return self.merge_channels(r, g, b)
def swap_channels(self, order: Tuple[int, int, int] = (2, 1, 0)) -> NDArray:
"""
交换通道顺序
参数:
order: 新的通道顺序
返回:
交换后的图像
"""
return self.image_rgb[:, :, list(order)]
def extract_dominant_color(self, k: int = 3) -> List[Tuple[Tuple[int, int, int], float]]:
"""
提取主要颜色
参数:
k: 颜色数量
返回:
颜色列表 [(RGB颜色, 占比), ...]
"""
# 将图像重塑为像素列表
pixels = self.image_rgb.reshape(-1, 3)
# 使用K-means聚类
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.2)
_, labels, centers = cv2.kmeans(
pixels.astype(np.float32),
k,
None,
criteria,
10,
cv2.KMEANS_RANDOM_CENTERS
)
# 计算每个聚类的占比
unique, counts = np.unique(labels, return_counts=True)
total = len(labels)
colors = []
for i, count in zip(unique, counts):
color = tuple(int(c) for c in centers[i])
percentage = count / total
colors.append((color, percentage))
# 按占比排序
colors.sort(key=lambda x: x[1], reverse=True)
return colors
def color_histogram(self, bins: int = 256) -> Dict[str, NDArray]:
"""
计算颜色直方图
参数:
bins: 直方图箱数
返回:
直方图字典
"""
r, g, b = self.get_channels()
hist_r = cv2.calcHist([r], [0], None, [bins], [0, 256]).flatten()
hist_g = cv2.calcHist([g], [0], None, [bins], [0, 256]).flatten()
hist_b = cv2.calcHist([b], [0], None, [bins], [0, 256]).flatten()
return {
'R': hist_r,
'G': hist_g,
'B': hist_b
}
def compute_color_correlation(self) -> NDArray:
"""
计算颜色通道相关性
返回:
3x3相关矩阵
"""
r, g, b = self.get_channels()
# 展平为一维数组
r_flat = r.flatten().astype(np.float64)
g_flat = g.flatten().astype(np.float64)
b_flat = b.flatten().astype(np.float64)
# 计算相关矩阵
data = np.stack([r_flat, g_flat, b_flat])
correlation = np.corrcoef(data)
return correlation
def white_balance_gray_world(self) -> NDArray:
"""
灰度世界白平衡
返回:
白平衡后的图像
"""
r, g, b = self.get_channels()
# 计算各通道均值
r_mean = np.mean(r)
g_mean = np.mean(g)
b_mean = np.mean(b)
# 计算灰度均值
gray_mean = (r_mean + g_mean + b_mean) / 3
# 调整各通道
if r_mean > 0:
r = np.clip(r * gray_mean / r_mean, 0, 255).astype(np.uint8)
if g_mean > 0:
g = np.clip(g * gray_mean / g_mean, 0, 255).astype(np.uint8)
if b_mean > 0:
b = np.clip(b * gray_mean / b_mean, 0, 255).astype(np.uint8)
return self.merge_channels(r, g, b)
def white_balance_white_patch(self) -> NDArray:
"""
白点白平衡
返回:
白平衡后的图像
"""
r, g, b = self.get_channels()
# 找到各通道最大值
r_max = np.max(r)
g_max = np.max(g)
b_max = np.max(b)
# 调整各通道
if r_max > 0:
r = np.clip(r * 255.0 / r_max, 0, 255).astype(np.uint8)
if g_max > 0:
g = np.clip(g * 255.0 / g_max, 0, 255).astype(np.uint8)
if b_max > 0:
b = np.clip(b * 255.0 / b_max, 0, 255).astype(np.uint8)
return self.merge_channels(r, g, b)
def color_cast_detection(self) -> Dict[str, Any]:
"""
检测色偏
返回:
色偏检测结果
"""
stats = self.get_channel_statistics()
# 计算通道均值差异
means = [stats['R']['mean'], stats['G']['mean'], stats['B']['mean']]
mean_diff = max(means) - min(means)
# 判断是否存在色偏
has_cast = mean_diff > 20 # 阈值
# 确定色偏类型
if has_cast:
max_channel = ['R', 'G', 'B'][np.argmax(means)]
min_channel = ['R', 'G', 'B'][np.argmin(means)]
cast_type = f"偏{max_channel}色"
else:
max_channel = None
min_channel = None
cast_type = "无明显色偏"
return {
'has_color_cast': has_cast,
'cast_type': cast_type,
'mean_difference': mean_diff,
'max_channel': max_channel,
'min_channel': min_channel,
'channel_means': means
}
def apply_color_filter(self,
filter_type: str,
intensity: float = 1.0) -> NDArray:
"""
应用颜色滤镜
参数:
filter_type: 滤镜类型 ('warm', 'cool', 'vintage', 'sepia')
intensity: 滤镜强度
返回:
滤镜处理后的图像
"""
r, g, b = self.get_channels()
if filter_type == 'warm':
# 暖色调滤镜
r = np.clip(r * (1 + 0.1 * intensity), 0, 255)
b = np.clip(b * (1 - 0.1 * intensity), 0, 255)
elif filter_type == 'cool':
# 冷色调滤镜
r = np.clip(r * (1 - 0.1 * intensity), 0, 255)
b = np.clip(b * (1 + 0.1 * intensity), 0, 255)
elif filter_type == 'vintage':
# 复古滤镜
r = np.clip(r * 1.1, 0, 255)
g = np.clip(g * 0.9, 0, 255)
b = np.clip(b * 0.8, 0, 255)
elif filter_type == 'sepia':
# 棕褐色滤镜
r_new = 0.393 * r + 0.769 * g + 0.189 * b
g_new = 0.349 * r + 0.686 * g + 0.168 * b
b_new = 0.272 * r + 0.534 * g + 0.131 * b
r = np.clip(r_new, 0, 255)
g = np.clip(g_new, 0, 255)
b = np.clip(b_new, 0, 255)
result = self.merge_channels(
r.astype(np.uint8),
g.astype(np.uint8),
b.astype(np.uint8)
)
return result
def demonstrate_rgb_operations():
"""
演示RGB色彩空间操作
"""
# 创建测试图像
image = np.zeros((400, 600, 3), dtype=np.uint8)
# 填充渐变色
for y in range(400):
for x in range(600):
image[y, x] = [
int(x * 255 / 600), # B: 从左到右增加
int(y * 255 / 400), # G: 从上到下增加
128 # R: 固定值
]
rgb_space = RGBColorSpace(image)
print("RGB色彩空间操作演示")
print("=" * 50)
# 通道统计
stats = rgb_space.get_channel_statistics()
print(f"通道统计:")
for channel, values in stats.items():
print(f" {channel}: 均值={values['mean']:.2f}, 标准差={values['std']:.2f}")
# 通道相关性
correlation = rgb_space.compute_color_correlation()
print(f"\n通道相关矩阵:")
print(f" {correlation}")
# 主要颜色
colors = rgb_space.extract_dominant_color(5)
print(f"\n主要颜色:")
for color, percentage in colors:
print(f" RGB{color}: {percentage*100:.1f}%")
# 色偏检测
cast_result = rgb_space.color_cast_detection()
print(f"\n色偏检测:")
print(f" 是否存在色偏: {cast_result['has_color_cast']}")
print(f" 色偏类型: {cast_result['cast_type']}")
# 白平衡
balanced = rgb_space.white_balance_gray_world()
print(f"\n灰度世界白平衡完成")
# 颜色滤镜
warm = rgb_space.apply_color_filter('warm', 1.0)
cool = rgb_space.apply_color_filter('cool', 1.0)
sepia = rgb_space.apply_color_filter('sepia', 1.0)
print(f"\n颜色滤镜: 暖色调、冷色调、棕褐色")
return {
'original': rgb_space.image_rgb,
'balanced': balanced,
'warm': warm,
'cool': cool,
'sepia': sepia
}
if __name__ == "__main__":
results = demonstrate_rgb_operations()
print("\nRGB色彩空间操作演示完成")
6.3 HSV色彩空间详解
6.3.1 HSV色彩空间原理
HSV色彩空间将颜色表示为色相(Hue)、饱和度(Saturation)和明度(Value)三个分量。色相表示颜色的类型,取值范围为0-360度(在OpenCV中归一化为0-179);饱和度表示颜色的纯度,取值范围为0-100%(在OpenCV中为0-255);明度表示颜色的明暗程度,取值范围为0-100%(在OpenCV中为0-255)。
HSV色彩空间的主要优势在于它将颜色信息(色相)与亮度信息(明度)分离,这使得颜色检测和分割变得更加简单。例如,要检测红色物体,只需要设置色相在红色范围内即可,而不需要考虑光照变化的影响。
以下代码展示了HSV色彩空间的各种操作。
python
"""
HSV色彩空间操作详解
演示HSV通道的各种处理方法
兼容Python 3.13
"""
import cv2
import numpy as np
from typing import Tuple, List, Optional, Dict, Any
from numpy.typing import NDArray
class HSVColorSpace:
"""
HSV色彩空间操作类
"""
# 常见颜色的HSV范围
COLOR_RANGES = {
'red': [
(np.array([0, 100, 100]), np.array([10, 255, 255])),
(np.array([160, 100, 100]), np.array([180, 255, 255]))
],
'orange': [
(np.array([11, 100, 100]), np.array([25, 255, 255]))
],
'yellow': [
(np.array([26, 100, 100]), np.array([34, 255, 255]))
],
'green': [
(np.array([35, 100, 100]), np.array([77, 255, 255]))
],
'cyan': [
(np.array([78, 100, 100]), np.array([99, 255, 255]))
],
'blue': [
(np.array([100, 100, 100]), np.array([124, 255, 255]))
],
'purple': [
(np.array([125, 100, 100]), np.array([155, 255, 255]))
],
'white': [
(np.array([0, 0, 200]), np.array([180, 30, 255]))
],
'gray': [
(np.array([0, 0, 50]), np.array([180, 30, 200]))
],
'black': [
(np.array([0, 0, 0]), np.array([180, 255, 50]))
]
}
def __init__(self, image: NDArray):
"""
初始化HSV色彩空间处理器
参数:
image: 输入图像(BGR格式)
"""
self.image_bgr = image
self.image_hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
self.height, self.width = image.shape[:2]
def get_channels(self) -> Tuple[NDArray, NDArray, NDArray]:
"""
分离HSV通道
返回:
(H, S, V) 通道数组
"""
h = self.image_hsv[:, :, 0].copy()
s = self.image_hsv[:, :, 1].copy()
v = self.image_hsv[:, :, 2].copy()
return h, s, v
def merge_channels(self, h: NDArray, s: NDArray, v: NDArray) -> NDArray:
"""
合并HSV通道
参数:
h: 色相通道
s: 饱和度通道
v: 明度通道
返回:
HSV图像
"""
return np.stack([h, s, v], axis=-1)
def hsv_to_bgr(self, hsv_image: NDArray) -> NDArray:
"""
HSV转BGR
参数:
hsv_image: HSV图像
返回:
BGR图像
"""
return cv2.cvtColor(hsv_image, cv2.COLOR_HSV2BGR)
def get_channel_statistics(self) -> Dict[str, Dict[str, float]]:
"""
获取各通道统计信息
返回:
统计信息字典
"""
h, s, v = self.get_channels()
return {
'H': {
'mean': float(np.mean(h)),
'std': float(np.std(h)),
'min': float(np.min(h)),
'max': float(np.max(h))
},
'S': {
'mean': float(np.mean(s)),
'std': float(np.std(s)),
'min': float(np.min(s)),
'max': float(np.max(s))
},
'V': {
'mean': float(np.mean(v)),
'std': float(np.std(v)),
'min': float(np.min(v)),
'max': float(np.max(v))
}
}
def detect_color(self, color_name: str) -> NDArray:
"""
检测特定颜色
参数:
color_name: 颜色名称
返回:
颜色掩码
"""
if color_name not in self.COLOR_RANGES:
raise ValueError(f"未知颜色: {color_name}")
ranges = self.COLOR_RANGES[color_name]
mask = np.zeros((self.height, self.width), dtype=np.uint8)
for lower, upper in ranges:
mask = cv2.bitwise_or(mask, cv2.inRange(self.image_hsv, lower, upper))
return mask
def detect_color_range(self,
h_range: Tuple[int, int],
s_range: Tuple[int, int] = (50, 255),
v_range: Tuple[int, int] = (50, 255)) -> NDArray:
"""
检测自定义颜色范围
参数:
h_range: 色相范围 (min, max)
s_range: 饱和度范围 (min, max)
v_range: 明度范围 (min, max)
返回:
颜色掩码
"""
lower = np.array([h_range[0], s_range[0], v_range[0]])
upper = np.array([h_range[1], s_range[1], v_range[1]])
return cv2.inRange(self.image_hsv, lower, upper)
def extract_color_region(self, color_name: str) -> NDArray:
"""
提取特定颜色区域
参数:
color_name: 颜色名称
返回:
提取的图像区域
"""
mask = self.detect_color(color_name)
return cv2.bitwise_and(self.image_bgr, self.image_bgr, mask=mask)
def replace_color(self,
source_color: str,
target_color: Tuple[int, int, int]) -> NDArray:
"""
替换颜色
参数:
source_color: 源颜色名称
target_color: 目标颜色 (B, G, R)
返回:
替换后的图像
"""
mask = self.detect_color(source_color)
result = self.image_bgr.copy()
result[mask > 0] = target_color
return result
def adjust_hue(self, offset: int) -> NDArray:
"""
调整色相
参数:
offset: 色相偏移量
返回:
调整后的BGR图像
"""
h, s, v = self.get_channels()
# 调整色相(循环)
h = (h.astype(np.int32) + offset) % 180
h = h.astype(np.uint8)
hsv_new = self.merge_channels(h, s, v)
return self.hsv_to_bgr(hsv_new)
def adjust_saturation(self, factor: float) -> NDArray:
"""
调整饱和度
参数:
factor: 饱和度因子
返回:
调整后的BGR图像
"""
h, s, v = self.get_channels()
s = np.clip(s * factor, 0, 255).astype(np.uint8)
hsv_new = self.merge_channels(h, s, v)
return self.hsv_to_bgr(hsv_new)
def adjust_value(self, factor: float) -> NDArray:
"""
调整明度
参数:
factor: 明度因子
返回:
调整后的BGR图像
"""
h, s, v = self.get_channels()
v = np.clip(v * factor, 0, 255).astype(np.uint8)
hsv_new = self.merge_channels(h, s, v)
return self.hsv_to_bgr(hsv_new)
def create_color_histogram(self, bins: int = 180) -> Dict[str, NDArray]:
"""
创建HSV直方图
参数:
bins: 直方图箱数
返回:
直方图字典
"""
h, s, v = self.get_channels()
hist_h = cv2.calcHist([h], [0], None, [bins], [0, 180]).flatten()
hist_s = cv2.calcHist([s], [0], None, [256], [0, 256]).flatten()
hist_v = cv2.calcHist([v], [0], None, [256], [0, 256]).flatten()
return {
'H': hist_h,
'S': hist_s,
'V': hist_v
}
def get_dominant_hue(self) -> Tuple[int, float]:
"""
获取主要色相
返回:
(主要色相值, 占比)
"""
h, s, v = self.get_channels()
# 只考虑饱和度和明度较高的像素
mask = (s > 50) & (v > 50)
h_filtered = h[mask]
if len(h_filtered) == 0:
return 0, 0.0
# 计算色相直方图
hist, bins = np.histogram(h_filtered, bins=18, range=(0, 180))
# 找到最大值
max_idx = np.argmax(hist)
dominant_hue = (bins[max_idx] + bins[max_idx + 1]) / 2
percentage = hist[max_idx] / len(h_filtered)
return int(dominant_hue), percentage
def segment_by_hue(self, n_segments: int = 6) -> List[Tuple[int, NDArray]]:
"""
按色相分割图像
参数:
n_segments: 分割数量
返回:
[(色相范围中心, 掩码), ...]
"""
segment_size = 180 // n_segments
segments = []
for i in range(n_segments):
lower = i * segment_size
upper = (i + 1) * segment_size
mask = cv2.inRange(
self.image_hsv,
np.array([lower, 50, 50]),
np.array([upper, 255, 255])
)
center = (lower + upper) // 2
segments.append((center, mask))
return segments
def color_transfer(self, target_image: NDArray) -> NDArray:
"""
颜色迁移(将目标图像的颜色风格迁移到当前图像)
参数:
target_image: 目标图像(BGR格式)
返回:
迁移后的图像
"""
# 转换目标图像到HSV
target_hsv = cv2.cvtColor(target_image, cv2.COLOR_BGR2HSV)
# 获取各通道
h_src, s_src, v_src = self.get_channels()
h_tgt, s_tgt, v_tgt = target_hsv[:, :, 0], target_hsv[:, :, 1], target_hsv[:, :, 2]
# 计算目标图像的均值和标准差
s_mean_tgt = np.mean(s_tgt)
s_std_tgt = np.std(s_tgt)
v_mean_tgt = np.mean(v_tgt)
v_std_tgt = np.std(v_tgt)
# 计算源图像的均值和标准差
s_mean_src = np.mean(s_src)
s_std_src = np.std(s_src)
v_mean_src = np.mean(v_src)
v_std_src = np.std(v_src)
# 迁移饱和度
if s_std_src > 0:
s_new = (s_src - s_mean_src) / s_std_src * s_std_tgt + s_mean_tgt
s_new = np.clip(s_new, 0, 255).astype(np.uint8)
else:
s_new = s_src
# 迁移明度
if v_std_src > 0:
v_new = (v_src - v_mean_src) / v_std_src * v_std_tgt + v_mean_tgt
v_new = np.clip(v_new, 0, 255).astype(np.uint8)
else:
v_new = v_src
# 合并并转换回BGR
hsv_new = self.merge_channels(h_src, s_new, v_new)
return self.hsv_to_bgr(hsv_new)
def analyze_color_distribution(self) -> Dict[str, Any]:
"""
分析颜色分布
返回:
颜色分布分析结果
"""
h, s, v = self.get_channels()
# 分析饱和度分布
low_sat = np.sum(s < 50) / s.size
high_sat = np.sum(s > 200) / s.size
# 分析明度分布
low_val = np.sum(v < 50) / v.size
high_val = np.sum(v > 200) / v.size
# 分析色相分布
h_filtered = h[(s > 50) & (v > 50)]
if len(h_filtered) > 0:
h_std = float(np.std(h_filtered))
else:
h_std = 0.0
return {
'saturation': {
'low_percentage': low_sat,
'high_percentage': high_sat,
'mean': float(np.mean(s))
},
'value': {
'low_percentage': low_val,
'high_percentage': high_val,
'mean': float(np.mean(v))
},
'hue': {
'std': h_std,
'dominant': self.get_dominant_hue()
},
'colorfulness': float(np.std(s) * np.mean(s) / 255)
}
def demonstrate_hsv_operations():
"""
演示HSV色彩空间操作
"""
# 创建测试图像
image = np.zeros((400, 600, 3), dtype=np.uint8)
# 填充不同颜色区域
image[:200, :200] = [0, 0, 255] # 红色
image[:200, 200:400] = [0, 255, 0] # 绿色
image[:200, 400:] = [255, 0, 0] # 蓝色
image[200:, :200] = [0, 255, 255] # 黄色
image[200:, 200:400] = [255, 0, 255] # 紫色
image[200:, 400:] = [255, 255, 0] # 青色
hsv_space = HSVColorSpace(image)
print("HSV色彩空间操作演示")
print("=" * 50)
# 通道统计
stats = hsv_space.get_channel_statistics()
print(f"通道统计:")
for channel, values in stats.items():
print(f" {channel}: 均值={values['mean']:.2f}")
# 颜色检测
for color in ['red', 'green', 'blue']:
mask = hsv_space.detect_color(color)
print(f"\n检测{color}色: {np.sum(mask > 0)}个像素")
# 主要色相
dominant_hue, percentage = hsv_space.get_dominant_hue()
print(f"\n主要色相: {dominant_hue}度, 占比{percentage*100:.1f}%")
# 颜色分布分析
analysis = hsv_space.analyze_color_distribution()
print(f"\n颜色分布分析:")
print(f" 饱和度均值: {analysis['saturation']['mean']:.2f}")
print(f" 明度均值: {analysis['value']['mean']:.2f}")
print(f" 色彩丰富度: {analysis['colorfulness']:.2f}")
# 色相调整
shifted = hsv_space.adjust_hue(60)
print(f"\n色相偏移60度完成")
# 饱和度调整
saturated = hsv_space.adjust_saturation(1.5)
print(f"饱和度增加50%完成")
return {
'original': image,
'shifted': shifted,
'saturated': saturated
}
if __name__ == "__main__":
results = demonstrate_hsv_operations()
print("\nHSV色彩空间操作演示完成")
6.4 LAB色彩空间详解
6.4.1 LAB色彩空间原理
LAB色彩空间(也称为CIELAB)是由国际照明委员会(CIE)于1976年定义的设备无关色彩空间。它将颜色表示为三个分量:L表示亮度(Lightness),取值范围为0-100;a表示从绿色到红色的变化,取值范围为-128到+127;b表示从蓝色到黄色的变化,取值范围为-128到+127。
LAB色彩空间的主要特点是感知均匀性,即颜色数值的变化与人眼感知的颜色变化成正比。这种特性使得LAB色彩空间在颜色差异计算、颜色迁移、图像增强等任务中非常有用。在OpenCV中,LAB的L分量范围为0-255,a和b分量范围为0-255(需要减去128得到实际值)。
以下代码展示了LAB色彩空间的各种操作。
python
"""
LAB色彩空间操作详解
演示LAB通道的各种处理方法
兼容Python 3.13
"""
import cv2
import numpy as np
from typing import Tuple, List, Dict, Any
from numpy.typing import NDArray
class LABColorSpace:
"""
LAB色彩空间操作类
"""
def __init__(self, image: NDArray):
"""
初始化LAB色彩空间处理器
参数:
image: 输入图像(BGR格式)
"""
self.image_bgr = image
self.image_lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
self.height, self.width = image.shape[:2]
def get_channels(self) -> Tuple[NDArray, NDArray, NDArray]:
"""
分离LAB通道
返回:
(L, A, B) 通道数组
"""
l = self.image_lab[:, :, 0].copy()
a = self.image_lab[:, :, 1].copy()
b = self.image_lab[:, :, 2].copy()
return l, a, b
def merge_channels(self, l: NDArray, a: NDArray, b: NDArray) -> NDArray:
"""
合并LAB通道
参数:
l: 亮度通道
a: a通道
b: b通道
返回:
LAB图像
"""
return np.stack([l, a, b], axis=-1)
def lab_to_bgr(self, lab_image: NDArray) -> NDArray:
"""
LAB转BGR
参数:
lab_image: LAB图像
返回:
BGR图像
"""
return cv2.cvtColor(lab_image, cv2.COLOR_LAB2BGR)
def get_channel_statistics(self) -> Dict[str, Dict[str, float]]:
"""
获取各通道统计信息
返回:
统计信息字典
"""
l, a, b = self.get_channels()
return {
'L': {
'mean': float(np.mean(l)),
'std': float(np.std(l)),
'min': float(np.min(l)),
'max': float(np.max(l))
},
'A': {
'mean': float(np.mean(a)) - 128, # 转换为实际值
'std': float(np.std(a)),
'min': float(np.min(a)) - 128,
'max': float(np.max(a)) - 128
},
'B': {
'mean': float(np.mean(b)) - 128,
'std': float(np.std(b)),
'min': float(np.min(b)) - 128,
'max': float(np.max(b)) - 128
}
}
def adjust_lightness(self, factor: float) -> NDArray:
"""
调整亮度
参数:
factor: 亮度因子
返回:
调整后的BGR图像
"""
l, a, b = self.get_channels()
l = np.clip(l * factor, 0, 255).astype(np.uint8)
lab_new = self.merge_channels(l, a, b)
return self.lab_to_bgr(lab_new)
def adjust_contrast_lab(self, factor: float) -> NDArray:
"""
在LAB空间调整对比度
参数:
factor: 对比度因子
返回:
调整后的BGR图像
"""
l, a, b = self.get_channels()
# 只调整L通道的对比度
l_mean = np.mean(l)
l = np.clip((l - l_mean) * factor + l_mean, 0, 255).astype(np.uint8)
lab_new = self.merge_channels(l, a, b)
return self.lab_to_bgr(lab_new)
def color_balance_lab(self) -> NDArray:
"""
在LAB空间进行颜色平衡
返回:
平衡后的BGR图像
"""
l, a, b = self.get_channels()
# 将a和b通道向中心(128)调整
a = np.clip((a.astype(np.float64) - 128) * 0.9 + 128, 0, 255).astype(np.uint8)
b = np.clip((b.astype(np.float64) - 128) * 0.9 + 128, 0, 255).astype(np.uint8)
lab_new = self.merge_channels(l, a, b)
return self.lab_to_bgr(lab_new)
def compute_color_difference(self, other: NDArray) -> NDArray:
"""
计算颜色差异(CIEDE2000简化版)
参数:
other: 另一张图像(BGR格式)
返回:
差异图像
"""
other_lab = cv2.cvtColor(other, cv2.COLOR_BGR2LAB)
l1, a1, b1 = self.get_channels()
l2 = other_lab[:, :, 0]
a2 = other_lab[:, :, 1]
b2 = other_lab[:, :, 2]
# 计算欧氏距离(简化版)
diff = np.sqrt(
(l1.astype(np.float64) - l2.astype(np.float64)) ** 2 +
(a1.astype(np.float64) - a2.astype(np.float64)) ** 2 +
(b1.astype(np.float64) - b2.astype(np.float64)) ** 2
)
return diff
def color_transfer_lab(self, target: NDArray) -> NDArray:
"""
在LAB空间进行颜色迁移
参数:
target: 目标图像(BGR格式)
返回:
迁移后的图像
"""
target_lab = cv2.cvtColor(target, cv2.COLOR_BGR2LAB)
l_src, a_src, b_src = self.get_channels()
l_tgt, a_tgt, b_tgt = target_lab[:, :, 0], target_lab[:, :, 1], target_lab[:, :, 2]
# 计算均值和标准差
def get_stats(channel):
return np.mean(channel), np.std(channel)
l_mean_src, l_std_src = get_stats(l_src)
a_mean_src, a_std_src = get_stats(a_src)
b_mean_src, b_std_src = get_stats(b_src)
l_mean_tgt, l_std_tgt = get_stats(l_tgt)
a_mean_tgt, a_std_tgt = get_stats(a_tgt)
b_mean_tgt, b_std_tgt = get_stats(b_tgt)
# 迁移
def transfer(channel, mean_src, std_src, mean_tgt, std_tgt):
if std_src > 0:
result = (channel.astype(np.float64) - mean_src) / std_src * std_tgt + mean_tgt
return np.clip(result, 0, 255).astype(np.uint8)
return channel
l_new = transfer(l_src, l_mean_src, l_std_src, l_mean_tgt, l_std_tgt)
a_new = transfer(a_src, a_mean_src, a_std_src, a_mean_tgt, a_std_tgt)
b_new = transfer(b_src, b_mean_src, b_std_src, b_mean_tgt, b_std_tgt)
lab_new = self.merge_channels(l_new, a_new, b_new)
return self.lab_to_bgr(lab_new)
def enhance_colors(self, strength: float = 1.0) -> NDArray:
"""
增强颜色
参数:
strength: 增强强度
返回:
增强后的BGR图像
"""
l, a, b = self.get_channels()
# 扩展a和b通道的范围
a = np.clip((a.astype(np.float64) - 128) * (1 + strength) + 128, 0, 255).astype(np.uint8)
b = np.clip((b.astype(np.float64) - 128) * (1 + strength) + 128, 0, 255).astype(np.uint8)
lab_new = self.merge_channels(l, a, b)
return self.lab_to_bgr(lab_new)
def separate_color_tint(self) -> Dict[str, NDArray]:
"""
分离色调
返回:
色调分离结果
"""
l, a, b = self.get_channels()
# 创建色调掩码
green_mask = a < 128 # a通道低值为绿色
red_mask = a >= 128 # a通道高值为红色
blue_mask = b < 128 # b通道低值为蓝色
yellow_mask = b >= 128 # b通道高值为黄色
return {
'lightness': l,
'green_tint': green_mask.astype(np.uint8) * 255,
'red_tint': red_mask.astype(np.uint8) * 255,
'blue_tint': blue_mask.astype(np.uint8) * 255,
'yellow_tint': yellow_mask.astype(np.uint8) * 255
}
def analyze_color_temperature(self) -> Dict[str, Any]:
"""
分析色温
返回:
色温分析结果
"""
l, a, b = self.get_channels()
# a通道平均值指示绿-红偏移
a_mean = np.mean(a) - 128
# b通道平均值指示蓝-黄偏移
b_mean = np.mean(b) - 128
# 判断色温
if a_mean > 10:
color_temp = "偏暖(红色调)"
elif a_mean < -10:
color_temp = "偏冷(绿色调)"
elif b_mean > 10:
color_temp = "偏暖(黄色调)"
elif b_mean < -10:
color_temp = "偏冷(蓝色调)"
else:
color_temp = "中性"
return {
'a_mean': a_mean,
'b_mean': b_mean,
'color_temperature': color_temp,
'warmth_score': float(a_mean + b_mean) # 正值偏暖,负值偏冷
}
def demonstrate_lab_operations():
"""
演示LAB色彩空间操作
"""
# 创建测试图像
image = np.zeros((400, 600, 3), dtype=np.uint8)
# 填充渐变
for y in range(400):
for x in range(600):
image[y, x] = [
int(x * 255 / 600),
int(y * 255 / 400),
128
]
lab_space = LABColorSpace(image)
print("LAB色彩空间操作演示")
print("=" * 50)
# 通道统计
stats = lab_space.get_channel_statistics()
print(f"通道统计:")
for channel, values in stats.items():
print(f" {channel}: 均值={values['mean']:.2f}")
# 色温分析
temp_analysis = lab_space.analyze_color_temperature()
print(f"\n色温分析:")
print(f" 色温: {temp_analysis['color_temperature']}")
print(f" 暖度评分: {temp_analysis['warmth_score']:.2f}")
# 亮度调整
brightened = lab_space.adjust_lightness(1.2)
print(f"\n亮度增加20%完成")
# 颜色增强
enhanced = lab_space.enhance_colors(0.5)
print(f"颜色增强完成")
# 颜色平衡
balanced = lab_space.color_balance_lab()
print(f"颜色平衡完成")
return {
'original': image,
'brightened': brightened,
'enhanced': enhanced,
'balanced': balanced
}
if __name__ == "__main__":
results = demonstrate_lab_operations()
print("\nLAB色彩空间操作演示完成")
6.5 多色彩空间综合应用
6.5.1 色彩空间选择策略
在实际应用中,选择合适的色彩空间对于获得最佳处理效果至关重要。以下代码展示了多色彩空间的综合应用。
python
"""
多色彩空间综合应用 - Tkinter图形界面
演示不同色彩空间在实际任务中的应用
兼容Python 3.13
"""
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from PIL import Image, ImageTk
import cv2
import numpy as np
from typing import Tuple, List, Dict, Any, Optional
from numpy.typing import NDArray
class HSVColorSpace:
"""
HSV色彩空间操作类(简化版)
"""
# 常见颜色的HSV范围
COLOR_RANGES = {
'red': [
(np.array([0, 100, 100]), np.array([10, 255, 255])),
(np.array([160, 100, 100]), np.array([180, 255, 255]))
],
'orange': [(np.array([11, 100, 100]), np.array([25, 255, 255]))],
'yellow': [(np.array([26, 100, 100]), np.array([34, 255, 255]))],
'green': [(np.array([35, 100, 100]), np.array([77, 255, 255]))],
'cyan': [(np.array([78, 100, 100]), np.array([99, 255, 255]))],
'blue': [(np.array([100, 100, 100]), np.array([124, 255, 255]))],
'purple': [(np.array([125, 100, 100]), np.array([155, 255, 255]))],
'white': [(np.array([0, 0, 200]), np.array([180, 30, 255]))],
'gray': [(np.array([0, 0, 50]), np.array([180, 30, 200]))],
'black': [(np.array([0, 0, 0]), np.array([180, 255, 50]))]
}
def __init__(self, image: NDArray):
self.image_bgr = image
self.image_hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
self.height, self.width = image.shape[:2]
def detect_color(self, color_name: str) -> NDArray:
"""检测特定颜色"""
if color_name not in self.COLOR_RANGES:
raise ValueError(f"未知颜色: {color_name}")
ranges = self.COLOR_RANGES[color_name]
mask = np.zeros((self.height, self.width), dtype=np.uint8)
for lower, upper in ranges:
mask = cv2.bitwise_or(mask, cv2.inRange(self.image_hsv, lower, upper))
return mask
class MultiColorSpaceProcessor:
"""
多色彩空间综合处理器
"""
def __init__(self, image: NDArray):
"""
初始化多色彩空间处理器
参数:
image: 输入图像(BGR格式)
"""
self.image_bgr = image
self.image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
self.image_hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
self.image_lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
self.image_ycrcb = cv2.cvtColor(image, cv2.COLOR_BGR2YCrCb)
self.height, self.width = image.shape[:2]
def detect_skin_color(self) -> NDArray:
"""
检测肤色(多色彩空间融合)
返回:
肤色掩码
"""
# RGB规则
r, g, b = self.image_rgb[:, :, 0], self.image_rgb[:, :, 1], self.image_rgb[:, :, 2]
rgb_mask = (r > 95) & (g > 40) & (b > 20) & \
(np.maximum(r, np.maximum(g, b)) - np.minimum(r, np.minimum(g, b)) > 15) & \
(np.abs(r.astype(np.int32) - g.astype(np.int32)) > 15) & \
(r > g) & (r > b)
# HSV规则
h, s, v = self.image_hsv[:, :, 0], self.image_hsv[:, :, 1], self.image_hsv[:, :, 2]
hsv_mask = (h >= 0) & (h <= 50) & (s >= 15) & (s <= 170)
# YCrCb规则
y, cr, cb = self.image_ycrcb[:, :, 0], self.image_ycrcb[:, :, 1], self.image_ycrcb[:, :, 2]
ycrcb_mask = (cr >= 133) & (cr <= 173) & (cb >= 77) & (cb <= 127)
# 融合
combined_mask = rgb_mask & hsv_mask & ycrcb_mask
# 形态学处理
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
combined_mask = cv2.morphologyEx(combined_mask.astype(np.uint8) * 255,
cv2.MORPH_OPEN, kernel)
combined_mask = cv2.morphologyEx(combined_mask, cv2.MORPH_CLOSE, kernel)
return combined_mask
def auto_white_balance(self, method: str = 'gray_world') -> NDArray:
"""
自动白平衡
参数:
method: 白平衡方法 ('gray_world', 'white_patch', 'lab')
返回:
白平衡后的图像
"""
if method == 'gray_world':
# 灰度世界假设
b, g, r = cv2.split(self.image_bgr)
b_mean, g_mean, r_mean = np.mean(b), np.mean(g), np.mean(r)
gray_mean = (b_mean + g_mean + r_mean) / 3
b = np.clip(b * gray_mean / b_mean, 0, 255).astype(np.uint8)
g = np.clip(g * gray_mean / g_mean, 0, 255).astype(np.uint8)
r = np.clip(r * gray_mean / r_mean, 0, 255).astype(np.uint8)
return cv2.merge([b, g, r])
elif method == 'white_patch':
# 白点假设
b, g, r = cv2.split(self.image_bgr)
b_max, g_max, r_max = np.max(b), np.max(g), np.max(r)
b = np.clip(b * 255.0 / b_max, 0, 255).astype(np.uint8)
g = np.clip(g * 255.0 / g_max, 0, 255).astype(np.uint8)
r = np.clip(r * 255.0 / r_max, 0, 255).astype(np.uint8)
return cv2.merge([b, g, r])
elif method == 'lab':
# LAB空间白平衡
l, a, b = cv2.split(self.image_lab)
a = np.clip((a.astype(np.float64) - 128) * 0.9 + 128, 0, 255).astype(np.uint8)
b = np.clip((b.astype(np.float64) - 128) * 0.9 + 128, 0, 255).astype(np.uint8)
balanced_lab = cv2.merge([l, a, b])
return cv2.cvtColor(balanced_lab, cv2.COLOR_LAB2BGR)
return self.image_bgr.copy()
def enhance_image(self,
brightness: float = 1.0,
contrast: float = 1.0,
saturation: float = 1.0) -> NDArray:
"""
综合图像增强
参数:
brightness: 亮度因子
contrast: 对比度因子
saturation: 饱和度因子
返回:
增强后的图像
"""
# 在LAB空间调整亮度和对比度
l, a, b = cv2.split(self.image_lab)
# 亮度调整
l = np.clip(l * brightness, 0, 255).astype(np.uint8)
# 对比度调整
l_mean = np.mean(l)
l = np.clip((l.astype(np.float64) - l_mean) * contrast + l_mean, 0, 255).astype(np.uint8)
# 饱和度调整(在HSV空间)
h, s, v = cv2.split(self.image_hsv)
s = np.clip(s * saturation, 0, 255).astype(np.uint8)
# 合并LAB并转换
enhanced_lab = cv2.merge([l, a, b])
result = cv2.cvtColor(enhanced_lab, cv2.COLOR_LAB2BGR)
return result
def color_segmentation(self,
colors: List[str],
combine: str = 'union') -> NDArray:
"""
多颜色分割
参数:
colors: 颜色名称列表
combine: 组合方式 ('union', 'intersection')
返回:
分割掩码
"""
# 创建HSV处理器(直接在内部创建)
hsv_processor = HSVColorSpace(self.image_bgr)
masks = []
for color in colors:
mask = hsv_processor.detect_color(color)
masks.append(mask)
if combine == 'union':
result = np.zeros((self.height, self.width), dtype=np.uint8)
for mask in masks:
result = cv2.bitwise_or(result, mask)
else: # intersection
result = np.ones((self.height, self.width), dtype=np.uint8) * 255
for mask in masks:
result = cv2.bitwise_and(result, mask)
return result
def analyze_image_colors(self) -> Dict[str, Any]:
"""
综合分析图像颜色
返回:
颜色分析结果
"""
# RGB分析
r, g, b = self.image_rgb[:, :, 0], self.image_rgb[:, :, 1], self.image_rgb[:, :, 2]
# HSV分析
h, s, v = self.image_hsv[:, :, 0], self.image_hsv[:, :, 1], self.image_hsv[:, :, 2]
# LAB分析
l, a, b_lab = self.image_lab[:, :, 0], self.image_lab[:, :, 1], self.image_lab[:, :, 2]
return {
'rgb': {
'r_mean': float(np.mean(r)),
'g_mean': float(np.mean(g)),
'b_mean': float(np.mean(b)),
'correlation': float(np.corrcoef(r.flatten(), g.flatten())[0, 1])
},
'hsv': {
'h_mean': float(np.mean(h)),
's_mean': float(np.mean(s)),
'v_mean': float(np.mean(v)),
'colorfulness': float(np.std(s) * np.mean(s) / 255)
},
'lab': {
'l_mean': float(np.mean(l)),
'a_mean': float(np.mean(a) - 128),
'b_mean': float(np.mean(b_lab) - 128)
},
'overall': {
'brightness': float(np.mean(v)),
'contrast': float(np.std(v)),
'saturation': float(np.mean(s))
}
}
class MultiColorSpaceApp:
"""
多色彩空间综合应用GUI
"""
def __init__(self, root):
self.root = root
self.root.title("多色彩空间综合应用")
self.root.geometry("1400x800")
self.processor = None
self.current_image = None
self.setup_ui()
def setup_ui(self):
"""设置用户界面"""
# 主容器
main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 配置网格权重
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
main_frame.columnconfigure(1, weight=1)
main_frame.rowconfigure(1, weight=1)
# 左侧控制面板(带滚动条,限制宽度)
control_container = ttk.Frame(main_frame, width=280)
control_container.grid(row=0, column=0, rowspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), padx=5)
control_container.grid_propagate(False) # 防止子组件改变容器大小
control_canvas = tk.Canvas(control_container)
scrollbar = ttk.Scrollbar(control_container, orient="vertical", command=control_canvas.yview)
control_frame = ttk.LabelFrame(control_canvas, text="控制面板", padding="10")
control_frame.bind(
"<Configure>",
lambda e: control_canvas.configure(scrollregion=control_canvas.bbox("all"))
)
control_canvas.create_window((0, 0), window=control_frame, anchor="nw")
control_canvas.configure(yscrollcommand=scrollbar.set)
control_canvas.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
# 添加鼠标滚轮事件
def _on_mousewheel(event):
control_canvas.yview_scroll(int(-1*(event.delta/120)), "units")
# Windows系统
control_canvas.bind_all("<MouseWheel>", _on_mousewheel)
# Linux系统
control_canvas.bind_all("<Button-4>", lambda event: control_canvas.yview_scroll(-1, "units"))
control_canvas.bind_all("<Button-5>", lambda event: control_canvas.yview_scroll(1, "units"))
# 文件操作
ttk.Label(control_frame, text="文件操作", font=('Arial', 10, 'bold')).pack(pady=(0, 5))
ttk.Button(control_frame, text="打开图像", command=self.load_image).pack(fill=tk.X, pady=2)
ttk.Button(control_frame, text="保存图像", command=self.save_image).pack(fill=tk.X, pady=2)
ttk.Separator(control_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10)
# 白平衡
ttk.Label(control_frame, text="白平衡", font=('Arial', 10, 'bold')).pack(pady=(0, 5))
ttk.Label(control_frame, text="白平衡方法:").pack(anchor=tk.W)
self.white_balance_method = tk.StringVar(value="gray_world")
method_combo = ttk.Combobox(control_frame, textvariable=self.white_balance_method,
values=["gray_world", "white_patch", "lab"], state="readonly")
method_combo.pack(fill=tk.X, pady=2)
ttk.Button(control_frame, text="应用白平衡", command=self.apply_white_balance).pack(fill=tk.X, pady=2)
ttk.Separator(control_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10)
# 图像增强
ttk.Label(control_frame, text="图像增强", font=('Arial', 10, 'bold')).pack(pady=(0, 5))
ttk.Label(control_frame, text="亮度因子:").pack(anchor=tk.W)
self.brightness_scale = tk.Scale(control_frame, from_=0.5, to=2.0, resolution=0.1, orient=tk.HORIZONTAL)
self.brightness_scale.set(1.0)
self.brightness_scale.pack(fill=tk.X, pady=2)
ttk.Label(control_frame, text="对比度因子:").pack(anchor=tk.W)
self.contrast_scale = tk.Scale(control_frame, from_=0.5, to=2.0, resolution=0.1, orient=tk.HORIZONTAL)
self.contrast_scale.set(1.0)
self.contrast_scale.pack(fill=tk.X, pady=2)
ttk.Label(control_frame, text="饱和度因子:").pack(anchor=tk.W)
self.saturation_scale = tk.Scale(control_frame, from_=0.5, to=2.0, resolution=0.1, orient=tk.HORIZONTAL)
self.saturation_scale.set(1.0)
self.saturation_scale.pack(fill=tk.X, pady=2)
ttk.Button(control_frame, text="应用增强", command=self.apply_enhance).pack(fill=tk.X, pady=2)
ttk.Separator(control_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10)
# 颜色分割
ttk.Label(control_frame, text="颜色分割", font=('Arial', 10, 'bold')).pack(pady=(0, 5))
ttk.Label(control_frame, text="选择颜色(Ctrl+多选):").pack(anchor=tk.W)
# 创建颜色列表框
self.color_listbox = tk.Listbox(control_frame, selectmode=tk.MULTIPLE, height=5)
colors = ['red', 'orange', 'yellow', 'green', 'cyan', 'blue', 'purple', 'white', 'gray', 'black']
for color in colors:
self.color_listbox.insert(tk.END, color)
self.color_listbox.pack(fill=tk.X, pady=2)
ttk.Label(control_frame, text="组合方式:").pack(anchor=tk.W)
self.combine_var = tk.StringVar(value="union")
combine_frame = ttk.Frame(control_frame)
combine_frame.pack(fill=tk.X, pady=2)
ttk.Radiobutton(combine_frame, text="并集", variable=self.combine_var, value="union").pack(side=tk.LEFT)
ttk.Radiobutton(combine_frame, text="交集", variable=self.combine_var, value="intersection").pack(side=tk.LEFT)
ttk.Button(control_frame, text="颜色分割", command=self.color_segmentation).pack(fill=tk.X, pady=2)
ttk.Separator(control_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10)
# 肤色检测
ttk.Label(control_frame, text="肤色检测", font=('Arial', 10, 'bold')).pack(pady=(0, 5))
ttk.Button(control_frame, text="检测肤色", command=self.detect_skin).pack(fill=tk.X, pady=2)
ttk.Separator(control_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10)
# 分析按钮
ttk.Label(control_frame, text="图像分析", font=('Arial', 10, 'bold')).pack(pady=(0, 5))
ttk.Button(control_frame, text="颜色分析", command=self.analyze_colors).pack(fill=tk.X, pady=2)
ttk.Separator(control_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10)
ttk.Button(control_frame, text="重置图像", command=self.reset_image).pack(fill=tk.X, pady=5)
# 图像显示区域
image_frame = ttk.LabelFrame(main_frame, text="图像显示", padding="10")
image_frame.grid(row=0, column=1, sticky=(tk.W, tk.E, tk.N, tk.S), padx=5)
image_frame.columnconfigure(0, weight=1)
image_frame.rowconfigure(0, weight=1)
self.image_label = ttk.Label(image_frame)
self.image_label.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 信息显示区域
info_frame = ttk.LabelFrame(main_frame, text="信息显示", padding="10")
info_frame.grid(row=1, column=1, sticky=(tk.W, tk.E, tk.N, tk.S), padx=5, pady=(5, 0))
info_frame.columnconfigure(0, weight=1)
info_frame.rowconfigure(0, weight=1)
self.info_text = tk.Text(info_frame, height=8, wrap=tk.WORD)
scrollbar = ttk.Scrollbar(info_frame, orient=tk.VERTICAL, command=self.info_text.yview)
self.info_text.configure(yscrollcommand=scrollbar.set)
self.info_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
# 创建初始测试图像
self.create_test_image()
def load_image(self):
"""加载图像"""
file_path = filedialog.askopenfilename(
title="选择图像文件",
filetypes=[("图像文件", "*.jpg *.jpeg *.png *.bmp *.tiff")]
)
if file_path:
try:
image = cv2.imread(file_path)
if image is None:
messagebox.showerror("错误", "无法加载图像文件")
return
self.processor = MultiColorSpaceProcessor(image)
self.current_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
self.display_image(self.current_image)
self.clear_info()
self.info_text.insert(tk.END, f"已加载图像: {file_path}\n")
self.info_text.insert(tk.END, f"图像尺寸: {image.shape[1]}x{image.shape[0]}\n")
except Exception as e:
messagebox.showerror("错误", f"加载图像失败: {str(e)}")
def save_image(self):
"""保存图像"""
if self.current_image is None:
messagebox.showwarning("警告", "没有可保存的图像")
return
file_path = filedialog.asksaveasfilename(
title="保存图像",
defaultextension=".jpg",
filetypes=[("JPEG图像", "*.jpg"), ("PNG图像", "*.png"), ("BMP图像", "*.bmp")]
)
if file_path:
try:
# 转换为BGR格式保存
bgr_image = cv2.cvtColor(self.current_image, cv2.COLOR_RGB2BGR)
cv2.imwrite(file_path, bgr_image)
messagebox.showinfo("成功", f"图像已保存到: {file_path}")
except Exception as e:
messagebox.showerror("错误", f"保存图像失败: {str(e)}")
def create_test_image(self):
"""创建测试图像"""
image = np.random.randint(0, 256, (400, 600, 3), dtype=np.uint8)
self.processor = MultiColorSpaceProcessor(image)
self.current_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
self.display_image(self.current_image)
self.info_text.insert(tk.END, "已创建随机测试图像 (400x600)\n")
def display_image(self, image_array):
"""显示图像"""
# 调整图像大小以适应显示区域
height, width = image_array.shape[:2]
max_width = 600
max_height = 400
if width > max_width or height > max_height:
scale = min(max_width / width, max_height / height)
new_width = int(width * scale)
new_height = int(height * scale)
image_array = cv2.resize(image_array, (new_width, new_height))
# 转换为PIL Image
pil_image = Image.fromarray(image_array)
photo = ImageTk.PhotoImage(pil_image)
self.image_label.config(image=photo)
self.image_label.image = photo
def apply_white_balance(self):
"""应用白平衡"""
if self.processor is None:
messagebox.showwarning("警告", "请先加载图像")
return
try:
method = self.white_balance_method.get()
result = self.processor.auto_white_balance(method)
self.current_image = cv2.cvtColor(result, cv2.COLOR_BGR2RGB)
self.display_image(self.current_image)
self.clear_info()
self.info_text.insert(tk.END, f"已应用白平衡\n")
self.info_text.insert(tk.END, f"方法: {method}\n")
except Exception as e:
messagebox.showerror("错误", f"白平衡失败: {str(e)}")
def apply_enhance(self):
"""应用图像增强"""
if self.processor is None:
messagebox.showwarning("警告", "请先加载图像")
return
try:
brightness = self.brightness_scale.get()
contrast = self.contrast_scale.get()
saturation = self.saturation_scale.get()
result = self.processor.enhance_image(brightness, contrast, saturation)
self.current_image = cv2.cvtColor(result, cv2.COLOR_BGR2RGB)
self.display_image(self.current_image)
self.clear_info()
self.info_text.insert(tk.END, f"已应用图像增强\n")
self.info_text.insert(tk.END, f"亮度因子: {brightness}\n")
self.info_text.insert(tk.END, f"对比度因子: {contrast}\n")
self.info_text.insert(tk.END, f"饱和度因子: {saturation}\n")
except Exception as e:
messagebox.showerror("错误", f"图像增强失败: {str(e)}")
def color_segmentation(self):
"""颜色分割"""
if self.processor is None:
messagebox.showwarning("警告", "请先加载图像")
return
try:
# 获取选中的颜色
selected_indices = self.color_listbox.curselection()
if not selected_indices:
messagebox.showwarning("警告", "请选择至少一种颜色")
return
colors = [self.color_listbox.get(i) for i in selected_indices]
combine = self.combine_var.get()
mask = self.processor.color_segmentation(colors, combine)
# 将掩码转换为可视化图像
mask_rgb = cv2.merge([mask, np.zeros_like(mask), np.zeros_like(mask)])
self.current_image = mask_rgb
self.display_image(self.current_image)
self.clear_info()
self.info_text.insert(tk.END, f"已执行颜色分割\n")
self.info_text.insert(tk.END, f"检测颜色: {', '.join(colors)}\n")
self.info_text.insert(tk.END, f"组合方式: {combine}\n")
pixel_count = np.sum(mask > 0)
self.info_text.insert(tk.END, f"检测到的像素数: {pixel_count}\n")
except Exception as e:
messagebox.showerror("错误", f"颜色分割失败: {str(e)}")
def detect_skin(self):
"""检测肤色"""
if self.processor is None:
messagebox.showwarning("警告", "请先加载图像")
return
try:
mask = self.processor.detect_skin_color()
# 将掩码转换为可视化图像
mask_rgb = cv2.merge([mask, np.zeros_like(mask), np.zeros_like(mask)])
self.current_image = mask_rgb
self.display_image(self.current_image)
self.clear_info()
self.info_text.insert(tk.END, "已执行肤色检测\n")
self.info_text.insert(tk.END, "检测方法: RGB + HSV + YCrCb 融合\n")
pixel_count = np.sum(mask > 0)
self.info_text.insert(tk.END, f"检测到的肤色像素数: {pixel_count}\n")
except Exception as e:
messagebox.showerror("错误", f"肤色检测失败: {str(e)}")
def analyze_colors(self):
"""颜色分析"""
if self.processor is None:
messagebox.showwarning("警告", "请先加载图像")
return
try:
analysis = self.processor.analyze_image_colors()
self.clear_info()
self.info_text.insert(tk.END, "=== 综合颜色分析 ===\n\n")
# RGB分析
rgb = analysis['rgb']
self.info_text.insert(tk.END, "RGB色彩空间:\n")
self.info_text.insert(tk.END, f" R均值: {rgb['r_mean']:.1f}\n")
self.info_text.insert(tk.END, f" G均值: {rgb['g_mean']:.1f}\n")
self.info_text.insert(tk.END, f" B均值: {rgb['b_mean']:.1f}\n")
self.info_text.insert(tk.END, f" R-G相关系数: {rgb['correlation']:.3f}\n\n")
# HSV分析
hsv = analysis['hsv']
self.info_text.insert(tk.END, "HSV色彩空间:\n")
self.info_text.insert(tk.END, f" H均值: {hsv['h_mean']:.1f}\n")
self.info_text.insert(tk.END, f" S均值: {hsv['s_mean']:.1f}\n")
self.info_text.insert(tk.END, f" V均值: {hsv['v_mean']:.1f}\n")
self.info_text.insert(tk.END, f" 色彩丰富度: {hsv['colorfulness']:.2f}\n\n")
# LAB分析
lab = analysis['lab']
self.info_text.insert(tk.END, "LAB色彩空间:\n")
self.info_text.insert(tk.END, f" L均值: {lab['l_mean']:.1f}\n")
self.info_text.insert(tk.END, f" a均值: {lab['a_mean']:.1f}\n")
self.info_text.insert(tk.END, f" b均值: {lab['b_mean']:.1f}\n\n")
# 整体分析
overall = analysis['overall']
self.info_text.insert(tk.END, "整体特征:\n")
self.info_text.insert(tk.END, f" 亮度: {overall['brightness']:.1f}\n")
self.info_text.insert(tk.END, f" 对比度: {overall['contrast']:.1f}\n")
self.info_text.insert(tk.END, f" 饱和度: {overall['saturation']:.1f}\n")
except Exception as e:
messagebox.showerror("错误", f"颜色分析失败: {str(e)}")
def reset_image(self):
"""重置图像"""
if self.processor is None:
messagebox.showwarning("警告", "没有可重置的图像")
return
try:
# 从原始BGR图像创建新的RGB图像
self.current_image = cv2.cvtColor(self.processor.image_bgr, cv2.COLOR_BGR2RGB)
self.display_image(self.current_image)
self.clear_info()
self.info_text.insert(tk.END, "图像已重置\n")
except Exception as e:
messagebox.showerror("错误", f"重置图像失败: {str(e)}")
def clear_info(self):
"""清空信息显示区域"""
self.info_text.delete(1.0, tk.END)
if __name__ == "__main__":
root = tk.Tk()
app = MultiColorSpaceApp(root)
root.mainloop()
6.6 本章小结
本章详细介绍了图像色彩空间的原理与应用,包括RGB、HSV、LAB、YCrCb等常用色彩空间。不同的色彩空间各有特点,适用于不同的应用场景:RGB适合图像显示和存储,HSV适合颜色检测和分割,LAB适合颜色分析和迁移,YCrCb适合视频处理。
在实际应用中,选择合适的色彩空间对于获得最佳处理效果至关重要。有时需要结合多个色彩空间的信息,才能实现更准确的颜色处理。例如,肤色检测通常结合RGB、HSV和YCrCb三个色彩空间的规则,以获得更准确的结果。
下一章将介绍图像几何变换与仿射变换矩阵,深入讲解图像的平移、旋转、缩放、透视变换等操作的数学原理和实现方法。
GPT-5.4辅助编程提示词:
text
我需要实现一个智能颜色检测系统,请帮我编写完整的Python代码:
需求描述:
1. 读取图像并分析其主要颜色
2. 检测指定的目标颜色(可配置颜色范围)
3. 统计各颜色的面积占比
4. 生成颜色分布可视化图
5. 支持批量处理多张图像
技术要求:
- 使用OpenCV进行色彩空间转换
- 支持RGB、HSV、LAB三种色彩空间
- 兼容Python 3.13
- 提供详细的颜色分析报告
- 包含可视化输出