1. 原因
在实际工作生活中,需要根据各种要求,提供不同底色的证件照电子版。常见的证照件底色有红底,蓝底,白底。
但我们在大部分情况只有其中一种底色的照片,就需要通过技术手段实现证件照换底色。但因为PS技术不到位,有瑕疵,通常处理的效果不理想。
本文基于opencv-python, 通过代码的方式实现y证件照底色的替换,同时针对相关的函数如图片缩放、翻转的使用方式进行学习。
2.图片来源及换底效果展示
2.1 图片来源说明
该图片来源于百度图片,如果侵权,请联系我删除!图片仅用于知识交流学习。
2.2 换底及翻转效果展示
将证件照的底色从蓝底换成白底,下面展示了图片的左右翻转
3. 源代码及相关知识介绍
3.1 相关知识介绍
3.1.1 cvtColor 颜色空间转换函数
cvtColor函数的定义是: def cvtColor(src, code, dst=None, dstCn=None): 用于将图片的颜色空间进行转换
注意: cv2.imread读取图片,是按BGR颜色空间读取的,不是最常见的RGB格式 ,在进行证件照底色变换时,需要将图片的颜色空间转换为HSV
即hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
3.1.2 inRange
inRange函数的定义是: def inRange(src, lowerb, upperb, dst=None):, 用于将图片中介于 lower/upper 之间的为变为白色,其余的变为黑色,效果如下:
函数参数说明:
- 第一个参数:src指的是原图
- 第二个参数:lowerb指的是图像中低于这个 lower_red 的值,图像值变为 0
- 第三个参数:upperb指的是图像中高于这个 upper_red 的值,图像值变为 0
- 第四个参数:dst 指输出,一般不指定。
注意: 在 lower_red~upper_red 之间的值变成 255, 注意:不同的证件照底色,inRange的lowerb,upperb需要根据HSV色彩空间表进行设置
3.1.3 HSV颜色模型
HSV(Hue, Saturation, Value)是根据颜色的直观特性由A. R. Smith在1978年创建的一种颜色空间, 也称六角锥体模型(Hexcone Model)。
是将RGB色彩空间中的点在倒圆锥体中的表示方法,HSV即:色调(H),饱和度(S),明度(V)
色调H
用角度度量,取值范围为0°~360°,从红色开始按逆时针方向计算,红色为0°,绿色为120°,蓝色为240°。它们的补色是:黄色为60°,青色为180°,紫色为300°;
饱和度S
饱和度S表示颜色接近光谱色的程度。一种颜色,可以看成是某种光谱色与白色混合的结果。其中光谱色所占的比例愈大,颜色接近光谱色的程度就愈高,颜色的饱和度也就愈高。饱和度高,颜色则深而艳。光谱色的白光成分为0,饱和度达到最高。通常取值范围为0%~100%,值越大,颜色越饱和。
明度V
明度表示颜色明亮的程度,对于光源色,明度值与发光体的光亮度有关;对于物体色,此值和物体的透射比或反射比有关。通常取值范围为0%(黑)到100%(白)。
HSV模型的三维表示从RGB立方体演化而来。设想从RGB沿立方体对角线的白色顶点向黑色顶点观察,就可以看到立方体的六边形外形。六边形边界表示色彩,水平轴表示纯度,明度沿垂直轴测量。HSV颜色空间可以用一个圆锥空间模型来描述。
圆锥的顶点处,V=0,H和S无定义,代表黑色。圆锥的顶面中心处V=max,S=0,H无定义,代表白色。见下图:
HSV色彩空间表: 在源代码中基于HSV色彩空间表的数据,放在colorLowerB、colorUpperB,用于根据指定的颜色,产生inRange函数的lowerb,upperb参数
3.2 源代码
证件照换底色,使用:changeImg = imageProcess.changeBackground(img1, 'blue', 'white'), 第二个参数是当前照片的底色,第三个照片是想向换成的底色
import cv2
import numpy as np
from PIL import ImageFont
class ImageProcess:
# 用于产生图片的mask遮罩,在cv2.inRange中使用, 效果是将证件底的底色变白色
colorLowerB = {
'red': np.array([156, 43, 46]),
'orange': np.array([11, 43, 46]),
'yellow': np.array([26, 43, 46]),
'green': np.array([35, 43, 46]),
'cyan': np.array([78, 43, 46]),
'blue': np.array([100, 43, 46]),
'purple': np.array([125, 43, 46]),
'black': np.array([0, 0, 0]),
'gray': np.array([0, 0, 46]),
'white': np.array([0, 0, 221]),
}
colorUpperB = {
'red': np.array([180, 255, 255]),
'orange': np.array([25, 255, 255]),
'yellow': np.array([34, 255, 255]),
'green': np.array([77, 255, 255]),
'cyan': np.array([99, 255, 255]),
'blue': np.array([124, 255, 255]),
'purple': np.array([155, 255, 255]),
'black': np.array([180, 255, 46]),
'gray': np.array([180, 43, 220]),
'white': np.array([180, 30, 255]),
}
# BGR通道
destColor = {
'red': (0, 0, 255),
'orange': (0, 165, 255),
'yellow': (0, 255, 255),
'green': (0, 128, 0),
'blue': (255, 0, 0),
'cyan': (255, 255, 0),
'purple': (128, 0, 128),
'black': (0, 0, 0),
'gray': (128, 128, 128),
'white': (255, 255, 255),
}
def __init__(self, scale=1):
self.scale = scale
def changeBackground(self, img, originColor='white', changeColor='blue'):
originImg = self.copyImg(img)
rows, cols, channels = img.shape
# 图片转换为灰度图
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# 图片的二值化处理
lowerB = ImageProcess.colorLowerB.get(originColor)
upperB = ImageProcess.colorUpperB.get(originColor)
mask = cv2.inRange(hsv, lowerB, upperB)
# 针对图片进行腐蚀膨胀
kernel = np.ones((3, 3), np.uint8)
# kernel = None
erode = cv2.erode(mask, kernel, iterations=1)
dilate = cv2.dilate(erode, kernel, iterations=1)
# 遍历每个像素点,进行颜色的替换
print(type(ImageProcess.destColor))
tmpColor = ImageProcess.destColor.get(changeColor)
print(tmpColor)
for i in range(rows):
for j in range(cols):
if dilate[i, j] == 255: # 像素点为255表示的是白色,我们就是要将白色处的像素点,替换为红色
img[i, j] = tmpColor # 此处替换颜色,为BGR通道,不是RGB通道
return img
def copyImg(self, img):
'''
复制图像
:param img:
:return:
'''
return cv2.cvtColor(img, cv2.NORMAL_CLONE)
def flipImg(self, img):
'''
翻转图像
:param img:
:return:
'''
rows, cols = img.shape[:2]
mapx = np.zeros(img.shape[:2], np.float32)
mapy = np.zeros(img.shape[:2], np.float32)
for i in range(rows):
for j in range(cols):
mapx.itemset((i, j), cols - 1 - j)
mapy.itemset((i, j), i)
return cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR)
def showMultiPics(self, imgArray):
rows = len(imgArray)
cols = len(imgArray[0])
# 第一张图片的宽高
width = imgArray[0][0].shape[1]
height = imgArray[0][0].shape[0]
for x in range(0, rows):
for y in range(0, cols):
# 遍历,如果是第一幅图像
if imgArray[x][y].shape[:2] == imgArray[0][0].shape[:2]:
imgArray[x][y] = cv2.resize(imgArray[x][y], (0, 0), None, self.scale, self.scale)
# 将其他矩阵变换为与第一幅图像相同大小,缩放比例为scale
else:
imgArray[x][y] = cv2.resize(imgArray[x][y],
(width, height), None, self.scale,
self.scale)
# 如果图像是灰度图,将其转换成彩色显示
if len(imgArray[x][y].shape) == 2:
imgArray[x][y] = cv2.cvtColor(imgArray[x][y], cv2.COLOR_GRAY2BGR)
# 创建一个空白画布,与第一张图片大小相同
imgBlank = np.zeros((height, width, 3), np.uint8)
hor = [imgBlank] * rows
for x in range(0, rows):
# 将元组里第x个列表水平排列
hor[x] = np.hstack(imgArray[x])
ver = np.vstack(hor) # 将不同列表垂直拼接
return ver
if __name__ == '__main__':
imageProcess = ImageProcess()
img1 = cv2.imread('zjz1.jpg')
img2 = cv2.imread('zjz1.jpg')
#缩放图片
img1 = cv2.resize(img1, None, fx=0.5, fy=0.5)
img2 = cv2.resize(img1, None, fx=0.5, fy=0.5)
originImg1 = imageProcess.copyImg(img1)
originImg2 = imageProcess.copyImg(img2)
flipImg = imageProcess.flipImg(img2)
changeImg = imageProcess.changeBackground(img1, 'blue', 'white')
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(originImg1, 'origin-pic', (0, 15), font, 0.5, (0, 0, 0), 1)
cv2.putText(originImg2, 'origin-pic', (0, 15), font, 0.5, (0, 0, 0), 1)
cv2.putText(flipImg, 'pic flip', (0, 15), font, 0.5, (0, 0, 0), 1)
cv2.putText(changeImg, 'pic change background color', (0, 15), font, 0.5, (0, 0, 0), 1)
cv2.imshow("show multi pics", imageProcess.showMultiPics(([originImg1, changeImg], [originImg2, flipImg])))
# 窗口等待的命令,0表示无限等待
cv2.waitKey(0)
cv2.destroyAllWindows()
3.3 代码存在的问题
如果证件照的底色在人脸,头发或衣服等区域存在,则这些地方也会被换颜色,造成效果不好。