代码写完了,怎么给别人用?
虚拟环境、依赖管理、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 依赖清单
| 依赖 | 版本 | 用途 |
|---|---|---|
| PyQt6 | 6.11.0 | GUI框架 |
| opencv-python | 4.9.0.80 | 图像处理 |
| numpy | 1.26.3 | 数值计算 |
| Pillow | 10.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}")
七、未来规划
短期
-
主题切换、工具栏自定义
-
批量导入导出
-
完善快捷键
中期
-
多液滴同时检测与测量
-
测量统计(平均值、标准差)
-
图像增强(对比度、亮度)
长期
-
深度学习模型提高精度
-
云同步测量数据
-
移动端适配
八、踩坑记录
-
PyInstaller打包后路径问题:用
sys._MEIPASS判断运行环境,动态获取资源路径 -
中文路径:
cv2.imdecode(np.fromfile(...))是必备技能 -
缺少字体:
ImageFont.load_default()兜底,避免程序崩溃 -
大图像内存溢出:限制最大尺寸,动态缩放
-
依赖版本冲突:用虚拟环境隔离,导出
requirements.txt时锁定版本号
系列文章总结
至此,液滴面积测量软件的系列文章告一段落:
-
架构设计与项目初始化
-
PyQt6 GUI开发实战
-
OpenCV图像预处理
-
轮廓检测与面积计算
-
面积计算与数据导出
-
标尺校准机制
-
交互式区域选择
-
手动多边形圈选
-
用户体验优化
-
项目部署与扩展(本文)
最后的最后,源代码到时候会公布挂出来,有兴趣的朋友,可以在评论区留言。
2765

被折叠的 条评论
为什么被折叠?



