项目部署与打包:从源码到exe,以及未来扩展方向

代码写完了,怎么给别人用?

虚拟环境、依赖管理、PyInstaller打包、跨平台兼容——一个不少

一、虚拟环境与依赖管理

1.1 创建虚拟环境

powershell

# 创建
python -m venv venv

# 激活(Windows)
venv\Scripts\activate

# 激活(Linux/macOS)
source venv/bin/activate

1.2 依赖导出与安装

powershell

# 导出当前环境依赖
pip freeze > requirements.txt

# 安装依赖(用清华源)
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

1.3 依赖清单

依赖版本用途
PyQt66.11.0GUI框架
opencv-python4.9.0.80图像处理
numpy1.26.3数值计算
Pillow10.2.0图像文字绘制

二、PyInstaller打包

2.1 安装

powershell

pip install pyinstaller

2.2 打包命令

powershell

# 基础版
pyinstaller --onefile --windowed --name "液滴面积测量工具" main.py

# 带图标
pyinstaller --onefile --windowed --name "液滴面积测量工具" --icon=icon.ico main.py

# 添加额外文件(config.json)
pyinstaller --onefile --windowed --name "液滴面积测量工具" --add-data "config.json;." main.py

2.3 参数说明

参数说明
--onefile打包成单个exe
--windowed不显示命令行窗口
--name输出文件名
--icon应用图标
--add-data打包额外文件

2.4 打包结果

text

dist/
└── 液滴面积测量工具.exe

三、跨平台兼容性

3.1 获取应用根目录(支持打包后)

python

import os
import sys

def get_app_root():
    if hasattr(sys, '_MEIPASS'):
        # PyInstaller打包后的临时目录
        return sys._MEIPASS
    # 开发环境目录
    return os.path.dirname(os.path.abspath(__file__))

# 配置文件路径
CONFIG_PATH = os.path.join(get_app_root(), "config.json")

3.2 中文路径支持(OpenCV)

python

def load_image(self, file_path):
    try:
        self.original_image = cv2.imdecode(
            np.fromfile(file_path, dtype=np.uint8), 
            cv2.IMREAD_COLOR
        )
        # ...
    except Exception as e:
        return False, f"加载图像失败: {str(e)}"

3.3 字体回退

python

def draw_area_text(self, image, pixel_area, real_area):
    try:
        font = ImageFont.truetype("arial.ttf", 24)
    except:
        try:
            font = ImageFont.truetype("times.ttf", 24)
        except:
            font = ImageFont.load_default()   # 兜底
    # ...

四、功能扩展建议

4.1 批量处理

python

def batch_process_images(self, image_paths):
    results = []
    for file_path in image_paths:
        success, msg = self.image_processor.load_image(file_path)
        if not success:
            results.append({'file': file_path, 'success': False, 'error': msg})
            continue
        
        success, msg = self.image_processor.auto_detect_droplet()
        if not success:
            results.append({'file': file_path, 'success': False, 'error': msg})
            continue
        
        scale = self.calibrator.get_scale()
        pixel_area, real_area = self.image_processor.calculate_area(scale)
        
        results.append({
            'file': file_path,
            'success': True,
            'pixel_area': pixel_area,
            'real_area': real_area
        })
    return results

4.2 多液滴检测

python

