本文将介绍如何在HSV空间替换衣服的颜色
完整代码可见我的Github
包括单纯opencv和用pyqt5实现图形界面的
HSV详解
HSV颜色空间介绍
HSV是一种将RGB色彩模型中的点在圆柱坐标系中的表示方法
-
色相
Hue( 是色彩的基本属性) 用角度度量, 取值范围为0°~360°, 在OpenCV中为0-180( 是由于8bit的最大值为255, ) 从红色开始按逆时针方向计算, 红色为0°, 绿色为120°,蓝色为240°, 。 -
饱和度
Saturation( 表示颜色接近光谱色的程度) 一种颜色。 可以看成是某种光谱色与白色混合的结果, 其中光谱色所占的比例愈大。 颜色接近光谱色的程度就愈高, 颜色的饱和度也就愈高, 饱和度高。 颜色则深而艳, 光谱色的白光成分为0。 饱和度达到最高, 通常取值范围为0%~100%。 值越大, 颜色越饱和, 在OpenCV中为0-255( ) 。 -
明度
Value( ) 明度表示颜色明亮的程度, 对于光源色, 明度值与发光体的光亮度有关, 对于物体色; 此值和物体的透射比或反射比有关, 光照对此值影响最大, 通常取值范围为0%。 黑( 到100%) 在OpenCV中为0-255( ) 。
RGB转换为HSV
$ r = R/255.0, \ g = G/255.0 , \ b=B/255.0 $
$ max = \max(r, g, b), \ min = \min(r, g, b) $
HSV与HSL的对比
注
上一张经典的对比图
-
在HSL中
亮度L分量跨越从黑色经过选择的色相到白色的完整范围, ; -
在HSV中
V分量只走一半行程, 从黑色到选择的色相, 。
下列左图为HSL
代码实现
第一步
img = cv2.imread(r"图片路径")
rows, cols, _ = img_hsv.shape
cv2.namedWindow("ChangeColor")
cv2.resizeWindow("ChangeColor", rows, cols)
# 创建一个鼠标事件响应
cv2.setMouseCallback("ChangeColor", get_color_BGR)
# 这是setMouseCallback的声明原型<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>参数分别为窗口名<span class="bd-box"><h-char class="bd bd-beg"><h-inner>、</h-inner></h-char></span>鼠标响应函数<span class="bd-box"><h-char class="bd bd-beg"><h-inner>、</h-inner></h-char></span>传给鼠标响应函数参数
# def setMouseCallback(windowName: Any,
# onMouse: Any,
# param: Any = None) -> None
# 回调函数<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span> 即鼠标事件响应函数
def get_color_BGR(envent, x, y, flags, param):
if envent == cv2.EVENT_LBUTTONDOWN:
# 注意<span class="bd-box"><h-char class="bd bd-beg"><h-inner>:</h-inner></h-char></span>opencv的行列与坐标相反(row, col) -> (y, x)
BGR = img[y, x].tolist()
第二步
HSV = [0, 0, 256] # 一个不存在的HSV值
def bgr2hsv(b, g, r):
# 限定像素BGR值的范围
assert (0 <= r < 256 and 0 <= g < 256 and 0 <= b < 256)
r, g, b = r / 255.0, g / 255.0, b / 255.0
Max = max(r, g, b)
Min = min(r, g, b)
D = Max - Min
HSV[2] = int(Max * 255)
HSV[1] = int(255 * (D / Max)) if Max != 0 else 0
if D == 0:
HSV[0] = 0
elif Max == r:
HSV[0] = (60 * ((g - b) / D)) % 360
elif Max == g:
HSV[0] = (120 + 60 * ((b - r) / D)) % 360
elif Max == b:
HSV[0] = (240 + 60 * ((r - g) / D)) % 360
HSV[0] = int(HSV[0] / 2)
return HSV
第三步
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# HSV 的下界限
lower = np.array([HSV[0] - 7, 50, 50])
# HSV 的上界限
upper = np.array([HSV[0] + 7, 255, 255])
# 找到H分量在这个范围的区域
mask = cv2.inRange(img_hsv, lower, upper)
# 腐蚀和膨胀的核kernel<span class="bd-box"><h-char class="bd bd-end"><h-inner>(</h-inner></h-char></span>可根据图片自己设计<span class="bd-box"><h-char class="bd bd-beg"><h-inner>)</h-inner></h-char></span>
kernel = np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]], dtype=np.uint8)
# 腐蚀图像<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>除去干扰点
eroded = cv2.erode(mask, kernel, iterations=1)
# 膨胀图像<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>恢复腐蚀前的大小
dilated = cv2.dilate(eroded, kernel, iterations=1)
第四步
# 在‘ChangeColor’窗口中创建滑动控制条
# 参数介绍 createTrackbar(const String& trackbarname, const String& winname, int *value, int count, TrackbarCallback onChange = 0, void *userdata = 0)
# 分别为滑动条名称<span class="bd-box"><h-char class="bd bd-beg"><h-inner>、</h-inner></h-char></span>窗口名称<span class="bd-box"><h-char class="bd bd-beg"><h-inner>、</h-inner></h-char></span>初始值<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>范围<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>滑块更改时的回调函数<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>传给回调函数的参数<span class="bd-box"><h-char class="bd bd-end"><h-inner>(</h-inner></h-char></span>默认为0<span class="bd-box"><h-char class="bd bd-beg"><h-inner>)</h-inner></h-char></span>
cv2.createTrackbar('H', 'ChangeColor', 140, 180, lambda x: None)
# cv2.createTrackbar('S', 'ChangeColor', 100, 255, lambda x: None)
# cv2.createTrackbar('V', 'ChangeColor', 100, 255, lambda x: None)
# 获取滑块的值
h = cv2.getTrackbarPos('H', 'ChangeColor')
# s = cv2.getTrackbarPos('S', 'ChangeColor')
# v = cv2.getTrackbarPos('V', 'ChangeColor')
for row in range(rows):
for col in range(cols):
if dilated[row, col] == 255:
img_hsv.itemset((row, col, 0), h)
# img_hsv.itemset((row, col, 1), s)
# img_hsv.itemset((row, col, 2), v)
img = cv2.cvtColor(img_hsv, cv2.COLOR_HSV2BGR)
cv2.imshow("ChangeColor", img)
完整代码
可以直接在我的Github上Clone
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# @FileName :main.py
# @Time :2023/4/7 19:46
# @Author :Ouyang Bin
import cv2
import numpy as np
# 不存在的HSV值
HSV = [0, 0, 256]
newHSV = [0, 0, 256]
def bgr2hsv(b, g, r):
# 限定像素BGR值的范围
assert (0 <= r < 256 and 0 <= g < 256 and 0 <= b < 256)
r, g, b = r / 255.0, g / 255.0, b / 255.0
Max = max(r, g, b)
Min = min(r, g, b)
D = Max - Min
HSV[2] = int(Max * 255)
HSV[1] = int(255 * (D / Max)) if Max != 0 else 0
if D == 0:
HSV[0] = 0
elif Max == r:
HSV[0] = (60 * ((g - b) / D)) % 360
elif Max == g:
HSV[0] = (120 + 60 * ((b - r) / D)) % 360
elif Max == b:
HSV[0] = (240 + 60 * ((r - g) / D)) % 360
HSV[0] = int(HSV[0] / 2)
return HSV
# 回调函数
def get_color_BGR(envent, x, y, flags, param):
if envent == cv2.EVENT_LBUTTONDOWN:
# 注意<span class="bd-box"><h-char class="bd bd-beg"><h-inner>:</h-inner></h-char></span>opencv的行列与坐标相反(row, col) -> (y, x)
BGR = img[y, x].tolist()
bgr2hsv(BGR[0], BGR[1], BGR[2])
img = cv2.imread(r"C:\Users\19941\Pictures\cloth.jpg")
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
rows, cols, _ = img_hsv.shape
cv2.namedWindow("ChangeColor")
cv2.resizeWindow("ChangeColor", rows, cols)
cv2.setMouseCallback("ChangeColor", get_color_BGR)
cv2.createTrackbar('H', 'ChangeColor', 140, 180, lambda x: None)
# cv2.createTrackbar('S', 'ChangeColor', 100, 255, lambda x: None)
# cv2.createTrackbar('V', 'ChangeColor', 100, 255, lambda x: None)
times = 0
while True:
cv2.imshow('ChangeColor', img)
if newHSV != HSV:
if times == 0:
# HSV 的下界限
lower = np.array([HSV[0] - 7, 50, 50])
# HSV 的上界限
upper = np.array([HSV[0] + 7, 255, 255])
mask = cv2.inRange(img_hsv, lower, upper)
# 核
kernel = np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]], dtype=np.uint8)
# 腐蚀图像<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>除去干扰点
eroded = cv2.erode(mask, kernel, iterations=1)
# 膨胀图像
dilated = cv2.dilate(eroded, kernel, iterations=1)
times += 1
h = cv2.getTrackbarPos('H', 'ChangeColor')
# s = cv2.getTrackbarPos('S', 'ChangeColor')
# v = cv2.getTrackbarPos('V', 'ChangeColor')
for row in range(rows):
for col in range(cols):
if dilated[row, col] == 255:
img_hsv.itemset((row, col, 0), h)
# img_hsv.itemset((row, col, 1), s)
# img_hsv.itemset((row, col, 2), v)
img = cv2.cvtColor(img_hsv, cv2.COLOR_HSV2BGR)
cv2.imshow("ChangeColor", img)
else:
img = cv2.cvtColor(img_hsv, cv2.COLOR_HSV2BGR)
cv2.imshow("ChangeColor", img)
if cv2.waitKey(1) & 0xff == 27:
break
cv2.destroyAllWindows()