作者: Jet L

  • 【Python】一个适配本网站性能的压缩图片脚本

    由于网站的服务器与带宽性能有限,因此上传的图片被严格限制了边长和大小,使用脚本可以有效对想要上传的文件进行批处理,节省时间。

    参考代码:

    实现对jpg、png、webp等文件的压缩,限制最长边2560,大小2m以下,随机文件名且保留可能含有的EXIF信息。

    暂不支持中文路径文件夹。

    import os
    import random
    import string
    from PIL import Image
    import tkinter as tk
    from tkinterdnd2 import TkinterDnD, DND_FILES
    
    # 生成随机文件名
    def generate_random_filename(length=10):
        """生成指定长度的随机文件名(字母和数字)"""
        return ''.join(random.choices(string.ascii_letters + string.digits, k=length))
    
    def compress_image(input_path, max_size=2560, max_file_size=2 * 1024 * 1024, quality=85):
        """对图片进行压缩和尺寸调整,保留EXIF信息"""
        img = Image.open(input_path)
        exif_data = img.info.get("exif")  # 获取EXIF数据
        width, height = img.size
        file_size = os.path.getsize(input_path)
        
        # 生成随机文件名前缀
        random_prefix = generate_random_filename(10)  # 10 位随机字符前缀
    
        # 获取文件扩展名
        file_extension = os.path.splitext(input_path)[1].lower()
    
        # 如果图片是RGBA格式,将其转换为RGB格式,只对JPEG格式需要转换
        if img.mode == 'RGBA' and file_extension not in ['.jpeg', '.jpg']:
            output_path = os.path.join(os.path.dirname(input_path), f"{random_prefix}_已压缩{file_extension}")
            if exif_data:
                img.save(output_path, quality=quality, optimize=True, exif=exif_data)
            else:
                img.save(output_path, quality=quality, optimize=True)
        else:
            # 如果图片不需要压缩,直接保存为“无需压缩”版本
            if max(width, height) <= max_size and file_size <= max_file_size:
                output_path = os.path.join(os.path.dirname(input_path), f"{random_prefix}_无需压缩{file_extension}")
                if exif_data:
                    img.save(output_path, exif=exif_data)
                else:
                    img.save(output_path)
            else:
                # 如果需要调整大小
                if max(width, height) > max_size:
                    scaling_factor = max_size / float(max(width, height))
                    new_size = (int(width * scaling_factor), int(height * scaling_factor))
                    img = img.resize(new_size, Image.LANCZOS)
                
                # 保存为JPEG格式,质量为85
                output_path = os.path.join(os.path.dirname(input_path), f"{random_prefix}_已压缩.jpg")
                if exif_data:
                    img.save(output_path, quality=quality, optimize=True, exif=exif_data)
                else:
                    img.save(output_path, quality=quality, optimize=True)
                
                # 如果文件过大,继续降低质量,直到符合要求
                while os.path.getsize(output_path) > max_file_size and quality > 10:
                    quality -= 10
                    if exif_data:
                        img.save(output_path, quality=quality, optimize=True, exif=exif_data)
                    else:
                        img.save(output_path, quality=quality, optimize=True)
        
        return output_path
    
    # 处理拖动的文件
    def on_drop(event):
        file_paths = event.data.split()
        process_images(file_paths)
    
    # 批量处理图片
    def process_images(file_paths):
        processed_files = []
        for file_path in file_paths:
            if is_image_file(file_path):
                processed_file = compress_image(file_path)
                processed_files.append(processed_file)
        print(f"处理完成的文件: {processed_files}")
    
    # 判断文件是否为图片
    def is_image_file(file_path):
        try:
            img = Image.open(file_path)
            return True
        except IOError:
            return False
    
    # 创建GUI界面
    root = TkinterDnD.Tk()
    root.title("图片压缩工具")
    root.geometry("600x400")
    
    label = tk.Label(root, text="将图片拖到这里", padx=20, pady=20)
    label.pack(padx=20, pady=20)
    
    # 绑定拖拽事件
    root.drop_target_register(DND_FILES)
    root.dnd_bind('<<Drop>>', on_drop)
    
    # 运行主循环
    root.mainloop()
    
  • 【Python】调用微信OCR来对输入的图片进行文字识别

    参考资料:

    1、三年磨一剑——微信OCR图片文字提取-腾讯云开发者社区-腾讯云

    2、可供独立使用且最小依赖的微信 OCR 功能包 – 吾爱破解 – 52pojie.cn

    3、GitHub – kanadeblisst00/wechat_ocr: 使用Python调用微信本地ocr服务

    4、[原创]Python调用微信OCR识别文字和坐标-编程技术-看雪-安全社区|安全招聘|kanxue.com

    示例代码:

    这段简易代码实现创建一个GUI,输入一个图片,将图片中的文字进行OCR,按段落生成到一个TXT中。

    import os
    import json
    import time
    import tkinter as tk
    from tkinter import filedialog, messagebox
    from wechat_ocr.ocr_manager import OcrManager, OCR_MAX_TASK_ID
    
    
    def ocr_result_callback(img_path: str, results: dict):
        save_text_to_txt(results, img_path)
    
    
    def save_text_to_txt(ocr_results, original_image_path):
        if 'ocrResult' in ocr_results and isinstance(ocr_results['ocrResult'], list):
            all_text = ""
            for result in ocr_results['ocrResult']:
                text = result.get('text', '')
                if text:
                    all_text += text + "\n"
    
            base_name, ext = os.path.splitext(original_image_path)
            txt_path = f"{base_name}_ocr_result.txt"
            with open(txt_path, 'w', encoding='utf-8') as f:
                f.write(all_text)
            print(f"已保存OCR结果到: {txt_path}")
        else:
            print("OCR结果不符合预期格式。")
    
    
    def select_image():
        file_path = filedialog.askopenfilename(
            title="选择图片",
            filetypes=(("Image files", "*.png;*.jpg;*.jpeg;*.bmp;*.tiff"), ("All files", "*.*"))
        )
        if not file_path:
            return
    
        wechat_ocr_dir = r"C:\Users\YourID\AppData\Roaming\Tencent\WeChat\XPlugin\Plugins\WeChatOCR79\extracted\WeChatOCR.exe"
        wechat_dir = r"C:\Program Files\Tencent\WeChat\[3.9.12.17]"
        ocr_manager = OcrManager(wechat_dir)
        ocr_manager.SetExePath(wechat_ocr_dir)
        ocr_manager.SetUsrLibDir(wechat_dir)
        ocr_manager.SetOcrResultCallback(ocr_result_callback)
        ocr_manager.StartWeChatOCR()
    
        ocr_manager.DoOCRTask(file_path)
    
        while ocr_manager.m_task_id.qsize() > 0 or ocr_manager.IsOcrRunning():
            time.sleep(0.5)
    
        ocr_manager.KillWeChatOCR()
        messagebox.showinfo("完成", "文字提取并保存完成!")
    
    
    def main():
        root = tk.Tk()
        root.title("OCR文字提取到txt工具")
        root.geometry("300x100")
    
        btn_select = tk.Button(root, text="选择图片", command=select_image)
        btn_select.pack(pady=20)
    
        root.mainloop()
    
    
    if __name__ == "__main__":
        main()
    

  • 【摄影】如何矫正Olympus 9mm F8 Fisheye

    前言

    奥林巴斯9mm F8鱼眼镜头是一个很有趣的玩具镜头,其小巧的体积,搭配不错的中心画质,成为M43相机中的最强镜头盖。

    鱼眼效果虽然有趣,在一些情况下我们还是想要一个标准的广角画面,因此对画面进行矫正就成为“一鱼两吃”的好选择。

    但是由于这枚镜头没有触点,因此我们在松下机身拍摄之后,只能通过电脑对照片进行后处理。问题来了,LR中没有该镜头的对应配置文件,该如何矫正图片?

    这里结合互联网信息,提供三种解决方式:

    1、LR中采用适马镜头配置文件。

    2、使用开源软件——Hugin。

    3、采用一个日本摄影师提供的Python脚本。

    以上方案都可以高效解决矫正问题,得到不错的成片。

    方法一:LR中采用适马镜头配置文件

    采用适马10mm F2.8 EX DC FISHEYE的矫正文件,可以取得不错的矫正效果。

    要注意此操作需要在LR中进行,Ps的Camera Raw插件里的镜头矫正文件不全,效果会有问题。

    方法二:开源软件Hugin

    步骤:

    (1)安装Hugin,打开需要矫正的图片。

    (2)选择全帧鱼眼,9mm,2x倍率。

    (3)在全景缝合中使用直线预测,先进界面下对输出内容进行优化,导出即可。

    方法三:Python脚本自动矫正

    该方法来自一个日本摄影师,我在python3.13下进行了一些调整,目前该代码创建一个GUI,可以实现拖动图片到GUI自动运行。

    存在的问题:边角畸变矫正还是存在一些问题,可以对一些数值进行调整,实现更好的畸变矫正结果。

    import numpy as np
    import cv2
    from PIL import Image
    import piexif
    import tkinter as tk
    from tkinterdnd2 import DND_FILES, TkinterDnD
    
    def process_image(file_path):
        # 定数
        scale = 0.95
        fsave = file_path.replace(".JPG", "_1.JPG").replace(".jpg", "_1.jpg")
        
        # 画像を開く
        image = Image.open(file_path)
        img = np.array(image, dtype=np.uint8)
        h, w = img.shape[:2]
        
        # 収差補正(Greenを拡大)
        green = cv2.resize(img[:,:,1], None, fx=1.0005, fy=1.0005, interpolation=cv2.INTER_CUBIC)
        difx = (green.shape[1] - w) // 2
        dify = (green.shape[0] - h) // 2
        img[:,:,1] = green[dify:dify+h, difx:difx+w]
        
        # 周辺減光補正
        size = max([h, w])  # 幅、高の大きい方を確保
        x = np.linspace(-w/size, w/size, w)
        y = np.linspace(-h/size, h/size, h)  # 長い方の辺が1になるように正規化
        xx, yy = np.meshgrid(x, y)
        r = np.sqrt(xx**2 + yy**2) 
        gain = 0.4 * r + 1   # 減光補正パラメータ(固定値)
        gainmap = np.dstack([gain, gain, gain])    # RGB同じゲイン
        img = np.clip(img * gainmap, 0., 255).astype(np.uint8)
        
        # 歪み補正
        f = max([h, w])
        mtx = np.array([[f,    0.,  w / 2],
                        [0.,   f,   h / 2],
                        [0.,   0.,  1    ]])
        
        # 歪み補正パラメータ(固定値) 
        dist = np.array([-0.63, -0.2, 0, 0, 0.8])
        
        n_mtx = cv2.getOptimalNewCameraMatrix(mtx, dist, (img.shape[1], img.shape[0]), 1)[0]
        map = cv2.initUndistortRectifyMap(mtx, dist, np.eye(3), n_mtx, (img.shape[1], img.shape[0]), cv2.CV_32FC1)
        
        # 拡大+shift
        mapx = map[0] * scale + (1 - scale) * w / 2
        mapy = map[1] * scale + (1 - scale) * h / 2
        img = cv2.remap(img, mapx, mapy, cv2.INTER_CUBIC)
        
        # 4:3 -> 3:2比率への変換 (高さを 8/9する)
        strt_y = h * 1 // 18
        end_y = h * 17 // 18
        img = img[strt_y:end_y, :, :]
        
        # Exif付与
        exif_dict = piexif.load(file_path)
        exif_dict["0th"][piexif.ImageIFD.ImageWidth]  = img.shape[1]
        exif_dict["0th"][piexif.ImageIFD.ImageLength] = img.shape[0]
        
        exif_dict["Exif"][piexif.ExifIFD.FocalLength] = (90, 10)
        exif_dict["Exif"][piexif.ExifIFD.FNumber] = (80, 10)
        exif_dict["Exif"][piexif.ExifIFD.FocalLengthIn35mmFilm] = 18
        exif_dict["Exif"][piexif.ExifIFD.PixelXDimension] = img.shape[1]
        exif_dict["Exif"][piexif.ExifIFD.PixelYDimension] = img.shape[0]
        
        exif_bytes = piexif.dump(exif_dict)
        
        # 保存
        im = Image.fromarray(img)
        im.save(fsave, "JPEG", exif=exif_bytes)
    
        # 显示保存路径和 EXIF 信息
        print(f"图像处理完成,文件已保存到: {fsave}")
    
        # 提取并显示特定 EXIF 信息
        exif_info = exif_dict["Exif"]
        aperture = exif_info.get(piexif.ExifIFD.FNumber, "未知")
        shutter_speed = exif_info.get(piexif.ExifIFD.ExposureTime, "未知")
        iso = exif_info.get(piexif.ExifIFD.ISOSpeedRatings, "未知")
    
        print("\n照片的主要 EXIF 信息:")
        print(f"光圈 (Aperture): {aperture}")
        print(f"快门速度 (Shutter Speed): {shutter_speed}")
        print(f"感光度 (ISO): {iso}")
    
    def drop(event):
        file_paths = event.data.strip('{}').split()  # 处理多个文件
        for file_path in file_paths:
            process_image(file_path)
    
    # 创建主窗口
    root = TkinterDnD.Tk()
    root.title("图片处理")
    root.geometry("400x200")
    
    # 提示信息
    label = tk.Label(root, text="将图片拖放到这里进行处理", padx=10, pady=10)
    label.pack(expand=True, fill=tk.BOTH)
    
    # 注册拖放事件
    root.drop_target_register(DND_FILES)
    root.dnd_bind('<<Drop>>', drop)
    
    # 启动 Tkinter 主循环
    root.mainloop()

    【施工中、、、】

  • 【Python】本地运行的,以缩略图搜原图脚本

    像我这种不太注重整理的人,在想找一张原图时候往往很抓狂,因为文件夹太多了!

    因此今天问ChatGPT“协调”了一段Python代码,可以有效的在本地用jpg缩略图来搜索原jpg图。

    代码主要用到PIL库,本来想用OpenCV但是实在是搞不定中文路径问题,本着能用就行的原则,因此只能使用PIL,代码如下👇。

  • 【自然笔记】玉带凤蝶羽化过程纪录

    提醒:本文具有大量昆虫图片,介意者谨慎观看。

    下面是一段800px长的拦阻索~

    OK,既然你看到这里了,那么让我们看看玉带凤蝶的羽化过程吧~

    家里饲养凤蝶幼虫一般都是在盒子中,但是羽化后的蝴蝶需要有个良好的攀爬物来为舒展翅膀。

    因此,如果蛹是附着在盒子中,我们可以将其小心取下,用一小截白纸卷成类似雪糕桶的样子,将蛹放在其中。

    就像这样,蝴蝶破蛹而出时候,让其有物体可以进行攀爬。

    刚羽化的蝴蝶是非常虚弱的,因此其更像是(挂)在枝子上。

    这时候的翅膀没法直接伸展开,需要把体液补充进翅脉,这一过程需要一段时间,期间蝴蝶基本是不会运动的,我们可以趁机多拍摄一些图片,观察一下崭新的蝴蝶。

    让人惊叹的纹路和色彩。

    找了一盏小灯,能看到五彩斑斓的黑色。

    随着翅膀逐渐舒展,整体状态也越来越好,翅膀也变得更加平整。

    一段时间后,蝴蝶会尝试展翅,这时候距离起飞已经不远。

    期间蝴蝶会清理自己的复眼,在排出多余的体液后不久便会尝试起飞。

    飞到板子上的蝴蝶。

    这个时候抓它就不是那么容易了,不过刚飞行的蝴蝶飞行能力还不是特别强大,很容易落地,可以尝试让它爬到手上,然后就可以放飞啦。

    目送蝴蝶飞到了楼下的树叶上。

    生命,如此奇妙。

  • 【小区观鸟】2024年6月1日

    【小区观鸟】2024年6月1日

    小区最常见的是乌鸫、麻雀、斑鸠、白头鹎这四大金刚,今天发现了此前没注意到的一种鸟——丝光椋(liáng)鸟。

  • 【简评】落日余晖,Panasonic G9M2是怎样一部相机?

    1、落日余晖,从降级的机身说起

    G9M2出乎预料的舍弃了前代G9强大、坚固且手感扎实(厚重)的机身设计,选择了S5M2相同的机身设计,而在全画幅这里,S5系列最多只能算做中端定位。

    因此,我们得到了一个全画幅大小的机身(在我看来这并不是槽点,因为再小的机身将会直接影响握持体验),得到了综合性能尚可的价格,失去了拍照易用的肩屏,失去了高端定位的翻折屏幕设计。

    机身的降级似乎彰显了G9M2在松下M43拍照旗舰中的一些隐约变动——定位上的不断降级。也许,日后只有顶级的M43摄像机,拍照扛把子的地位将逐步被全画幅机型所替代。

    2、电脑开机?从奇怪的特性说起

    我是从单反转到G9M2的,中间也用过诸如索尼Alpha、富士XH、XT等系列的相机,但是,G9M2在开机的时候就给我一整个冲击——超级慢的开机时间,这对于用惯了单反的人是难以置信的。

    G9M2在长时间未开机之后会进入类似休眠的情况,开机速度非常缓慢,再开机会快一些但也难称迅速,这对于一部主打性能的相机来说简直不可理喻,仿佛有什么庞大的软件在启动加载一样,喂,你可是一部相机!一部要随时待命的相机!

    此外,G9M2还有一个让人难以理解的BUG,那就是在塞入不同的存储卡时,回放速度也会发生改变,双卡会让回访速度下降,非常影响使用体验。

    3、难言强大,从尴尬的镜头群说起

    M43的镜头群固然数量众多,然而更新缓慢,因此我们可以说它镜头群丰富,但是难言强大与生命力,尤其是在G9M2搭载相位对焦之后,松下并没有非常充足的新镜头,来适配这一对焦系统。日常使用中可以明细感受到依旧是DFD下的反差拥有足够的速度,而相位在对焦速度上并不如人意,所以昔日丰富的镜头群反而成了一个潜在的掣肘,松下还有精力去更新那么庞大的M43产品线吗?看一看松下在镜头与机身的布局和宣传,可能性应该是越来越小。

    4、小巧?轻便!从一年的综合体验说起

    小巧上的疑问来源于机身,对于M43来说,这个机身不可谓不大,尤其是神经病一样的EVF突出部。(这个突出部可以从S5M2的拆机图解释,其EVF的背面是主板的顶部。所以借用机身的坏处就在这里,G9M2又没有风扇,额头的空间应该浪费了)

    但是整个系统的轻便是毋庸置疑的,得益于较小的成像圈,镜头体积可以做的比较小,从多镜头、多焦段的外出拍摄来看,整体系统可以称得上轻便,搭配丰富的自定义按键、波轮来使用也可谓舒适。

    5、难以预料,从M43的未来说起

    随着索尼将全局CMOS带到无反领域,全画幅的速度进入了一个拐点,下面就看各个厂家如何精确划分下一代全画幅相机的产品线。

    而松下M43传感器的速度在如今这个节点并不能说飞快,DGO技术的加入更是让续航崩溃,在速度、功耗、画质均不占优的情况下,松下M43还能支持多久,这恐怕不仅是消费者的疑问,更是松下市场部门、研发部门要权衡的问题,从G9M2的放货及市场表现上来看,这种问题的答案可能显而易见了。

    没有什么会不朽,M43走过了一段辉煌的岁月,等来了无反市场的全面发展,只不过这份市场的注脚是全画幅乃至中画幅,M43能走多远,这真是一个问题。

  • 【摄影师推荐】我的朋友——方方神奇(方知有)

    方方神奇是一个位于上海的摄影师,以人物写真、跟拍纪实、旅拍、风景、静物等内容为主,作为一名艺术硕士,她具有的电影学等教育背景让她的构图和调色颇为考究。

    点击进入她的个人网站:

    身为职业摄影师,她的勤劳和天赋,带来了持续、可靠的出片品质。这种努力让她几乎熟悉上海各个民政局及附近适合拍照的地方(笑)。

    但是和传统的商业摄影不同——这里是以我这个旁观的视角看来——拍照对方方来说不仅仅是一份工作,她所交付的图片带有活泼的构图和灵动的色彩,也就是我们所说的:包含了情感,这是一种难以用语言描述,却可以通过眼睛而直击心房的感触。

  • 【摄影师推荐】小春ハルカ——如沐春风

    小春ハルカ,昵称86ca86,是一个来自日本群马县的摄影师(所以她昵称中的86指的是AE86?XD),似乎是95年出生,她的作品我看到之后就想到四个字:如沐春风。

    她经常使用的器材应该是尼康。

    尼康云创中有对应的色彩配置可以使用:

    👉👉👉 淡く優しく瞬間を残せる。あたたかな色であふれるHidamari Color Flexible Color Picture Control | ニコンイメージング

    另外值得一提的是松下的Color Lab中,也有她所提供的LUT可以选择。

    instagram主页: