Python + OpenCv 更换照片背景颜色

        学习了贾志刚老师OpenCv的课程后,对人像抠图产生了兴趣,加之到照相馆换照片的背景颜色,每次都要一二十元,于是萌发了自己做一个小程序的想法。经过一年多在网上搜索的背景颜色素材,还有在照相馆里师傅们修图后的电子版照片,一共整理了14组背景颜色的数据。最近单位出差期间,晚上抽空将自己的想法变成了现实,很开心,现将代码发出来供朋友们借鉴。

        在此,首先要声明的是:背景颜色BGR的数据都是个人整理,仅供参考,若有侵犯您的知识产权,请联系我删帖,并请多多原谅!    背景图来自与豆包AI生成,同样真心希望也不要产生侵权等情况......  在这方面我真的不懂是否存在错误,确实好担心的......

        好了,闲话不说,直接上完整代码:(文件名:main_backColorPhoto.py  同文件夹 inPhoto 和文件夹 outPhoto 三项内容放到一个文件夹下。)

import cv2 as cv
import numpy as np
from tkinter import filedialog

# 0输入照片的纵向、1横向像素大小,2待选背景纵向像素长度,3c_int,
# 4选中背景代码,5选择背景计算开关,6B,7G,8R,9打开或保存标记,
# 10显示图像高宽像素限制,11HSV数值幅宽
rc_ext = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 500, 30]
hvs_set = [100, 120, 150, 150, 255, 255, 0, 0, 0]  # hvs 像素值设置

# 将RGB颜色转换为HSV颜色
def rgb_to_hsv(r, g, b):
    rgb = np.uint8([[[r, g, b]]])
    hsv = cv.cvtColor(rgb, cv.COLOR_BGR2HSV)
    return hsv[0][0]

# 图片上鼠标响应
def mouse_callback(event, x, y, flags, param):  # 图片上鼠标左键单击处取像素坐标
    if event == cv.EVENT_LBUTTONDOWN:  # 左键按下时触发该事件
        if y >= 5 and y <= rc_ext[2] and x >= (rc_ext[3] + 5) and x <= (rc_ext[3] + 25):
            rc_ext[4] = 1
            rc_ext[6] = 175
            rc_ext[7] = 141
            rc_ext[8] = 81
        if y >= 5 and y <= rc_ext[2] and x >= (rc_ext[3] + 30) and x <= (rc_ext[3] + 50):
            rc_ext[4] = 2
            rc_ext[6] = 212
            rc_ext[7] = 141
            rc_ext[8] = 91
        if y >= 5 and y <= rc_ext[2] and x >= (rc_ext[3] + 55) and x <= (rc_ext[3] + 75):
            rc_ext[4] = 3
            rc_ext[6] = 236
            rc_ext[7] = 179
            rc_ext[8] = 124
        if y >= 5 and y <= rc_ext[2] and x >= (rc_ext[3] + 80) and x <= (rc_ext[3] + 100):
            rc_ext[4] = 4
            rc_ext[6] = 245
            rc_ext[7] = 95
            rc_ext[8] = 34
        if y >= 5 and y <= rc_ext[2] and x >= (rc_ext[3] + 105) and x <= (rc_ext[3] + 125):
            rc_ext[4] = 5
            rc_ext[6] = 245
            rc_ext[7] = 134
            rc_ext[8] = 34
        if y >= 5 + rc_ext[2] and y <= rc_ext[2] * 2 and x >= (rc_ext[3] + 5) and x <= (rc_ext[3] + 25):
            rc_ext[4] = 6
            rc_ext[6] = 233
            rc_ext[7] = 167
            rc_ext[8] = 2
        if y >= 5 + rc_ext[2] and y <= rc_ext[2] * 2 and x >= (rc_ext[3] + 30) and x <= (rc_ext[3] + 50):
            rc_ext[4] = 7
            rc_ext[6] = 244
            rc_ext[7] = 188
            rc_ext[8] = 33
        if y >= 5 + rc_ext[2] and y <= rc_ext[2] * 2 and x >= (rc_ext[3] + 55) and x <= (rc_ext[3] + 75):
            rc_ext[4] = 8
            rc_ext[6] = 10
            rc_ext[7] = 10
            rc_ext[8] = 210
        if y >= 5 + rc_ext[2] and y <= rc_ext[2] * 2 and x >= (rc_ext[3] + 80) and x <= (rc_ext[3] + 100):
            rc_ext[4] = 9
            rc_ext[6] = 33
            rc_ext[7] = 37
            rc_ext[8] = 245
        if y >= 5 + rc_ext[2] and y <= rc_ext[2] * 2 and x >= (rc_ext[3] + 105) and x <= (rc_ext[3] + 125):
            rc_ext[4] = 10
            rc_ext[6] = 35
            rc_ext[7] = 50
            rc_ext[8] = 230
        if y >= 5 + rc_ext[2] * 2 and y <= rc_ext[2] * 3 and x >= (rc_ext[3] + 5) and x <= (rc_ext[3] + 25):
            rc_ext[4] = 11
            rc_ext[6] = 34
            rc_ext[7] = 142
            rc_ext[8] = 244
        if y >= 5 + rc_ext[2] * 2 and y <= rc_ext[2] * 3 and x >= (rc_ext[3] + 30) and x <= (rc_ext[3] + 50):
            rc_ext[4] = 12
            rc_ext[6] = 228
            rc_ext[7] = 229
            rc_ext[8] = 227
        if y >= 5 + rc_ext[2] * 2 and y <= rc_ext[2] * 3 and x >= (rc_ext[3] + 55) and x <= (rc_ext[3] + 75):
            rc_ext[4] = 13
            rc_ext[6] = 0
            rc_ext[7] = 0
            rc_ext[8] = 0
        if y >= 5 + rc_ext[2] * 2 and y <= rc_ext[2] * 3 and x >= (rc_ext[3] + 80) and x <= (rc_ext[3] + 100):
            rc_ext[4] = 14
            rc_ext[6] = 255
            rc_ext[7] = 255
            rc_ext[8] = 255
        if y >= 5 + rc_ext[2] * 2 and y <= rc_ext[2] * 3 and x >= (rc_ext[3] + 105) and x <= (rc_ext[3] + 125):
            rc_ext[4] = 15  # 自定义背景颜色

            num_b = input("请输入表示blue的数值: ")  # 提示用户输入blue数值
            try:  # 将输入的字符串转换为整数
                num_b = int(num_b)
                print("你输入的blue值是: ", num_b)
            except ValueError:
                print("输入的blue数值不是有效的数字,请重新输入。")

            num_g = input("请输入表示green的数值: ")  # 提示用户输入green数值
            try:  # 将输入的字符串转换为整数
                num_g = int(num_g)
                print("你输入的green值是: ", num_g)
            except ValueError:
                print("输入的green数值不是有效的数字,请重新输入。")

            num_r = input("请输入表示red的数值: ")  # 提示用户输入red数值
            try:  # 将输入的字符串转换为整数
                num_r = int(num_r)
                print("你输入的red值是: ", num_r)
            except ValueError:
                print("输入的red数值不是有效的数字,请重新输入。")

            if rc_ext[6] != num_b or rc_ext[7] != num_g or rc_ext[8] != num_r:  # 重复使用自定义背景色
                rc_ext[6] = num_b
                rc_ext[7] = num_g
                rc_ext[8] = num_r
                rc_ext[5] = 25

        if y >= 5 + rc_ext[2] * 3 and y <= rc_ext[2] * 4 - 5 and x >= (rc_ext[3] + 5) and x <= (rc_ext[3] + 62):
            rc_ext[9] = 16
        if y >= 5 + rc_ext[2] * 3 and y <= rc_ext[2] * 4 - 5 and x >= (rc_ext[3] + 69) and x <= (rc_ext[3] + 125):
            rc_ext[9] = 17

    # if event == cv.EVENT_RBUTTONDBLCLK:  # 右键双击
    # if enent == cv.EVENT_RBUTTONDOWN: # 右键单击

