使用python开发一个图片与json互转小工具

本文详细描述了一个使用Python编写的图片转Base64编码并保存到JSON文件的小工具,同时介绍了如何利用PyQt创建前端界面,以及后端处理中涉及的库如Base64、OpenCV、PIL、IO库和json库的使用。
摘要由CSDN通过智能技术生成

使用python开发一个图片与json互转小工具

工具界面

截图
在这里插入图片描述

功能

  • 把图片转为json文件,图片数据使用base64进行编码
  • 除了图片数据以外,json文件还存储图片路径,图片宽度和高度
  • json文件还原成图片

工具生成的json文件
在这里插入图片描述

base64编码

  • Base64是一种二进制到文本的编码方式。如果要更具体一点的话,可以认为它是一种将
    byte数组编码为字符串的方法,而且编码出的字符串只包含ASCII基础字符。
  • Base64使用到的64个字符:
  • A-Z 26个
  • a-z 26个
  • 0-9 10个
  • +1个
  • / 1个

前端-pyqt

前端完全由pyqt完成。主窗体继承QMainWindow得来。窗体包括两部分,一部分是菜单栏,一部分是图像显示区域。
文件选择对话框

在这里插入图片描述

图像显示区域,继承QLabel得来。
在这里插入图片描述

后端处理

  • base64库:处理base64编码和解码
  • opencv,PIL,IO库:处理图片读取与输出,获取图片宽度和高度
  • json库:json对象生成与读取

代码

import copy
import io
import cv2
import sys
import os
import json
import base64
from PIL import Image
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtGui import QImage, QPixmap, QPainter, QPen, QIcon
from PyQt5.QtWidgets import QFileDialog, QMessageBox, QLabel, QApplication, QMainWindow
from PyQt5.QtCore import QRect, Qt, QSize


class ImageLabel(QLabel):
    """"
    用于显示图片的 Label
    """

    def __init__(self, parent=None):
        super().__init__(parent)
        self.x0 = 0
        self.y0 = 0
        self.x1 = 0
        self.y1 = 0
        self.flag = False  # 标记是否能够绘制矩形
        self.__isClear = False  # 标记是否是清除矩形
        self.setAlignment(Qt.AlignCenter)  # 居中对齐
        self.setFrameShape(QtWidgets.QFrame.Box)  # 设置边框
        self.setStyleSheet("border-width: 1px;border-style: solid;border-color: rgb(218, 218, 218)")
        self.setText("")
        self.__w, self.__h = 0, 0
        self.pixmap_width, self.pixmap_height = 0, 0  # pixmap 的宽度、高度
        self.pixmap_x_start, self.pixmap_y_start = 0, 0  # pixmap 在 label 中的起点位置
        self.pixmap_x_end, self.pixmap_y_end = 0, 0  # pixamp 在 label 中的终点位置
        self.img_x_start, self.img_y_start = 0, 0  # 图片中选择的矩形区域的起点位置
        self.img_x_end, self.img_y_end = 0, 0  # 图片中选择的矩形区域的终点位置
        self.autoFillBackground()

    # 鼠标点击事件
    def mousePressEvent(self, event):
        # self.flag = True
        # 鼠标点击,相当于开始绘制矩形,将 isClear 置为 False
        self.__isClear = False
        self.x0 = event.x()
        self.y0 = event.y()
        # 计算 Pixmap 在 Label 中的位置
        self.__w, self.__h = self.width(), self.height()
        self.pixmap_x_start = (self.__w - self.pixmap_width) / 2
        self.pixmap_y_start = (self.__h - self.pixmap_height) / 2
        self.pixmap_x_end = self.pixmap_x_start + self.pixmap_width
        self.pixmap_y_end = self.pixmap_y_start + self.pixmap_height
        # 获取图片的在label中的位置信息

    # 鼠标释放事件
    def mouseReleaseEvent(self, event):
        # self.flag = False
        self.setCursor(Qt.ArrowCursor)  # 鼠标释放,矩形已经绘制完毕,恢复鼠标样式

    # 鼠标移动事件
    def mouseMoveEvent(self, event):
        if self.flag:
            self.x1 = event.x()
            self.y1 = event.y()
            self.update()

    def setPixmap(self, pixmap):
        super().setPixmap(pixmap)
        self.pixmap_width, self.pixmap_height = pixmap.width(), pixmap.height()

    # 绘制事件
    def paintEvent(self, event):
        super().paintEvent(event)

        # 判断是否是清除
        if self.__isClear:
            return  # 是清除,则不需要执行下面的绘制操作。即此次 paint 事件没有绘制操作,因此界面中没有绘制的图形(从而相当于清除整个界面中已有的图形)

        # 判断用户起始位置是否在图片区域,只有在图片区域才画选择的矩形图
        if (self.pixmap_x_start <= self.x0 <= self.pixmap_x_end) \
                and (self.pixmap_y_start <= self.y0 <= self.pixmap_y_end):
            # 判断结束位置是否在图片区域内,如果超过,则直接设置成图片区域的终点
            if self.x1 > self.pixmap_x_end:
                self.x1 = int(self.pixmap_x_end)
            elif self.x1 < self.pixmap_x_start:
                self.x1 = int(self.pixmap_x_start)

            if self.y1 > self.pixmap_y_end:
                self.y1 = int(self.pixmap_y_end)
            elif self.y1 < self.pixmap_y_start:
                self.y1 = int(self.pixmap_y_start)
            # 矩形框区域
            rect = QRect(self.x0, self.y0, self.x1 - self.x0, self.y1 - self.y0)
            painter = QPainter(self)
            painter.setPen(QPen(Qt.red, 2, Qt.SolidLine))
            painter.drawRect(rect)
            # 计算矩形区域在图片中的位置
            self.img_x_start = int(self.x0 - self.pixmap_x_start)
            self.img_x_end = int(self.x1 - self.pixmap_x_start)
            self.img_y_start = int(self.y0 - self.pixmap_y_start)
            self.img_y_end = int(self.y1 - self.pixmap_y_start)

    def clearRect(self):
        # 清除
        self.__isClear = True
        self.update()


