欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

使用cv为批量图片自定义添加水印logo

程序员文章站 2022-06-15 18:50:33
一、使用cv为批量图片自定义添加文字/水印logo需求:需要大量带有各式水印、二维码、自定义文字的图片,水印、二维码 大小不固定、位置不固定、模糊程度不固定功能:自定义为图片添加 图片logo/文字 1、添加水印 ① 随机选取水印logo 添加的位置可以随机插入【“四角”,“去心域”】,也可以指定固定位置插入水印logo ② 可以自定义logo水印插入 “四角”、“去心域”的大小占比 ③ logo水印大小、模糊程度自定义 2、添加文...

一、使用cv为批量图片自定义添加文字/水印logo

需求:需要大量带有各式水印、二维码、自定义文字的图片,水印、二维码 大小不固定、位置不固定、模糊程度不固定
功能:自定义为图片添加 图片logo/文字
    1、添加水印
        ① 随机选取水印logo 添加的位置可以随机插入【“四角”,“去心域”】,也可以指定固定位置插入水印logo
        ② 可以自定义logo水印插入 “四角”、“去心域”的大小占比
        ③ logo水印大小、模糊程度自定义
    2、添加文本
        。。。
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import cv2
import os
import numpy as np
import time
import argparse
from tqdm import tqdm