# 主程序-图片背景颜色更换
def backColorPhoto_demo():
    frame = cv.imread("./inPhoto/1724078664489.png")  # 不用摄像头,转到调入图片
    dst_out = frame.copy()  # 建立一个结果的图片
    cv.namedWindow("Change the background color of the color", cv.WINDOW_AUTOSIZE)  # 显示帧画面和结果画面的窗口
    r_int = 0  # 设置内部变量用于存储显示图像纵向像素值
    c_int = 0  # 设置内部变量用于存储显示图像横向像素值

    while (1):  # 手动操作更换人像背景
        if rc_ext[9] == 16:  # 选择一个图片
            file_path = filedialog.askopenfilename(filetypes=[('图片', '.jpg .jpeg .bmp .png'),  ('All Files', '*')]) 
            frame = cv.imread(file_path)
            dst_out = frame.copy()  # 建立一个结果的图片
            print("图片", file_path, "加载完成!")
            rc_ext[9] = 0  # 关闭本if循环
            rc_ext[4] = 0  # 打开程序初始化开关

        elif rc_ext[9] == 17:  # 保存结果
            cv.imwrite('./outPhoto/' + str(rc_ext[4]) +'.png', dst_out)
            print("图像保存在:/outPhoto/文件夹当中,文件名为:" + str(rc_ext[4]) + ".png")
            rc_ext[9] = 0  # 关闭本if循环

        elif rc_ext[4] == 0:  # 程序初始化
            rc_ext[0], rc_ext[1], channels = frame.shape  # 图像纵向、横向、深度像素值

            # 以下13行为本代码核心部分-2
            pix_val1 = frame[50, 50]
            pix_val2 = frame[50, (rc_ext[1] - 50)]
            hvs_set[6] = int(pix_val1[0] / 2 + pix_val2[0] / 2)
            hvs_set[7] = int(pix_val1[1] / 2 + pix_val2[1] / 2)
            hvs_set[8] = int(pix_val1[2] / 2 + pix_val2[2] / 2)
            hsv = rgb_to_hsv(hvs_set[6], hvs_set[7], hvs_set[8])
            # print(f"HSV value for red: H={hsv[0]}, S={hsv[1]}, V={hsv[2]}")
            hvs_set[0] = hsv[0] - rc_ext[11]
            hvs_set[3] = hsv[0] + rc_ext[11]
            hvs_set[1] = hsv[1] - rc_ext[11]
            hvs_set[4] = hsv[1] + rc_ext[11]
            hvs_set[2] = hsv[2] - rc_ext[11]
            hvs_set[5] = hsv[2] + rc_ext[11]

            if rc_ext[0] >= rc_ext[1]:  # 图像为高大于宽的纵向图像
                r_int = int((rc_ext[0] * rc_ext[10]) / rc_ext[1])  # 显示图像纵向像素值
                c_int = rc_ext[10]  # 显示图像横向像素值
            else:  # 图像为高小于宽的横向图像
                r_int = rc_ext[10]  # 显示图像纵向像素值
                c_int = int((rc_ext[1] * rc_ext[10]) / rc_ext[0])  # 显示图像横向像素值

            # 创建接近半黑底色的空图像
            dstImg = np.zeros((r_int, (c_int * 2 + 130), 3), dtype=np.uint8)  # 参数分别表示高度、宽度和通道数
            dstImg[:] = [80, 80, 80]  # 半黑色底
            rc_ext[2] = int(r_int / 4)
            rc_ext[3] = c_int
            dstImg[5:rc_ext[2], (c_int + 5):(c_int + 25), :] = [175, 141, 81]
            dstImg[5:rc_ext[2], (c_int + 30):(c_int + 50), :] = [212, 141, 91]
            dstImg[5:rc_ext[2], (c_int + 55):(c_int + 75), :] = [236, 179, 124]
            dstImg[5:rc_ext[2], (c_int + 80):(c_int + 100), :] = [245, 95, 34]
            dstImg[5:rc_ext[2], (c_int + 105):(c_int + 125), :] = [245, 134, 34]

            dstImg[5 + rc_ext[2]:rc_ext[2] * 2, (c_int + 5):(c_int + 25), :] = [233, 167, 2]
            dstImg[5 + rc_ext[2]:rc_ext[2] * 2, (c_int + 30):(c_int + 50), :] = [244, 188, 33]
            dstImg[5 + rc_ext[2]:rc_ext[2] * 2, (c_int + 55):(c_int + 75), :] = [10, 10, 210]
            dstImg[5 + rc_ext[2]:rc_ext[2] * 2, (c_int + 80):(c_int + 100), :] = [33, 37, 245]
            dstImg[5 + rc_ext[2]:rc_ext[2] * 2, (c_int + 105):(c_int + 125), :] = [35, 50, 230]

            dstImg[5 + rc_ext[2] * 2:rc_ext[2] * 3, (c_int + 5):(c_int + 25), :] = [34, 142, 244]
            dstImg[5 + rc_ext[2] * 2:rc_ext[2] * 3, (c_int + 30):(c_int + 50), :] = [228, 229, 227]
            dstImg[5 + rc_ext[2] * 2:rc_ext[2] * 3, (c_int + 55):(c_int + 75), :] = [0, 0, 0]
            dstImg[5 + rc_ext[2] * 2:rc_ext[2] * 3, (c_int + 80):(c_int + 100), :] = [255, 255, 255]

            dstImg[5 + rc_ext[2] * 3:rc_ext[2] * 4 - 5, (c_int + 5):(c_int + 62), :] = [0, 255, 255]
            dstImg[5 + rc_ext[2] * 3:rc_ext[2] * 4 - 5, (c_int + 69):(c_int + 125), :] = [0, 255, 0]

            frame_B = cv.resize(frame, (c_int, r_int))  # 此时参数为(cols,rows)
            dstImg[:, (c_int + 130):(c_int * 2 + 130), :] = frame_B[:, :, :]  # 
            rc_ext[4] = 20  # 关闭本if循环

        elif rc_ext[4] > 0 and rc_ext[4] < 16 and rc_ext[5] != rc_ext[4]:  # 根据鼠标点击选择进行背景更换
            if rc_ext[4] == 15:
                dstImg[5 + rc_ext[2] * 2:rc_ext[2] * 3, (c_int + 105):(c_int + 125), :] = [rc_ext[6], rc_ext[7], rc_ext[8]]

            # 以下14行为本代码核心部分-1
            hsvimg = cv.cvtColor(frame, cv.COLOR_BGR2HSV)
            lower_red = np.array([hvs_set[0], hvs_set[1], hvs_set[2]])
            upper_red = np.array([hvs_set[3], hvs_set[4], hvs_set[5]])
            hbimg = cv.inRange(hsvimg, lower_red, upper_red)  # 生成黑白人像抠图

            k = np.ones((5, 5), np.uint8)
            r = cv.morphologyEx(hbimg, cv.MORPH_CLOSE, k)

            print("正在更换背景," + str(rc_ext[4]) + "请稍后......")
            rows, cols, channels = dst_out.shape
            for i in range(rows):
                for j in range(cols):
                    if r[i, j] == 255:
                        dst_out[i, j] = (rc_ext[6], rc_ext[7], rc_ext[8])

            dst_A = cv.resize(dst_out, (c_int, r_int))  # 此时参数为(cols,rows)
            dstImg[:, :c_int, :] = dst_A[:, :, :]  # 加载换底后图像
            rc_ext[5] = rc_ext[4]  # 关闭本if循环
            print("背景更换完成!")

        cv.setMouseCallback("Change the background color of the color", mouse_callback, dstImg)  # 鼠标响应操作
        cv.imshow("Change the background color of the color", dstImg)  # 显示合成后图像

        c = cv.waitKey(1)  # 点击 ESC 退出程序
        if c == 27:
            break
    cv.destroyAllWindows()  # 关闭所有窗口并释放资源