class MyWindow(QMainWindow):

    def __init__(self):
        super(MyWindow, self).__init__()
        self.current_img = None
        self.img_base64 = None
        self.setupUi()

    def setupUi(self):

        self.resize(926, 806)
        self.setWindowTitle("图片转JSON")
        # self.central_widget:主窗口
        self.central_widget = QtWidgets.QWidget(self)
        self.central_widget_layout = QtWidgets.QVBoxLayout()
        self.central_widget.setLayout(self.central_widget_layout)
        # 主窗口布局间隙
        self.central_widget_layout.setContentsMargins(0, 0, 0, 0)
        self.central_widget_layout.setSpacing(0)
        #  self.title:横向菜单栏
        self.title = QtWidgets.QFrame(self.central_widget)
        self.title.setMinimumSize(QtCore.QSize(0, 55))
        self.title.setMaximumSize(QtCore.QSize(188888, 55))
        self.title.setFrameShape(QtWidgets.QFrame.StyledPanel)
        self.title.setFrameShadow(QtWidgets.QFrame.Raised)
        # self.title_layout:横向菜单栏布局
        self.title_layout = QtWidgets.QHBoxLayout()
        self.title.setLayout(self.title_layout)
        self.operation = QtWidgets.QFrame(self.title)
        self.operation.setMinimumSize(QtCore.QSize(450, 45))
        self.operation.setMaximumSize(QtCore.QSize(450, 45))
        self.operation.setFrameShape(QtWidgets.QFrame.StyledPanel)
        self.operation.setFrameShadow(QtWidgets.QFrame.Raised)
        # title_button_layout:title的按钮横向布局
        self.title_button_layout = QtWidgets.QHBoxLayout()
        self.operation.setLayout(self.title_button_layout)
        self.btn_open = QtWidgets.QToolButton(self.operation)
        self.title_button_layout.addWidget(self.btn_open)
        self.btn_img_to_json = QtWidgets.QToolButton(self.operation)
        self.title_button_layout.addWidget(self.btn_img_to_json)

        self.btn_json_to_img = QtWidgets.QToolButton(self.operation)
        self.title_button_layout.addWidget(self.btn_json_to_img)

        self.title_layout.addWidget(self.operation)
        # spacerItem弹簧
        spacerItem = QtWidgets.QSpacerItem(100, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.title_layout.addItem(spacerItem)
        # 主窗口布局添加标题菜单控件
        self.central_widget_layout.addWidget(self.title)
        self.img_display = ImageLabel(self)
        self.central_widget_layout.addWidget(self.img_display)
        self.setCentralWidget(self.central_widget)
        # 按钮显示文字
        self.btn_open.setText("打开")
        self.btn_open.setIcon(QIcon("./icon/open.png"))
        self.btn_open.setIconSize(QSize(36, 36))
        self.btn_open.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)

        self.btn_img_to_json.setText("图片转json")
        self.btn_img_to_json.setIcon(QIcon("./icon/save.png"))
        self.btn_img_to_json.setIconSize(QSize(36, 36))
        self.btn_img_to_json.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)

        self.btn_json_to_img.setText("json转图片")
        self.btn_json_to_img.setIcon(QIcon("./icon/save.png"))
        self.btn_json_to_img.setIconSize(QSize(36, 36))
        self.btn_json_to_img.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)

        # 按钮绑定事件
        self.btn_open.clicked.connect(self.open_img)
        self.btn_img_to_json.clicked.connect(self.img_to_json)
        self.btn_json_to_img.clicked.connect(self.json_to_img)

        # 字体统一定义
        font = QtGui.QFont()
        font.setPointSize(10)
        self.setFont(font)
        self.btn_open.setFont(font)
        self.btn_img_to_json.setFont(font)

        # 格式设置
        self.central_widget.setStyleSheet("background: rgb(252, 255, 255)")
        self.title.setStyleSheet("background: rgb(60, 60, 60)")
        self.btn_open.setStyleSheet("background: rgba(0, 0, 0, 0);\n"
                                    "color: rgb(255, 255, 255)")
        self.btn_img_to_json.setStyleSheet("background: rgba(0, 0, 0, 0);\n"
                                           "color: rgb(255, 255, 255)")

        self.btn_json_to_img.setStyleSheet("background: rgba(0, 0, 0, 0);\n"
                                           "color: rgb(255, 255, 255)")

    def open_img(self):
        """
        “打开” 按钮的点击事件
        """
        img_name, img_type = QFileDialog.getOpenFileName(self, "打开图片或json", "", "*.jpg;*.png;*.jpeg;*.json")
        if (img_name == "") or (img_name is None):
            self.__show_warning_message_box("未选择图片")
            return
        if img_name.split("/")[-1].split(".")[-1] == "json":
            self.__show_warning_message_box("读入json成功,点击json转图片按钮进行转换")
            self.json_name = img_name
            with open(img_name, 'r') as fcc_file:
                fcc_data = json.load(fcc_file)
                self.img_base64 = fcc_data["imageData"]
        else:
            img = cv2.imread(img_name)  # 读取图像
            self.showImage(img)
            self.current_img = img
            self.last_img = self.current_img
            self.original_img = copy.deepcopy(self.current_img)
            self.original_img_path = img_name

    def showImage(self, img, is_grayscale=False):
        x = img.shape[1]  # 获取图像大小
        y = img.shape[0]
        self.zoomscale = 1  # 图片放缩尺度
        bytesPerLine = 3 * x
        if len(img.shape) == 2:  # 判断是否为灰度图,如果是灰度图,需要转换成三通道图
            img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
        frame = QImage(img.data, x, y, bytesPerLine, QImage.Format_RGB888).rgbSwapped()
        pix = QPixmap.fromImage(frame)
        self.img_display.setPixmap(pix)
        self.img_display.repaint()

    def __show_warning_message_box(self, msg):
        QMessageBox.warning(self, "警告", msg, QMessageBox.Ok)

    def __show_info_message_box(self, msg):
        QMessageBox.information(self, "提示", msg, QMessageBox.Ok)

    def undo_img(self):
        """
        “恢复” 按钮的点击事件,将图片恢复到最初的状态
        :return:
        """
        if self.current_img is None:
            self.__show_warning_message_box("未选择图片")
            return
        self.current_img = self.original_img
        self.last_img = self.current_img
        self.showImage(self.current_img)

    def img_to_json(self):
        """
         “保存” 按钮的点击事件,将图片恢复到最初的状态
        :return:
        """
        if self.current_img is None:
            self.__show_warning_message_box("未选择图片")
            return

        filepath, filename = os.path.splitext(self.original_img_path)
        json_path, json_type = QFileDialog.getSaveFileName(self, "保存json文件", filepath + ".json", "*" + ".json")
        # 处理
        size = self.current_img.shape
        width = size[1]  # 宽度
        height = size[0]  # 高度
        with open(self.original_img_path, 'rb') as f:
            image_base64 = base64.b64encode(f.read())
            image_base64 = str(image_base64, encoding='utf-8')
        data = {"imagePath": self.original_img_path, "imageHeight": height,
                "imageWidth": width, "imageData": image_base64}
        with open(json_path, 'w') as f:
            json.dump(data, f)
        self.__show_warning_message_box("转换完成")

    def json_to_img(self):
        if self.img_base64 is None:
            self.__show_warning_message_box("未选择json文件")
            return
        filepath, filename = os.path.split(self.json_name)
        output_filename = os.path.join(filepath, filename.split(".")[0] + ".jpg")
        self.convert_and_save_image(self.img_base64, output_filename)
        self.__show_warning_message_box("转换完成")
        img = cv2.imread(output_filename)  # 读取图像
        self.showImage(img)

    def convert_and_save_image(self, base64_str, output_filename):
        image_data = base64.b64decode(base64_str)
        image = Image.open(io.BytesIO(image_data))
        image.save(output_filename)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MyWindow()
    window.show()
    sys.exit(app.exec_())
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

立秋6789

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

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

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

打赏作者

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

抵扣说明:

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

余额充值