python编程:实现对数据库中图片文件的查看及比对

当谈到图像查看和管理时,我们往往会使用一些工具软件,比如Windows自带的照片查看器或者第三方工具。那如果你想要一个更加强大和定制化的图像查看器呢?这时候就需要自己动手写一个程序了。
C:\pythoncode\new\ShowSqliteImage.py

这里我们将介绍一个使用Python和wxPython编写的图像查看器应用程序。它不仅可以查看图像,还支持缩放、旋转等基本操作,最酷的是它可以连接SQLite数据库,从中读取图像并计算图像的MD5哈希值,用于查找重复图像。

程序的界面非常简洁,顶部有一个文本框用于选择SQLite数据库文件,中间是一个列表框列出数据库中的所有图像名称。选择一个图像名称后,就会在右侧区域显示该图像。下方有几个按钮,可以对图像进行旋转、放大、缩小和重置等操作。还有一个独特的"Compare MD5"按钮,点击它就会计算当前图像的MD5哈希值,并在数据库中搜索是否有重复的图像。

代码的编写利用了wxPython这个跨平台的GUI库,使用Python的PIL库来处理图像。SQLite则用于存储图像数据和元数据。代码结构清晰,功能实现也很巧妙,是一个不错的wxPython编程实例。

完整代码:

import wx
import sqlite3
import os
import hashlib
from datetime import datetime
import io
from PIL import Image, ImageOps