# 程序入口
if __name__ == '__main__':
    backColorPhoto_demo()

# 参考资料
# https://blog.csdn.net/weixin_69553582/article/details/130327057
# 给照片换底色(python+opencv)
# https://www.5axxw.com/questions/content/dk51ik
# 在Python中,如何使用filedialog.askopenfilename()仅获取文件名本身

实例原图:(文件名: 1724078664489.png 豆包生成的,懒得改了。放到inPhoto文件夹下,主要为了文件夹里面的界面干净。)

e3944d486e7348c9bd137680ff29c356.png

 文档布局:

b0e5a2fe03014c479d859e826b69125e.jpeg

运行初始界面:

659f603adc97408db689192565c93ae9.jpeg

更换红色界面效果:

258f41073e1f4da783d42a5354572ac4.jpeg

更换自定义背景颜色:

27e36819820841dfab405e40e5d45cfc.jpeg

下面对程序相关情况说明如下:

        1.界面中间共四行带色块的鼠标点击区域,上面三行是背景选择色块,初始设置有14个色块可选择(从上到下,从左到右默认编号是1到14),点击哪个颜色就更换成哪个颜色的背景。第三行最右侧是自定义色块(默认编号15),点击后手工输入blue、green、red三个颜色值后进行背景颜色更换。  界面最下面一行两个色块,左边黄色的是打开图像功能,右侧绿色是保存图像功能(没有添加文字标识,自己知道是咋回事,也是懒得添加了),默认保存更改完成的照片到 outPhoto 文件夹里面,文件名为默认编号加 .png 。

        2.数组 rc_ext = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 500, 30] 的最后一项30是自己的经验值,表示的是 lower_red 和 upper_red 对应数值差的一半,在实例图片上效果算是最佳平衡点。用真人照片测试,这个数值放成40效果不错。用  lower_red = np.array([100, 120, 150]) 和 upper_red = np.array([150, 255, 255]) 对蓝色背景原图的背景更换没有问题,但对其他颜色原图背景则毫无用处。    数组倒数第二项 500 这个数值,控制显示效果上每个图片的高或宽不能超过500像素,朋友们可以视情况自行调整。

        3.此代码不同机器上更换背景需要的时间也不同。本文截图在Linux环境下生成,代码在Windows和Linux环境下都能运行。这个代码还有不完善的地方,比如:实例图更改背景后,左下角还没没被修改的地方;真人照片更换背景后,人的轮廓旁边一圈还有没被修改的地方;只能对单一背景颜色的图片修改,背景上下颜色不一致,以及有其他内容的时候,修改效果就很不好等等。我感觉 核心代码-1 的部分如果换成深度学习人像识别模块应该效果会更好,贾志刚老师的课程中有相关内容,但我还学得不精,浅尝辄止,非常惭愧,受自己知识和能力的限制,没有办法采用此种方法......  所以上面代码作为我自己的一个兴趣,就仅供参考了。

        4.在代码编写的过程中,参考了很多前辈的文章和经验,请原谅在此不再一一列出了,向各位前辈表示感谢!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值