def detect_multiple_droplets(self):
    if self.original_image is None:
        return False, "请先加载图像"
    
    gray = cv2.cvtColor(self.original_image, cv2.COLOR_BGR2GRAY)
    blurred = cv2.GaussianBlur(gray, (15, 15), 0)
    _, thresh = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
    edges = cv2.Canny(thresh, 50, 150)
    contours, _ = cv2.findContours(edges.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    
    # 过滤小面积
    min_area = 100
    valid_contours = [cnt for cnt in contours if cv2.contourArea(cnt) > min_area]
    
    if len(valid_contours) == 0:
        return False, "未检测到液滴"
    
    self.contours = valid_contours
    
    # 绘制所有轮廓(第一个红色,其余绿色)
    result = self.original_image.copy()
    for i, contour in enumerate(valid_contours):
        color = (0, 0, 255) if i == 0 else (0, 255, 0)
        cv2.drawContours(result, [contour], -1, color, 2)
    
    self.processed_image = result
    return True, f"检测到 {len(valid_contours)} 个液滴"

4.3 测量历史记录

python

def load_measurement_history(self):
    history_path = os.path.join(get_app_root(), "history.json")
    if os.path.exists(history_path):
        try:
            with open(history_path, 'r', encoding='utf-8') as f:
                return json.load(f)
        except:
            return []
    return []

def save_measurement(self, data):
    history = self.load_measurement_history()
    history.append(data)
    if len(history) > 100:
        history = history[-100:]   # 最多100条
    history_path = os.path.join(get_app_root(), "history.json")
    with open(history_path, 'w', encoding='utf-8') as f:
        json.dump(history, f, indent=4, ensure_ascii=False)

4.4 可调参数设置

python

class ProcessingSettings:
    def __init__(self):
        self.gaussian_kernel = 15
        self.canny_low = 50
        self.canny_high = 150
        self.min_contour_area = 100
    
    def apply_settings(self, image):
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        blurred = cv2.GaussianBlur(gray, (self.gaussian_kernel, self.gaussian_kernel), 0)
        _, thresh = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
        edges = cv2.Canny(thresh, self.canny_low, self.canny_high)
        contours, _ = cv2.findContours(edges.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        return [cnt for cnt in contours if cv2.contourArea(cnt) > self.min_contour_area]

五、性能优化

5.1 限制图像最大尺寸

python

def load_image(self, file_path):
    self.original_image = cv2.imdecode(np.fromfile(file_path, dtype=np.uint8), cv2.IMREAD_COLOR)
    if self.original_image is None:
        return False, "无法加载图像"
    
    # 限制最大2048像素,防止内存溢出
    max_size = 2048
    h, w = self.original_image.shape[:2]
    if w > max_size or h > max_size:
        scale = max_size / max(w, h)
        self.original_image = cv2.resize(self.original_image, None, fx=scale, fy=scale)
    
    self.processed_image = self.original_image.copy()
    return True, "图像加载成功"

5.2 简单的缓存机制

python

class ImageProcessor:
    def __init__(self):
        self.processed_cache = {}
    
    def auto_detect_droplet(self):
        cache_key = hash(tuple(self.region_points)) if self.region_points else "full"
        if cache_key in self.processed_cache:
            self.contour = self.processed_cache[cache_key]
            return True, "使用缓存结果"
        
        # ... 正常检测 ...
        self.processed_cache[cache_key] = self.contour
        return True, "液滴检测成功"

六、错误日志记录

python

import logging

def setup_logging():
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(levelname)s - %(message)s',
        handlers=[
            logging.FileHandler('app.log', encoding='utf-8'),
            logging.StreamHandler()
        ]
    )

# 程序入口调用
setup_logging()

# 使用示例
logging.info("应用程序启动")
logging.error(f"图像加载失败: {error_msg}")

七、未来规划

短期

  • 主题切换、工具栏自定义

  • 批量导入导出

  • 完善快捷键

中期

  • 多液滴同时检测与测量

  • 测量统计(平均值、标准差)

  • 图像增强(对比度、亮度)

长期

  • 深度学习模型提高精度

  • 云同步测量数据

  • 移动端适配


八、踩坑记录

  1. PyInstaller打包后路径问题:用sys._MEIPASS判断运行环境,动态获取资源路径

  2. 中文路径cv2.imdecode(np.fromfile(...))是必备技能

  3. 缺少字体ImageFont.load_default()兜底,避免程序崩溃

  4. 大图像内存溢出:限制最大尺寸,动态缩放

  5. 依赖版本冲突:用虚拟环境隔离,导出requirements.txt时锁定版本号


系列文章总结

至此,液滴面积测量软件的系列文章告一段落:

  1. 架构设计与项目初始化

  2. PyQt6 GUI开发实战

  3. OpenCV图像预处理

  4. 轮廓检测与面积计算

  5. 面积计算与数据导出

  6. 标尺校准机制

  7. 交互式区域选择

  8. 手动多边形圈选

  9. 用户体验优化

  10. 项目部署与扩展(本文)

最后的最后,源代码到时候会公布挂出来,有兴趣的朋友,可以在评论区留言。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

在世修行

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

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

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

打赏作者

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

抵扣说明:

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

余额充值