1. 原因

在实际工作生活中,需要根据各种要求,提供不同底色的证件照电子版。常见的证照件底色有红底,蓝底,白底。

但我们在大部分情况只有其中一种底色的照片,就需要通过技术手段实现证件照换底色。但因为PS技术不到位,有瑕疵,通常处理的效果不理想。

本文基于opencv-python,  通过代码的方式实现y证件照底色的替换,同时针对相关的函数如图片缩放、翻转的使用方式进行学习。

2.图片来源及换底效果展示

2.1 图片来源说明

该图片来源于百度图片,如果侵权,请联系我删除!图片仅用于知识交流学习。

java opencv照片换底色 opencv背景替换_opencv

2.2 换底及翻转效果展示

将证件照的底色从蓝底换成白底,下面展示了图片的左右翻转

java opencv照片换底色 opencv背景替换_计算机视觉_02

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色彩空间表进行设置

java opencv照片换底色 opencv背景替换_opencv_03

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无定义,代表白色。见下图:

java opencv照片换底色 opencv背景替换_opencv_04

                                 

java opencv照片换底色 opencv背景替换_颜色空间_05

HSV色彩空间表: 在源代码中基于HSV色彩空间表的数据,放在colorLowerB、colorUpperB,用于根据指定的颜色,产生inRange函数的lowerb,upperb参数

java opencv照片换底色 opencv背景替换_java opencv照片换底色_06

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 代码存在的问题

    如果证件照的底色在人脸,头发或衣服等区域存在,则这些地方也会被换颜色,造成效果不好。