class AddLogoText():
    def __init__(self,source_imgs_dir,
                 wm_imgs_dir=None,
                 text_path=None,
                 report_dir=None,
                 alpha=0.3,
                 position=None,
                 random=True,
                 insert_type='move_heart',
                 type_ = 'img',*args,**kwargs):

        self.source_imgs_dir = source_imgs_dir # 原图片的目录路径,str
        self.wm_imgs_dir = wm_imgs_dir # logo或者二维码的目录路径,logo/二维码图片不可是灰度图,str
        self.text_path = text_path # 图片写文字的文件路径
        self.report_dir = report_dir # 图片合成后输入的文件夹
        self.alpha = alpha # logo 插入的模糊度,1.0 表示完全不模糊
        self.position = position # loge、二维码插入的像素位置元祖,使用时需要将 random 置为 False,tuple
        self.random = random # 是否随机插入,开启时position无效 ,bool
        if not random:
            assert position is not None
        self.insert_type = insert_type # random = True时有效,随机插入的类型;'move_heart' 随机插入至原图片去心域;'four_corn' 随机插入至原图四个角的位置
        assert insert_type in ['move_heart','four_corn']
        self.type_ = type_ # "img" :在图片中 插入 loge , "text":在图片中插入文本
        assert type_ in ['img','text']

        # 可变参数
        self.size_height = 70  # logo/二维码 resize 插入原图片时的高度的像素大小,int
        self.percent=0.33  # logo/二维码插入图片时 不能越界【左上 左下 右上 右下】 percent占比的位置 【仅仅在 随机插入 即random=True 有效 】,float
        assert 0.0 < self.percent < 0.5

    def read_image(self,fpath):
        """
        cv 加载图片,返回图片的数组,如果图片为 灰度图,flag = False,否则返回 true
        :return:
        """
        flag = True
        img_bgr = cv2.imread(fpath)
        
        if img_bgr is None:
            raise Exception("图片读取失败,可能图片路径中存在中文字符或者文件本身非cv可以读取为图片的数据类型,图片路径为{}".format(fpath))
            
        dim = img_bgr.ndim
        if dim == 2:
            flag = False
        return img_bgr,flag

    def load_images(self,source_dir):
        """
        给定文件夹,加载数据
        :return:
        """
        assert source_dir is not None

        father_dir_paths = []
        file_paths = []
        for root, dirs, files in os.walk(source_dir):
            for file in files:
                # 获取文件所属目录
                father_dir_paths.append(root)
                # 获取文件路径
                file_paths.append(os.path.join(root, file))
        return file_paths, father_dir_paths

    def time_stamp_str(self,str_):
        """
        为了保证字符串的唯一性,给字符串末尾添加时间戳
        :param str_:
        :return:
        """
        stamp_ = str(time.time()).replace('.','')
        str_ = str(str_) + stamp_
        return str_

    def cv_save_img(self,array,img_name,report_dir):
        """
        用 cv2 将 三通道 BGR array 保证为 img_name,保存在 report_dir中
        :param array: bgr 图片的数组
        :param img_name: 图片的名字,CV2文件中不可以有中文
        :param report_dir: 图片保存到的目录中, CV2 路径中不可以有中文
        :return:
        """
        assert report_dir is not None
        # 判断目录是否存在
        if not os.path.exists(report_dir):
            os.makedirs(report_dir)

        img_path = os.path.join(report_dir,img_name)
        cv2.imwrite(img_path,array)

    # TODO 核心函数
    def add_logo_single_img(self,source_img_fpath,
                              wm_img_fpath,
                              alpha=0.6,
                              size_height=100,
                              percent=0.3,
                              position=None,
                              random=True,
                              insert_type='move_heart'):

        """
        将一张图片 与 loge 通过cv2.addWeighted 的方式进行融合,本函数不处理原图为灰度图的情况
        tip:融合的特点,loge/二维码浅色的位置会随着深色的位置一起淡化或加深【如果想要loge/二维码深色或浅色的部分,其他舍弃掉需要用到 cv位运算 】

        :param source_img_path: 原图片的文件路径,str
        :param wm_img_path: logo或者二维码的文件路径,logo/二维码图片不可是灰度图,str
        :param alpha: logo 加入图片的模糊状态,最大 1.0,即不做模糊处理,float
        :param size_height: logo/二维码 resize 插入原图片时的高度的像素大小,int
        :param percent: logo/二维码插入图片时 不能越界【左上 左下 右上 右下】 percent占比的位置 【仅仅在 随机插入 即random=True 有效 】,float
        :param position: logo 插入的 左上角的像素位置,tuple
        :param random: 插入位置是否随机,bool
        :param insert_type: # random = True时有效,随机插入的类型;'move_heart' 随机插入至原图片去心域;'four_corn' 随机插入至原图四个角的位置
        :return: s_img,position; s_img:插入logo后图片的 bgr数组,position 插入logo的 左上角定点的 位置
        """

        if not random:
            assert position is not None
        # 加载 img logo图
        s_img,s_flag = self.read_image(source_img_fpath)
        s_img_h,s_img_w = s_img.shape[:2]
        wm_img,wm_flag = self.read_image(wm_img_fpath)
        tmp1_h, tmp1_w = wm_img.shape[:2]

        # bgr 通道执行
        if s_flag and wm_flag:
            # 将 logo/二维码 resize
            wm_logo = cv2.resize(wm_img,dsize=(int(tmp1_w/(tmp1_h*1.0/size_height)),size_height),interpolation=cv2.INTER_LINEAR)
            wm_logo_h,wm_logo_w = wm_logo.shape[:2]
            # print(s_img_h,s_img_w,wm_logo_h,wm_logo_w)

            # 判断 logo、二维码的尺寸小于图片尺寸
            if (wm_logo_h < s_img_h) and (wm_logo_w < s_img_w):

                # 如果是随机插入图片,需要生成随机插入点 , 否则 使用position的地址作为图片的插入点
                if random:
                    if insert_type == 'four_corn':
                        # TODO 生成随机的 '四角'的顶点
                        percent_h_top = int(s_img_h * 1.0 * percent)
                        percent_h_tail = int(s_img_h * 1.0 * (1-percent))
                        percent_w_l = int(s_img_w * 1.0 * percent)
                        percent_w_r = int(s_img_w * 1.0 * (1-percent))

                        if np.random.uniform() < 0.5: # 如果随机数小于 0.5 判断顶点在上方,否则在下方
                            ran_h = np.random.randint(low=0,high=percent_h_top)
                        else:
                            ran_h = np.random.randint(low=percent_h_tail, high=s_img_h)
                        if np.random.uniform() < 0.5: # 如果随机数小于 0.5 判断顶点在左方,否则在右方
                            ran_w = np.random.randint(low=0,high=percent_w_l)
                        else:
                            ran_w = np.random.randint(low=percent_w_r, high=s_img_w)
                        position = (ran_h,ran_w)
                    elif insert_type == 'move_heart':
                        # TODO 生成随机的 去除'心域'的顶点
                        percent_h_top = int(s_img_h * 1.0 * percent)
                        percent_h_tail = int(s_img_h * 1.0 * (1 - percent))
                        percent_w_l = int(s_img_w * 1.0 * percent)
                        percent_w_r = int(s_img_w * 1.0 * (1 - percent))

                        ran_h = np.random.randint(low=0,high=s_img_h)
                        if percent_h_tail > ran_h >= percent_h_top: # 当 高度h 落在中间的 区域时,去除 '心域'
                            if np.random.uniform() < 0.5:  # 如果改值小于 0.5,选择 “左边”,否则选择 “右边”
                                ran_w = np.random.randint(low=0,high=percent_w_l)
                            else:
                                ran_w = np.random.randint(low=percent_w_r,high=s_img_w)
                        else:
                            ran_w = np.random.randint(low=0,high=s_img_w)
                        position = (ran_h,ran_w)

                # 判断 position 插入后是否越界,如果越界,将logo、二维码左移/上移 至极限位置
                start_h,start_w = position
                if start_h + wm_logo_h > s_img_h:
                    start_h = s_img_h - wm_logo_h
                if start_w + wm_logo_w > s_img_w:
                    start_w = s_img_w - wm_logo_w
                position = (start_h,start_w)

                # 将图片的数据 深拷贝一份,用于 cv2.addWeighted
                s_img_copy = s_img.copy()
                # 将loge、二维码数据嵌入
                s_img_copy[start_h:start_h + wm_logo_h , start_w:start_w + wm_logo_w ,...] = wm_logo
                # 图片融合
                s_img = cv2.addWeighted(s_img,1.0-alpha,s_img_copy,alpha,gamma=0)

                return s_img,position

            else:
                print('当前logo/二维码尺寸为h:{} w:{},原图尺寸太小,不做处理'.format(wm_logo_h,wm_logo_w))
                return s_img,False

        else: # 原图为灰度图
            print('原图为灰度图,不做处理')
            return s_img,False

    def add_logo_allImgs(self):
        """
        通过 cv2.addWeighted 的方式将 所有的源图片 与 随机选择的logo 进行融合
        :return:
        """
        source_img_paths,_ = self.load_images(self.source_imgs_dir)
        wm_img_paths,_ = self.load_images(self.wm_imgs_dir)

        print('logo 嵌入中,一共{}张源图片,{}张logo图片'.format(len(source_img_paths),len(wm_img_paths)))
        for idx,img in tqdm(enumerate(source_img_paths)):
            # 随机取一个 二维码/loge图片
            wm_img_fpath = np.random.choice(wm_img_paths)
            img_bgr,_ = self.add_logo_single_img(source_img_fpath=img,
                                       wm_img_fpath=wm_img_fpath,
                                       alpha=self.alpha,
                                       size_height=self.size_height,
                                       percent=self.percent,
                                       position=self.position,
                                       random=self.random,
                                       insert_type=self.insert_type)

            img_name = self.time_stamp_str(str( idx+1) + '_' ) + '.jpg'
            # 保存图片
            self.cv_save_img(img_bgr,img_name,self.report_dir)

    def add_text_single_img(self):
        """
        给一张图片添加文本
        :return:
        """

    def add_text_allImgs(self):
        """
        给所有的图片添加文本
        :return:
        """
        pass

    def run(self):
        if self.type_ == 'img':
            print('当前模式为嵌入logo的模式!')
            self.add_logo_allImgs()
        elif self.type_ == "text":
            print('当前模式为嵌入文本的模式!')
            self.add_text_allImgs()


if __name__ == '__main__':

    """
    # 回调函数
    def str2bool(v):
        if isinstance(v, bool):
            return v
        if v.lower() in ('yes', 'true', 't', 'y', '1'):
            return True
        elif v.lower() in ('no', 'false', 'f', 'n', '0'):
            return False
        else:
            raise argparse.ArgumentTypeError('Boolean value expected.')


    def str2tuple(v):
        str_tuple = ''.join(v)
        return eval(str_tuple)


    parser = argparse.ArgumentParser()
    parser.add_argument('--source_dir', help='source imgs', type=str)
    parser.add_argument('--wm_dir', help='logo imgs', type=str)
    parser.add_argument('--text_path', help='text file path', type=str)
    parser.add_argument('--report_dir', help='report imgs path', type=str)
    parser.add_argument('--type_', help='insert img or text', type=str, default='img')
    parser.add_argument('--alpha', help='alpha', type=float, default=0.3)
    parser.add_argument('--position', help='position tuple logo or text, eg: (20,20)', type=str2tuple, default=(0, 0))
    parser.add_argument('--insert_type', help='insert logo or text type', type=str, default='move_heart')
    parser.add_argument('--random', help='random YES or NO', type=str2bool, default='1')

    args = parser.parse_args()
    source_dir = args.source_dir
    wm_dir = args.wm_dir
    text_path = args.text_path
    report_dir = args.report_dir
    type_ = args.type_
    alpha = args.alpha
    position = args.position
    print(position, type(position))
    random = args.random
    insert_type = args.insert_type


    执行脚本:
	python add_logo_text.py --source_dir C:\Users\******\Desktop\LanJunTools\datas\source_imgs --wm_dir C:\Users\******\Desktop\LanJunTools\datas\QR_logos --text_path NO --report_dir ./report_datas  --type_ img  --alpha 0.3  --position (20,20)  --insert_type move_heart --random 1

    python add_logo_text.py --source_dir C:\Users\******\Desktop\LanJunTools\datas\source_imgs --wm_dir C:\Users\******\Desktop\LanJunTools\datas\QR_logos --text_path NO --report_dir ./report_datas  --type_ img  --alpha 0.3  --position (20,20)  --insert_type move_heart --random 0
    """

    source_dir = './datas/source_imgs'
    wm_dir = './datas/QR_logos'
    text_path = None
    report_dir = './report_datas'
    type_ = 'img'
    alpha = 0.3
    position = None
    random = True
    insert_type = 'move_heart'

    obj = AddLogoText(source_imgs_dir=source_dir,
                 wm_imgs_dir=wm_dir,
                 text_path=text_path,
                 report_dir= report_dir,
                 alpha=alpha,
                 position=position,
                 random=random,
                 insert_type=insert_type,
                 type_ = type_)

    obj.run() 

本文地址:https://blog.csdn.net/qq_16555103/article/details/108856674