【Python自动化】为某个目录下的图片添加边框

功能介绍

电脑启动时,自动执行该脚本,动态监听某个目录,对该目录下(包含子目录)未添加过边框的图片,添加边框(可设置边框宽度和颜色)

功能实现

图片处理框架选择

目前Python处理图片的两个比较流行的库:pillow和OpenCV,本文采用两种方式实现,顺便比较一下两个库处理图片的性能差异

1.使用pillow实现
import os
from PIL import Image

class ImageBorderHandler:

    def __init__(self, source_folder: str, output_folder: str):
        self.source_folder = source_folder
        self.output_folder = output_folder
        if not os.path.exists(output_folder):
            os.mkdir(output_folder)

    def _get_source_file_abs_path(self, file_name: str):
        return os.path.join(self.source_folder, file_name)

    def _get_output_file_abs_path(self, file_name: str):
        return os.path.join(self.output_folder, file_name)
	
    def _prepare_output_abs_path(self, file_name: str):
        """
        检查输出路径,如果父级目录不存在,自动创建
        :param file_name: 待输出文件,可能含有父级目录
        """
        full_path = os.path.join(self.output_folder, file_name)
        if not os.path.exists(full_path):
            dir_item = self.output_folder
            for path_item in file_name.split(os.path.sep)[: -1]:
                dir_item += f'{os.path.sep}{path_item}'
                if not os.path.exists(dir_item):
                    os.mkdir(dir_item)

    def border_image(self, image_file_name: str, border_size=3, border_color=(212, 212, 212)):
        """
        为图片添加边框
        :param image_file_name: 待处理图片文件名
        :param border_size: 边框宽度,默认值3px
        :param border_color: 边框颜色,默认值为浅灰色
        :return: None
        """
        if not os.path.exists(self._get_source_file_abs_path(image_file_name)):
            return

        self._pillow_border_image(image_file_name, border_size=border_size, border_color=border_color)

    def _pillow_border_image(self, image_file_name: str, border_size: int, border_color):
        origin_image = Image.open(self._get_source_file_abs_path(image_file_name))
        (w, h) = origin_image.size

        # 宽度为原始图片尺寸 + 两个border的尺寸
        border_image = Image.new('RGB', (w + 2 * border_size, h + 2 * border_size), border_color)
        border_image.paste(origin_image, (border_size, border_size))
       
        self._prepare_output_abs_path(image_file_name)
        border_image.save(self._get_output_file_abs_path(image_file_name))
2.使用OpenCV实现
import os
import cv2

class ImageBorderHandler:

    def __init__(self, source_folder: str, output_folder: str):
        self.source_folder = source_folder
        self.output_folder = output_folder
        if not os.path.exists(output_folder):
            os.mkdir(output_folder)

    def _get_source_file_abs_path(self, file_name: str):
        return os.path.join(self.source_folder, file_name)

    def _get_output_file_abs_path(self, file_name: str):
        return os.path.join(self.output_folder, file_name)

    def _prepare_output_abs_path(self, file_name: str):
        """
        检查输出路径,如果父级目录不存在,自动创建
        :param file_name: 待输出文件,可能含有父级目录
        """
        full_path = os.path.join(self.output_folder, file_name)
        if not os.path.exists(full_path):
            dir_item = self.output_folder
            for path_item in file_name.split(os.path.sep)[: -1]:
                dir_item += f'{os.path.sep}{path_item}'
                if not os.path.exists(dir_item):
                    os.mkdir(dir_item)

    def border_image(self, image_file_name: str, border_size=3, border_color=(212, 212, 212)):
        """
        为图片添加边框
        :param image_file_name: 待处理图片文件名
        :param border_size: 边框宽度,默认值3px
        :param border_color: 边框颜色,默认值为浅灰色
        :return: None
        """
        if not os.path.exists(self._get_source_file_abs_path(image_file_name)):
            return

        self._open_cv_border_image(image_file_name, border_size=border_size, border_color=border_color)

    def _pillow_border_image(self, image_file_name: str, border_size: int, border_color):
        origin_image = Image.open(self._get_source_file_abs_path(image_file_name))
        (w, h) = origin_image.size

        # 宽度为原始图片尺寸 + 两个border的尺寸
        border_image = Image.new('RGB', (w + 2 * border_size, h + 2 * border_size), border_color)
        border_image.paste(origin_image, (border_size, border_size))

		self._prepare_output_abs_path(image_file_name)
        border_image.save(self._get_output_file_abs_path(image_file_name))

    def _open_cv_border_image(self, image_file_name: str, border_size: int, border_color: tuple):
        origin_image = cv2.imread(self._get_source_file_abs_path(image_file_name))
        bordered_image = cv2.copyMakeBorder(origin_image,
                                            border_size, border_size, border_size, border_size,
                                            cv2.BORDER_CONSTANT, value=border_color)

		self._prepare_output_abs_path(image_file_name)
        cv2.imwrite(self._get_output_file_abs_path(image_file_name), bordered_image)