class ImageViewerApp(wx.Frame):
    def __init__(self, parent, title):
        super(ImageViewerApp, self).__init__(parent, title=title, size=(1000, 700))
        
        self.panel = wx.Panel(self)
        self.db_path = ""
        self.original_image = None
        
        self.init_ui()
        self.Centre()
        self.Show()

    def init_ui(self):
        vbox = wx.BoxSizer(wx.VERTICAL)
        
        # Database selection
        hbox1 = wx.BoxSizer(wx.HORIZONTAL)
        self.db_path_text = wx.TextCtrl(self.panel)
        db_path_btn = wx.Button(self.panel, label='Select Database')
        db_path_btn.Bind(wx.EVT_BUTTON, self.on_select_database)
        hbox1.Add(self.db_path_text, proportion=1, flag=wx.EXPAND|wx.ALL, border=5)
        hbox1.Add(db_path_btn, flag=wx.ALL, border=5)
        
        vbox.Add(hbox1, flag=wx.EXPAND)
        
        hbox2 = wx.BoxSizer(wx.HORIZONTAL)
        
        # List of image names
        self.image_list = wx.ListBox(self.panel)
        self.image_list.Bind(wx.EVT_LISTBOX, self.on_select_image)
        hbox2.Add(self.image_list, proportion=1, flag=wx.EXPAND|wx.ALL, border=5)
        
        # Image display area and controls
        right_panel = wx.Panel(self.panel)
        right_sizer = wx.BoxSizer(wx.VERTICAL)
        
        self.image_display = wx.StaticBitmap(right_panel)
        right_sizer.Add(self.image_display, proportion=1, flag=wx.EXPAND|wx.ALL, border=5)
        
        btn_sizer = wx.BoxSizer(wx.HORIZONTAL)
        rotate_btn = wx.Button(right_panel, label='Rotate')
        rotate_btn.Bind(wx.EVT_BUTTON, self.on_rotate_image)
        btn_sizer.Add(rotate_btn, flag=wx.ALL, border=5)
        
        zoom_in_btn = wx.Button(right_panel, label='Zoom In')
        zoom_in_btn.Bind(wx.EVT_BUTTON, self.on_zoom_in_image)
        btn_sizer.Add(zoom_in_btn, flag=wx.ALL, border=5)
        
        zoom_out_btn = wx.Button(right_panel, label='Zoom Out')
        zoom_out_btn.Bind(wx.EVT_BUTTON, self.on_zoom_out_image)
        btn_sizer.Add(zoom_out_btn, flag=wx.ALL, border=5)
        
        reset_btn = wx.Button(right_panel, label='Reset')
        reset_btn.Bind(wx.EVT_BUTTON, self.on_reset_image)
        btn_sizer.Add(reset_btn, flag=wx.ALL, border=5)
        
        compare_btn = wx.Button(right_panel, label='Compare MD5')
        compare_btn.Bind(wx.EVT_BUTTON, self.on_compare_md5)
        btn_sizer.Add(compare_btn, flag=wx.ALL, border=5)
        
        right_sizer.Add(btn_sizer, flag=wx.ALL|wx.CENTER, border=10)
        right_panel.SetSizer(right_sizer)
        
        hbox2.Add(right_panel, proportion=2, flag=wx.EXPAND|wx.ALL, border=5)
        
        vbox.Add(hbox2, proportion=1, flag=wx.EXPAND)
        
        self.panel.SetSizer(vbox)
    
    def on_select_database(self, event):
        with wx.FileDialog(self, "Choose SQLite database file", wildcard="SQLite files (*.db)|*.db|All files (*.*)|*.*", style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as fileDialog:
            if fileDialog.ShowModal() == wx.ID_CANCEL:
                return
        
            self.db_path = fileDialog.GetPath()
            self.db_path_text.SetValue(self.db_path)
            self.load_image_names()
    
    def load_image_names(self):
        if not self.db_path:
            wx.MessageBox('Database path is required', 'Error', wx.OK | wx.ICON_ERROR)
            return
        
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        cursor.execute("SELECT picname FROM pics")
        rows = cursor.fetchall()
        
        self.image_list.Clear()
        for row in rows:
            self.image_list.Append(row[0])
        
        conn.close()
    
    def on_select_image(self, event):
        selected_image = self.image_list.GetString(self.image_list.GetSelection())
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        cursor.execute("SELECT pic, picmd5, picdate FROM pics WHERE picname = ?", (selected_image,))
        row = cursor.fetchone()
        
        if row:
            pic_data, picmd5, picdate = row
            self.original_image = Image.open(io.BytesIO(pic_data))
            self.display_image(self.original_image)
            self.SetTitle(f"MD5: {picmd5} | Date: {picdate}")
        
        conn.close()
    
    def display_image(self, image):
        wx_image = wx.Image(image.size[0], image.size[1])
        wx_image.SetData(image.convert("RGB").tobytes())
        bitmap = wx.Bitmap(wx_image)
        self.image_display.SetBitmap(bitmap)
        self.panel.Layout()
    
    def on_rotate_image(self, event):
        if self.original_image:
            self.original_image = self.original_image.rotate(90, expand=True)
            self.display_image(self.original_image)
    
    def on_zoom_in_image(self, event):
        if self.original_image:
            width, height = self.original_image.size
            self.original_image = self.original_image.resize((width + int(width * 0.1), height + int(height * 0.1)), Image.ANTIALIAS)
            self.display_image(self.original_image)
    
    def on_zoom_out_image(self, event):
        if self.original_image:
            width, height = self.original_image.size
            self.original_image = self.original_image.resize((width - int(width * 0.1), height - int(height * 0.1)), Image.ANTIALIAS)
            self.display_image(self.original_image)
    
    def on_reset_image(self, event):
        if self.original_image:
            self.load_image_names()
            self.original_image = None
            self.image_display.SetBitmap(wx.NullBitmap)
            self.SetTitle("Image Viewer")
    
    def on_compare_md5(self, event):
        if self.original_image:
            img_byte_arr = io.BytesIO()
            self.original_image.save(img_byte_arr, format='PNG')
            md5_hash = hashlib.md5(img_byte_arr.getvalue()).hexdigest()
            
            conn = sqlite3.connect(self.db_path)
            cursor = conn.cursor()
            
            cursor.execute("SELECT picname FROM pics WHERE picmd5 = ?", (md5_hash,))
            row = cursor.fetchone()
            
            if row:
                wx.MessageBox(f"Image with MD5 {md5_hash} found: {row[0]}", 'MD5 Match', wx.OK | wx.ICON_INFORMATION)
            else:
                wx.MessageBox(f"No image with MD5 {md5_hash} found.", 'MD5 Match', wx.OK | wx.ICON_INFORMATION)
            
            conn.close()

if __name__ == '__main__':
    app = wx.App(False)
    frame = ImageViewerApp(None, "Image Viewer")
    app.MainLoop()


好的,我们来看一下这个图像查看器的主要代码部分:

def on_select_image(self, event):
    # 获取选中的图像名称
    selected_image = self.image_list.GetString(self.image_list.GetSelection())
    conn = sqlite3.connect(self.db_path)
    cursor = conn.cursor()

    # 从数据库中查询该图像的数据和元数据
    cursor.execute("SELECT pic, picmd5, picdate FROM pics WHERE picname = ?", (selected_image,))
    row = cursor.fetchone()

    if row:
        pic_data, picmd5, picdate = row
        # 从字节数据创建PIL Image对象
        self.original_image = Image.open(io.BytesIO(pic_data))
        self.display_image(self.original_image)
        self.SetTitle(f"MD5: {picmd5} | Date: {picdate}")

    conn.close()

def display_image(self, image):
    # 将PIL Image对象转换为wxPython可显示的位图
    wx_image = wx.Image(image.size[0], image.size[1])
    wx_image.SetData(image.convert("RGB").tobytes())
    bitmap = wx.Bitmap(wx_image)
    self.image_display.SetBitmap(bitmap)
    self.panel.Layout()

这部分代码是在选择了一个图像后执行的。首先从列表框获取选中的图像名称,然后连接到SQLite数据库,使用SQL查询语句从pics表中获取该图像名称对应的图像数据(pic字段)、MD5哈希值(picmd5)和日期(picdate)。

接着使用PIL库的Image.open()方法从字节数据创建一个Image对象,就可以对该图像进行后续的操作了。display_image()函数则负责将PIL Image对象转换为wxPython可以显示的位图,并设置到GUI的StaticBitmap控件上。

def on_rotate_image(self, event):
    if self.original_image:
        self.original_image = self.original_image.rotate(90, expand=True)
        self.display_image(self.original_image)

这段代码实现了图像旋转功能。利用PIL的Image.rotate()方法可以将图像按指定角度旋转,expand=True表示可以扩展输出尺寸以适应旋转后的图像。

def on_compare_md5(self, event):
    if self.original_image:
        img_byte_arr = io.BytesIO()
        self.original_image.save(img_byte_arr, format='PNG')
        md5_hash = hashlib.md5(img_byte_arr.getvalue()).hexdigest()
        
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        cursor.execute("SELECT picname FROM pics WHERE picmd5 = ?", (md5_hash,))
        row = cursor.fetchone()
        
        if row:
            wx.MessageBox(f"Image with MD5 {md5_hash} found: {row[0]}", 'MD5 Match', wx.OK | wx.ICON_INFORMATION)
        else:
            wx.MessageBox(f"No image with MD5 {md5_hash} found.", 'MD5 Match', wx.OK | wx.ICON_INFORMATION)
        
        conn.close()

这是一个非常巧妙的功能,可以根据计算出的MD5哈希值在数据库中查找是否有重复的图像。首先将当前图像保存到字节IO流中,然后使用hashlib计算该字节流的MD5哈希值。接着连接数据库,执行SQL查询语句查找pics表中是否有相同MD5哈希值的记录。根据查询结果,弹出不同的MessageBox提示信息。

结果如下:

在这里插入图片描述

通过这些代码,我们可以看到作者利用了Python的多个强大库和SQLite数据库,实现了一个既实用又有创意的小程序。代码写得很优雅,值得我们学习和借鉴。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/669972.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

赛轮集团受邀出席2024国际新能源智能网联汽车创新生态大会

赛轮集团受邀出席2024国际新能源智能网联汽车创新生态大会 5月22日-24日,以“汽车供应链的创新与重构”为主题的2024国际新能源智能网联汽车创新生态大会(以下简称CIEV2024)在温州瑞安隆重召开。会议期间,CIEV2024高端对话成功召…

Docker(Centos7+)

先确定是否 Centos 7 及以上的版本 查看是否 ping 通外网 linux centos7运行下面的代码,基本上都可以正常安装 # 删除之前的docker残留 yum -y remove docker*yum install -y yum-utilsyum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/…

Linux、Windows安装python环境(最新版及历史版本指定版本)-python

目录 一、Linux环境二、windows环境最新版本下载指定版本下载配置VScode开发环境 python 官网地址: https://www.python.org/ 一、Linux环境 以openEuler/CentOS为例 查看可安装python源版本 dnf provides python*默认安装新版本 dnf install -y python3. 进入p…

拼多多商品信息一键抓取:深度解析商品详情接口,Python实战代码来袭!

拼多多的商品详情接口允许开发者通过指定的商品ID获取商品的详细信息,如商品标题、价格、描述、图片等。接口采用HTTP请求方式,支持GET方法,返回格式为JSON。 三、接口调用 要调用拼多多的商品详情接口,你需要遵循以下步骤&…

C++ vector的使用和简单模拟实现(超级详细!!!)

目录 前言 1.STL是什么 2.vector使用 2.1 vector简介 2.2 常用接口函数 1. 构造函数 2.operator[ ]和size,push_back 3. 用迭代器进行访问和修改 4. 范围for遍历 5.修改类型函数 pop_back find insert erase 6. 容量相关函数capacity resize reserve 3.…

【Node】Assertion testing 模块的使用

简言 node:assert 模块提供了一组用于验证不变式的断言函数。 node版本:20.14.0 Assertion testing 测试断言模块 node:assert 模块是一个测试相关的模块。 严格模式和非严格模式 感觉该模块的严格模式和js的严格模式相匹配,非严格模式也是这样的。…

掘金AI 商战 宝典 进阶班:如何用AI绘画设计(实战实操 现学现用 玩赚超值)

课程内容 10-第十讲用AI做网站设计 11-第十一讲用AI做艺术字 12-第十二讲用AI做室内设计(上) 13-第十三讲用AI做室内设计(下) 14-第十四讲用AI抠图与修图 15-第十五讲用AI修复模糊照片 16-第十六讲用AI自动做PPT(上) 17-第十七讲用AI自动做PPT(下) 18-第十八讲用AI做文…

atcoder350,351,352,353,354,355期部分题解

声明:有些题感觉已经说到很明白了,就先不写代码了,有空会补上 目录 350D: new friend 350E: toward 0 351D:Grid and Magnet 352D:permutation subsequence 353C: sigma problem 353D: another sigma problem 354C: atcoder magics …

一文读懂存内计算与近存计算的分类与应用

存内计算与近存计算-基础理论及分类 技术基础知识和分类 "近存计算"与"存内计算"易混淆,本章明晰其分类,并比较各内存驱动方法的独特优势。可计算存储器设备可作分立加速器或替代现有存储模块。我们深入剖析每种方法的利弊&#xf…

像艺术家一样工作

接下来开始翻译这本小册子 豆瓣评分还是挺高的,目前在国内没有看到有在售的翻译版本 书名直译的话是:像艺术家一样去偷 作者可能是为了制造营销话题,所以起了这么一个名字 但是偷这个词总归不太体面,所以我把书名翻译为&#…

Qos令牌桶算法:笔记0601

令牌桶 令牌:目前看到2种表述,csdn表示一个令牌代表一个字节,51cto是一个令牌代表一个bit。51cto上关于cisco qos算法描述多表达为一个令牌一个bit (不知道rfc上咋表达的懒得去查了,主打一个好读书不求甚解,感觉应该是…

c++学习----初识类和对象(上)

1.面向过程和面向对象初步认识 C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。 C是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完 成。…

rtl8723DU移植 android4.4 4418

一、 linux 的移植。 首先编译一遍确保没有问题。 将驱动拷贝到 driver/net/wireless 目录下。 使用的是: 改写 makefile Kconfig 去改写 8723 的makefile 设置menuconfig 使能固有的 库。 使能USB部分 ieee 部分 编译一遍 有报错。 解决: …

基于深度学习YOLOv8\YOLOv5的花卉识别鲜花识别检测分类系统设计

本文将介绍基于深度学习YOLOv8\YOLOv5PySide6SQLite的花卉检测与识别系统,该系统基于YOLOv8算法,并与YOLOv5版本进行比较,该系统不仅实现了对花卉的精准识别和分类,还提供了包括用户认证管理、模型快速切换及界面个性化定制在内的…

ssm汉服文化平台网站

博主介绍:✌程序员徐师兄、8年大厂程序员经历。全网粉丝15w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇…

【TB作品】msp430f5529单片机墨水屏,口袋板,tmp421温度,温控风扇

文章目录 一、扬声器模块介绍二、驱动介绍三、程序介绍四、全部代码下载 msp430f5529d单片机墨水屏,口袋板,tmp421温度,温控风扇 基本要求:高于20度开转,温度越高转速越快,高于40度风扇停转,温…

Day45 动态规划part05

LC1049最后一块石头重量II(未掌握) 未掌握分析:其实本题跟LC416分割等和子集类似,本质上题目的要求是尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小,也就是01背包问题weight和value都是stones数组,题目可以看…

Java的JDK环境变量配置(Windows)

只写了需要配置的环境变量 注:从JDK1.5开始,配置Java环境变量时,不再需要配置CLASSPATH,只需要配置JAVA_HOME和Path 1、配置JAVA_HOME 找到自己的JDK位置,我这里是 C:\dev\java\jdk-17.0.119在环境变量-系统变量中&…

【已解决】Error in the HTTP2 framing layer

1.问题描述 在使用git将代码上传github的时候在最后一部push的时候遇到这个fatal 2.解决方案 由于我原先设置的origin是http协议下的,如下 git remote add origin https://github.com/Charlesbibi/Simple_Cloud.githttp协议下行不通不妨试一试ssh协议下&#xff…

代码随想录算法训练营 day23| ● 669. 修剪二叉搜索树 ● 108.将有序数组转换为二叉搜索树 ● 538.把二叉搜索树转换为累加树

文章目录 前言669. 修剪二叉搜索树思路方法一 递归法方法二 迭代法 108.将有序数组转换为二叉搜索树思路方法一 递归法方法二 迭代法 538.把二叉搜索树转换为累加树思路方法一方法二 总结 前言 迭代法都没看主要是669和538【538很简单】 669. 修剪二叉搜索树 思路 不用看教程…