如何用OpenCV提取纯色块图像中的所有颜色并分离色块?
纯色块图像的颜色提取与色块分离
问题背景
需要处理由纯色块组成的图像,目标是提取所有视觉上的纯色,并将每个色块单独分离。尝试过以下方法但效果不佳:
- 自动阈值提取:无法适配多色块场景
t, _ = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) _, target = cv2.threshold(img, t, 255, cv2.THRESH_BINARY_INV) cnts, hierarchys = cv2.findContours(target, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
- 边缘检测+形态学操作:残留大量多余轮廓
gray_temp = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) edges = 255 - cv2.Canny(gray_temp, 25, 50) kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)) edges = cv2.erode(edges, kernel, iterations=1) cnts, hierarchys = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
另外,用numpy提取颜色时,视觉相同的像素RGB值存在差异;使用extcolors库时,不同图像需要调整不同的tolerance值,通用性差。
解决方案
1. 色彩量化:解决同视觉色RGB不一致问题
用K-means聚类将相似颜色自动归为一类,无需手动调参,适配不同图像。核心思路是把像素的RGB值作为特征聚类,将视觉相近的像素合并为同一颜色:
import cv2 import numpy as np from sklearn.cluster import KMeans # 读取图像并转RGB格式 img = cv2.imread("your_image.jpg") img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 将图像转为二维像素数组 pixels = img_rgb.reshape(-1, 3) # 设定聚类数(可根据图像色块数量预估,或用肘部法则自动判断最优值) k = 5 kmeans = KMeans(n_clusters=k, random_state=42) kmeans.fit(pixels) # 提取聚类后的纯色中心(即图像中所有视觉纯色) extracted_colors = kmeans.cluster_centers_.astype(int) # 生成颜色统一的量化图像 quantized_img = kmeans.labels_.reshape(img_rgb.shape[:2])
2. 基于量化图像分离色块
量化后的图像每个色块颜色完全统一,用连通区域分析或轮廓检测即可精准分离:
方法一:连通区域分析(推荐,能区分同色不连通的色块)
# 遍历每个颜色类别 for color_idx in range(k): # 生成当前颜色的掩码 mask = (quantized_img == color_idx).astype(np.uint8) * 255 # 获取连通区域信息 num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(mask, connectivity=8) # 遍历每个连通色块(跳过背景标签0) for i in range(1, num_labels): # 提取当前色块的掩码 block_mask = (labels == i).astype(np.uint8) * 255 # 提取色块图像 block = cv2.bitwise_and(img, img, mask=block_mask) # 保存或处理色块 cv2.imwrite(f"block_color{color_idx}_id{i}.png", block)
方法二:轮廓检测
for color_idx in range(k): mask = (quantized_img == color_idx).astype(np.uint8) * 255 # 查找外轮廓(避免嵌套轮廓干扰) cnts, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) for cnt in cnts: # 创建空白画布绘制色块 block = np.zeros_like(img) cv2.drawContours(block, [cnt], -1, (255,255,255), thickness=cv2.FILLED) # 提取色块 block = cv2.bitwise_and(img, block) cv2.imwrite(f"block_color{color_idx}.png", block)
3. 原有方法的改进思路
- 自动阈值法仅适用于单通道二值场景,多色块图像必须先做色彩量化,再对每个颜色单独做阈值处理
- 边缘检测法的误差源于颜色细微差异,先量化消除噪声后,再用边缘检测能大幅减少多余轮廓
内容的提问来源于stack exchange,提问作者HamGuy
相关产品推荐
相关产品推荐