监听指定目录

自动在目标目录下创建一个隐藏文件processed_cache.txt(以.开头),存放已经添加过边框的图片文件名。该示例中监听的目录为:/Users/jason315/Desktop/test-image,将添加边框后的图片保存到/Users/jason315/Desktop/image-bordered目录下

关键代码如下

import os
import cv2
import time
from PIL import Image

class ImageBorderHandler:
	def __init__(self, source_folder: str, output_folder: str):
	   self.processed_cache_file = '.processed_cache.txt'
	   self.source_folder = source_folder
	   self.output_folder = output_folder
	
	   # 文件初始化
	   if not os.path.exists(output_folder):
	       os.mkdir(output_folder)
	   if not os.path.exists(self._get_processed_cache_abs_path()):
	       with open(self._get_processed_cache_abs_path(), 'w') as f:
	           f.flush()
	
	   # processed_cache初始化
	   self.processed_cache = set()
	   with open(self._get_processed_cache_abs_path(), 'r') as f:
	       for line in f.readlines():
	           self.processed_cache.add(line.replace('\n', ''))
       
	@staticmethod
    def _log_with_time(log_str):
        """
        打印日志, 格式[yyyy-MM-dd HH:mm:ss] 日志内容
        """
        print(f'[{time.strftime("%Y-%m-%d %H:%M:%S")}] {log_str}')

	@staticmethod
    def _is_expected_image_file(file_name: str):
        suffix = file_name[file_name.rindex('.') + 1:]
        return suffix.lower() in ['png', 'jpeg', 'jpg']

    def _inner_process_file(self, the_file: str, processed_file_list: list[str]):
        file_name = the_file[the_file.rindex(os.path.sep) + 1:]
        if os.path.isdir(the_file):
            # 递归处理文件夹
            for file_item in os.listdir(the_file):
                self._inner_process_file(f'{the_file}/{file_item}', processed_file_list)
            return
        elif file_name.startswith('.') or not self._is_expected_image_file(file_name):
            return

        # 处理还未添加边框的图片
        path_under_source_folder = the_file.split(self.source_folder)[1]
        if path_under_source_folder.startswith(os.path.sep):
            path_under_source_folder = path_under_source_folder[1:]
        if path_under_source_folder not in self.processed_cache:
            self.border_image(path_under_source_folder)
            self.processed_cache.add(path_under_source_folder)
            processed_file_list.append(f'{path_under_source_folder}\n')

    def _inner_process(self):
        processed_file_list = []
        self._inner_process_file(self.source_folder, processed_file_list)
        if len(processed_file_list) > 0:
            with open(self._get_processed_cache_abs_path(), 'a') as f:
                f.writelines(processed_file_list)
                f.flush()
            self._log_with_time(f'add borders for {len(processed_file_list)} images')

    def start_process(self):
        """
        开始监听目标文件夹,每隔5秒处理1次
        :return:
        """
        self._log_with_time('the ImageBorderHandler starts working ...')
        while(True):
            self._inner_process()
            time.sleep(5)

if __name__ == '__main__':
    image_border_handler = ImageBorderHandler('/Users/jason315/Desktop/test-image', '/Users/jason315/Desktop/image-bordered')
    image_border_handler.start_process()

启动后添加一些图片到test-image目录下,观察输出日志
该图片已自动添加边框

配置开机自启动

新建文件image-border-auto-start.sh,编写脚本,调用Python启动main方法

cd /Users/jason315/Practice/pythonspace/jason-tool/com/jasonidea/tools
python ./image_border_handler.py

本文以MacOS设置开机自启动为例,Windows系统请参考这篇博客

1.修改自启动脚本运行权限
chmod 777 image-border-auto-start.sh
2.右键点击文件,修改打开方式

修改文件启动方式为Terminal(终端)
在这里插入图片描述
在这里插入图片描述

3.设置 > 用户与群组 > 登录项

添加自启动脚本
在这里插入图片描述

完整代码

image_border_handler.py

import os
import cv2
import time
from PIL import Image


class ImageBorderHandler:

    def __init__(self, source_folder: str, output_folder: str):
        self.processed_cache_file = '.processed_cache.txt'
        self.source_folder = source_folder
        self.output_folder = output_folder

        # 文件初始化
        if not os.path.exists(output_folder):
            os.mkdir(output_folder)
        if not os.path.exists(self._get_processed_cache_abs_path()):
            with open(self._get_processed_cache_abs_path(), 'w') as f:
                f.flush()

        # processed_cache初始化
        self.processed_cache = set()
        with open(self._get_processed_cache_abs_path(), 'r') as f:
            for line in f.readlines():
                self.processed_cache.add(line.replace('\n', ''))

    def _get_processed_cache_abs_path(self):
        return os.path.join(self.source_folder, self.processed_cache_file)

    def _get_source_file_abs_path(self, file_name: str):
        return os.path.join(self.source_folder, file_name)

    def _get_output_file_abs_path(self, file_name: str):
        return os.path.join(self.output_folder, file_name)

    def _prepare_output_abs_path(self, file_name: str):
        """
        检查输出路径,如果父级目录不存在,自动创建
        :param file_name: 待输出文件,可能含有父级目录
        """
        full_path = os.path.join(self.output_folder, file_name)
        if not os.path.exists(full_path):
            dir_item = self.output_folder
            for path_item in file_name.split(os.path.sep)[: -1]:
                dir_item += f'{os.path.sep}{path_item}'
                if not os.path.exists(dir_item):
                    os.mkdir(dir_item)

    @staticmethod
    def _log_with_time(log_str):
        """
        打印日志, 格式[yyyy-MM-dd HH:mm:ss] 日志内容
        """
        print(f'[{time.strftime("%Y-%m-%d %H:%M:%S")}] {log_str}')

    def border_image(self, image_file_name: str, border_size=3, border_color=(212, 212, 212)):
        """
        为图片添加边框
        :param image_file_name: 待处理图片文件名
        :param border_size: 边框宽度,默认值3px
        :param border_color: 边框颜色,默认值为浅灰色
        :return: None
        """
        if not os.path.exists(self._get_source_file_abs_path(image_file_name)):
            return

        self._open_cv_border_image(image_file_name, border_size=border_size, border_color=border_color)

    def _pillow_border_image(self, image_file_name: str, border_size: int, border_color):
        origin_image = Image.open(self._get_source_file_abs_path(image_file_name))
        (w, h) = origin_image.size

        # 宽度为原始图片尺寸 + 两个border的尺寸
        border_image = Image.new('RGB', (w + 2 * border_size, h + 2 * border_size), border_color)
        border_image.paste(origin_image, (border_size, border_size))

        self._prepare_output_abs_path(image_file_name)
        border_image.save(self._get_output_file_abs_path(image_file_name))

    def _open_cv_border_image(self, image_file_name: str, border_size: int, border_color: tuple):
        origin_image = cv2.imread(self._get_source_file_abs_path(image_file_name))
        bordered_image = cv2.copyMakeBorder(origin_image,
                                            border_size, border_size, border_size, border_size,
                                            cv2.BORDER_CONSTANT, value=border_color)

        self._prepare_output_abs_path(image_file_name)
        cv2.imwrite(self._get_output_file_abs_path(image_file_name), bordered_image)

    @staticmethod
    def _is_expected_image_file(file_name: str):
        suffix = file_name[file_name.rindex('.') + 1:]
        return suffix.lower() in ['png', 'jpeg', 'jpg']

    def _inner_process_file(self, the_file: str, processed_file_list: list[str]):
        file_name = the_file[the_file.rindex(os.path.sep) + 1:]
        if os.path.isdir(the_file):
            # 递归处理文件夹
            for file_item in os.listdir(the_file):
                self._inner_process_file(f'{the_file}/{file_item}', processed_file_list)
            return
        elif file_name.startswith('.') or not self._is_expected_image_file(file_name):
            return

        # 处理还未添加边框的图片
        path_under_source_folder = the_file.split(self.source_folder)[1]
        if path_under_source_folder.startswith(os.path.sep):
            path_under_source_folder = path_under_source_folder[1:]
        if path_under_source_folder not in self.processed_cache:
            self.border_image(path_under_source_folder)
            self.processed_cache.add(path_under_source_folder)
            processed_file_list.append(f'{path_under_source_folder}\n')

    def _inner_process(self):
        processed_file_list = []
        self._inner_process_file(self.source_folder, processed_file_list)
        if len(processed_file_list) > 0:
            with open(self._get_processed_cache_abs_path(), 'a') as f:
                f.writelines(processed_file_list)
                f.flush()
            self._log_with_time(f'add borders for {len(processed_file_list)} images')

    def start_process(self):
        """
        开始监听目标文件夹,每隔5秒处理1次
        :return:
        """
        self._log_with_time('the ImageBorderHandler starts working ...')
        while(True):
            self._inner_process()
            time.sleep(5)


if __name__ == '__main__':
    image_border_handler = ImageBorderHandler('/Users/jason315/Desktop/test-image', '/Users/jason315/Desktop/image-bordered')
    image_border_handler.start_process()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一束尘光

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值