字典get方法返回None还是空字符串?Python默认值类型真相大曝光

第一章:字典get方法返回None还是空字符串?Python默认值类型真相大曝光

在Python中,字典的 `get` 方法常用于安全地获取键对应的值,避免因键不存在而触发 `KeyError` 异常。然而,许多开发者对 `get` 方法的默认返回值存在误解,尤其是误以为其默认返回空字符串。

get方法的基本行为

当调用字典的 `get(key)` 方法且指定的键不存在时,若未提供第二个参数,则默认返回 `None`,而非空字符串。这一点是理解字典行为的关键。
# 示例:get方法的默认返回值
data = {'name': 'Alice', 'age': 25}
print(data.get('name'))      # 输出: Alice
print(data.get('email'))     # 输出: None(不是空字符串!)
print(data.get('email', '')) # 输出: 空字符串(显式指定)
上述代码中,`data.get('email')` 返回 `None`,说明默认值为 `None`。只有显式传入空字符串作为第二个参数时,才会返回空字符串。

常见误区与最佳实践

为了避免逻辑错误,建议始终明确指定默认值类型,特别是当后续代码期望特定类型(如字符串、列表)时。
  • 若期望字符串,应使用 .get(key, "")
  • 若期望列表,应使用 .get(key, [])
  • 若需区分“键不存在”和“值为空”,保留 None 作为默认更安全
调用方式键存在键不存在(无默认值)键不存在(有默认值)
d.get('key')返回实际值None不适用
d.get('key', '')返回实际值不适用''
正确理解 `get` 方法的返回机制,有助于写出更健壮、可维护的Python代码。

第二章:深入理解字典get方法的工作机制

2.1 get方法的语法结构与参数解析

在HTTP请求中,`GET`方法用于从指定资源获取数据。其基本语法结构简洁明了,通常由请求行、请求头和空行组成。
基础语法示例
GET /api/users?id=123&role=admin HTTP/1.1
Host: example.com
Accept: application/json
上述请求通过查询字符串传递参数,`id=123` 和 `role=admin` 被附加在URL后,用于过滤用户数据。
常用查询参数说明
  • id:唯一标识资源的主键
  • limit:限制返回结果数量
  • offset:指定起始位置,用于分页
  • sort:定义排序字段与方向
参数编码规范
参数名是否必需数据类型
q字符串
page整数

2.2 默认返回值None的底层逻辑分析

在Python中,所有函数若未显式使用return语句,解释器会自动插入return None。这源于CPython虚拟机的实现机制:函数执行完毕后,栈帧(frame)必须返回一个PyObject指针,而None对应唯一的单例对象Py_None
函数返回机制示例

def empty_func():
    pass

result = empty_func()
print(result)  # 输出: None
上述代码中,empty_func无返回值,解释器在编译阶段为其字节码末尾添加LOAD_CONST NoneRETURN_VALUE指令,确保控制流结束时压入None并返回。
底层对象模型
  • Py_None是C语言层面的全局静态对象,类型为NoneType
  • 所有None引用均指向同一内存地址,节省资源;
  • 引用计数机制保障其生命周期安全。

2.3 显式指定默认值的实际应用场景

在配置驱动的应用程序中,显式指定默认值能有效避免因缺失配置导致的运行时错误。例如,在微服务架构中,某些环境变量可能未被设置,通过默认值可确保服务正常启动。
配置初始化场景
type Config struct {
    Timeout  time.Duration `env:"TIMEOUT" default:"30s"`
    Retries  int           `env:"RETRIES" default:"3"`
    Endpoint string        `env:"ENDPOINT" default:"http://localhost:8080"`
}
该结构体使用结构标签显式定义默认值。当环境变量未提供时,配置解析器会自动注入默认超时时间、重试次数和端点地址,提升系统鲁棒性。
数据库字段设计
  • 用户注册时间:默认为 CURRENT_TIMESTAMP
  • 状态字段:默认设为 'active'
  • 软删除标记:默认 false,避免误删数据
此类设计减少插入语句的冗余,同时保证数据一致性。

2.4 None与空字符串在业务逻辑中的差异

在程序设计中,None 与空字符串('')虽看似相似,但在语义上存在本质区别。None 表示“无值”或“未定义”,而空字符串是一个实际存在的、长度为0的字符串对象。
语义差异的实际影响
  • None 常用于表示字段尚未赋值或数据缺失
  • 空字符串则可能代表用户提交了空输入或默认占位
def process_name(name):
    if name is None:
        return "Unknown"
    elif name == "":
        return "Anonymous"
    else:
        return name.strip()
上述函数中,None 被解释为“未知”,而空字符串被视为“匿名”,体现了业务场景中的不同处理路径。若混淆二者,可能导致数据误判。
数据库交互中的表现
值类型数据库存储Python 反序列化
NULLNULLNone
空字符串''''

2.5 get方法与键是否存在判断的等价性验证

在字典或映射结构中,`get` 方法常用于安全获取键对应的值。当键不存在时,避免抛出异常是其核心优势。
常见实现方式对比
  • dict.get(key) 返回 None 或默认值
  • key in dict 显式判断键是否存在
cache = {'a': 1, 'b': 2}
value = cache.get('c', -1)  # 返回 -1
exists = 'c' in cache       # 返回 False
上述代码中,get 方法隐含了存在性判断逻辑。两者语义不同:前者关注“取值兜底”,后者聚焦“条件判断”。
性能与可读性权衡
方式性能可读性
get()高(单次查找)
in + 访问低(两次查找)
因此,在需要默认值场景下,get 更高效且简洁。

第三章:默认值类型的常见误区与陷阱

3.1 误将None当作空字符串处理的典型错误

在Python开发中,常有开发者误将 None 与空字符串 "" 视为等价,导致运行时异常或逻辑错误。
常见错误场景
当从数据库或API获取数据时,字段值可能为 None。若直接进行字符串操作,会引发 AttributeError

user_name = get_user_name()  # 可能返回 None
formatted = user_name.strip().title()  # AttributeError: 'NoneType' object has no attribute 'strip'
上述代码未对 None 做校验,调用字符串方法时崩溃。
安全处理方式
推荐使用条件判断或默认值机制:

user_name = get_user_name()
formatted = user_name.strip().title() if user_name else "Unknown"
此写法确保即使 user_nameNone,也能安全赋默认值,避免程序中断。

3.2 可变对象作为默认值引发的潜在问题

在 Python 中,使用可变对象(如列表、字典)作为函数参数的默认值可能导致意外的行为。因为默认值在函数定义时被**一次性初始化**,所有后续调用共享同一对象引用。
典型问题示例
def add_item(item, target_list=[]):
    target_list.append(item)
    return target_list

print(add_item("a"))  # 输出: ['a']
print(add_item("b"))  # 输出: ['a', 'b'] —— 预期应为 ['b']
上述代码中,target_list 默认指向同一个列表对象。每次调用未传参时,均操作该共享实例,导致数据累积。
安全实践建议
  • 使用 None 作为默认值占位符
  • 在函数体内初始化可变对象
修正方式:
def add_item(item, target_list=None):
    if target_list is None:
        target_list = []
    target_list.append(item)
    return target_list
此模式避免了跨调用的状态污染,确保每次调用都基于独立的新对象。

3.3 类型混淆导致的程序健壮性下降

在动态类型语言中,变量类型在运行时才确定,若缺乏严格的类型校验,极易引发类型混淆问题。此类问题常导致程序在执行数学运算、对象操作或函数调用时出现不可预期行为。
典型类型混淆场景

function add(a, b) {
  return a + b;
}
console.log(add(1, 2));     // 输出:3
console.log(add("1", 2));   // 输出:"12"
上述代码中,加法操作因类型混淆被解释为字符串拼接,而非数值相加。参数 ab 未进行类型检查,导致逻辑偏差。
防范策略
  • 使用 TypeScript 等静态类型系统提前捕获错误
  • 在关键函数入口添加类型断言或运行时校验
  • 启用严格模式(strict mode)减少隐式类型转换

第四章:最佳实践与工程应用策略

4.1 根据业务需求合理设置默认返回类型

在构建API接口时,合理设置默认返回类型能够提升系统可用性与前端对接效率。应根据核心业务场景选择最常使用的数据格式作为默认响应类型。
常见返回类型对比
  • JSON:轻量通用,适合Web和移动端交互
  • XML:结构严谨,多用于企业级或 legacy 系统
  • Protobuf:高效压缩,适用于高并发微服务通信
配置示例(Go + Gin)
// 设置默认响应为JSON
func setupRouter() *gin.Engine {
    r := gin.Default()
    r.Use(func(c *gin.Context) {
        c.Header("Content-Type", "application/json; charset=utf-8")
    })
    return r
}
上述代码通过中间件统一设置响应头,确保未显式指定格式时默认输出JSON,降低客户端解析复杂度。参数说明:application/json 声明媒体类型,charset=utf-8 防止中文乱码。

4.2 使用类型注解提升代码可读性与安全性

类型注解是现代编程语言中增强代码健壮性的关键特性。通过显式声明变量、函数参数和返回值的类型,开发者能够更清晰地表达意图,同时让工具链在编码阶段捕获潜在错误。
类型注解的基本用法
以 Python 为例,可在函数定义中添加类型提示:
def calculate_area(radius: float) -> float:
    return 3.14159 * radius ** 2
上述代码中,radius: float 表示参数为浮点数,-> float 指定返回类型。这不仅提升了可读性,还便于静态分析工具(如 mypy)进行类型检查。
类型注解带来的优势
  • 提高代码可维护性,团队协作更高效
  • IDE 能提供更精准的自动补全与错误提示
  • 减少运行时类型错误,增强程序稳定性

4.3 单元测试中对默认值的覆盖验证

在编写单元测试时,验证函数或方法对默认值的处理至关重要,尤其在配置初始化、参数缺省等场景下。
常见默认值处理模式
许多函数会为入参提供默认值,确保调用方未传参时仍能正常运行。测试应覆盖这些路径,防止逻辑遗漏。
测试用例设计示例
  • 验证未传参时是否使用预期默认值
  • 确认默认值不会意外被修改(如可变对象)
  • 检查多层级嵌套结构中的默认字段填充

func TestConfigWithDefaults(t *testing.T) {
    cfg := NewConfig(nil) // 使用默认配置
    if cfg.Timeout != 30 {
        t.Errorf("expected default timeout 30s, got %d", cfg.Timeout)
    }
    if cfg.Retries != 3 {
        t.Errorf("expected default retries 3, got %d", cfg.Retries)
    }
}
上述代码测试配置对象在 nil 输入时是否正确应用默认超时和重试次数。通过断言关键字段,确保默认行为稳定可靠。

4.4 在API设计中规范默认值的使用方式

在API设计中,合理设置参数默认值能显著提升接口的易用性和健壮性。应避免将业务逻辑强加于默认行为,确保默认值符合大多数使用场景。
默认值的设计原则
  • 保持一致性:相同语义参数在不同接口中应使用相同默认值
  • 优先使用安全值:如分页大小默认设为20而非100,防止数据泄露
  • 明确文档化:所有默认值应在API文档中清晰标注
代码示例:Go语言中的默认值处理
type QueryOptions struct {
    Limit  int `json:"limit"`
    Offset int `json:"offset"`
    Sort   string `json:"sort"`
}

func (q *QueryOptions) SetDefaults() {
    if q.Limit == 0 {
        q.Limit = 20 // 默认分页大小
    }
    if q.Sort == "" {
        q.Sort = "created_at desc" // 默认排序规则
    }
}
上述代码通过SetDefaults方法在业务逻辑前统一填充默认值,确保外部调用无需关注基础配置,同时保留显式覆盖能力。

第五章:总结与进阶思考

性能调优的实际路径
在高并发系统中,数据库查询往往是性能瓶颈的源头。通过索引优化、连接池配置和缓存策略,可显著提升响应速度。例如,在使用 Go 语言开发的服务中,合理配置 database/sql 的连接参数至关重要:
// 设置最大空闲连接数和生命周期
db.SetMaxIdleConns(10)
db.SetMaxOpenConns(100)
db.SetConnMaxLifetime(time.Hour)
微服务架构中的可观测性实践
现代分布式系统依赖日志、指标和链路追踪三位一体的监控体系。以下工具组合已被多个生产环境验证有效:
  • Prometheus:采集服务指标(如请求延迟、QPS)
  • Loki:集中式日志收集,轻量且与 PromQL 兼容
  • Jaeger:分布式链路追踪,定位跨服务调用问题
技术选型对比参考
在消息队列的选型中,不同场景适用不同方案:
系统吞吐量延迟典型场景
Kafka极高毫秒级日志聚合、事件流
RabbitMQ中等微妙至毫秒任务队列、RPC 响应
持续交付流程中的自动化测试策略
CI/CD 流程中建议分层执行测试: - 单元测试:每次提交触发,覆盖核心逻辑 - 集成测试:每日构建运行,验证服务间交互 - 端到端测试:发布前手动或定时执行,模拟用户行为
经过你上面的建议修改后主程序还是出现了下面的错误 2025-07-13 09:19:24,919 - root - DEBUG - obj_cam_operation实例方法: ['Close_device', 'Get_parameter', 'Open_device', 'Save_Bmp', 'Save_jpg', 'Set_parameter', 'Set_trigger_mode', 'Start_grabbing', 'Stop_grabbing', 'Trigger_once', 'Work_thread', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'b_exit', 'b_open_device', 'b_save_bmp', 'b_save_jpg', 'b_start_grabbing', 'b_thread_closed', 'buf_lock', 'buf_save_image', 'exposure_time', 'frame_rate', 'gain', 'h_thread_handle', 'n_connect_num', 'n_save_image_size', 'obj_cam', 'st_device_list', 'st_frame_info'] 2025-07-13 09:19:24,919 - root - ERROR - 调用open_device失败: 'CameraOperation' object has no attribute 'open_device' 下面是cam_operation的主代码请检查是不是没有定义或者说是拼写错误 # -*- coding: utf-8 -*- import threading import time import sys import inspect import ctypes import os import cv2 import numpy as np import logging from .MvCameraControl_class import * from datetime import datetime from ctypes import * from enum import Enum from ctypes import byref, cast, POINTER, c_ubyte from .MvCameraControl_class import MvCamera from .Cam_eraConstants import * from .Camera_Params_header import MV_FRAME_OUT_INFO_EX, PixelType_Gvsp_BGR8_Packed # 配置日志系统 def setup_logging(log_level=logging.INFO): """配置全局日志系统""" logging.basicConfig( level=log_level, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler("camera_operation.log"), logging.StreamHandler() ] ) logging.info("日志系统初始化完成") setup_logging(logging.INFO) # 像素格式兼容处理 try: # 尝试导入像素类型定义 from .PixelType_header import * logging.info("成功从 PixelType_header 导入像素类型定义") except ImportError: try: # 尝试从父目录导入 from .PixelType_header import * logging.info("成功从全局 PixelType_header 导入像素类型定义") except ImportError: logging.warning("警告: 无法导入 PixelType_header,创建虚拟定义") # 创建必要的虚拟定义 PixelType_Gvsp_Undefined = -1 PixelType_Gvsp_Mono8 = 0x01000008 PixelType_Gvsp_BayerGR8 = 0x01080008 PixelType_Gvsp_RGB8_Packed = 0x02180014 # 定义像素格式映射表 PIXEL_FORMATS = { "MONO8": PixelType_Gvsp_Mono8 if 'PixelType_Gvsp_Mono8' in globals() else 0x01080001, "MONO10": PixelType_Gvsp_Mono10 if 'PixelType_Gvsp_Mono10' in globals() else 0x01100003, "MONO12": PixelType_Gvsp_Mono12 if 'PixelType_Gvsp_Mono12' in globals() else 0x01100005, "BAYER_BG8": PixelType_Gvsp_BayerBG8 if 'PixelType_Gvsp_BayerBG8' in globals() else 0x0108000B, "BAYER_GB8": PixelType_Gvsp_BayerGB8 if 'PixelType_Gvsp_BayerGB8' in globals() else 0x0108000A, "BAYER_GR8": PixelType_Gvsp_BayerGR8 if 'PixelType_Gvsp_BayerGR8' in globals() else 0x01080008, "BAYER_RG8": PixelType_Gvsp_BayerRG8 if 'PixelType_Gvsp_BayerRG8' in globals() else 0x01080009, "RGB8": PixelType_Gvsp_RGB8_Packed if 'PixelType_Gvsp_RGB8_Packed' in globals() else 0x02180014, "BGR8": PixelType_Gvsp_BGR8_Packed, "YUV422": PixelType_Gvsp_YUV422_Packed if 'PixelType_Gvsp_YUV422_Packed' in globals() else 0x02100032, "YUV422_YUYV": PixelType_Gvsp_YUV422_YUYV_Packed if 'PixelType_Gvsp_YUV422_YUYV_Packed' in globals() else 0x0210001F } # 像素格式枚举 class PixelFormat(Enum): MONO8 = PIXEL_FORMATS["MONO8"] MONO10 = PIXEL_FORMATS["MONO10"] MONO12 = PIXEL_FORMATS["MONO12"] BAYER_BG8 = PIXEL_FORMATS["BAYER_BG8"] BAYER_GB8 = PIXEL_FORMATS["BAYER_GB8"] BAYER_GR8 = PIXEL_FORMATS["BAYER_GR8"] BAYER_RG8 = PIXEL_FORMATS["BAYER_RG8"] RGB8 = PIXEL_FORMATS["RGB8"] YUV422 = PIXEL_FORMATS["YUV422"] YUV422_YUYV = PIXEL_FORMATS["YUV422_YUYV"] def is_mono_data(enGvspPixelType): """判断是否为单色图像格式""" mono_formats = [ PIXEL_FORMATS["MONO8"], PIXEL_FORMATS["MONO10"], PIXEL_FORMATS["MONO12"], 0x010C0004, # Mono10 Packed 0x010C0006, # Mono12 Packed 0x01080002, # Mono8 Signed 0x0110000C # Mono16 ] return enGvspPixelType in mono_formats def is_color_data(enGvspPixelType): """判断是否为彩色图像格式""" color_formats = [ # Bayer格式 PIXEL_FORMATS["BAYER_BG8"], PIXEL_FORMATS["BAYER_GB8"], PIXEL_FORMATS["BAYER_GR8"], PIXEL_FORMATS["BAYER_RG8"], 0x01100011, # BayerBG10 0x01100010, # BayerGB10 0x0110000E, # BayerGR10 0x0110000F, # BayerRG10 0x01100017, # BayerBG12 0x01100016, # BayerGB12 0x01100014, # BayerGR12 0x01100015, # BayerRG12 # YUV格式 PIXEL_FORMATS["YUV422"], PIXEL_FORMATS["YUV422_YUYV"], # RGB格式 PIXEL_FORMATS["RGB8"], 0x0220001E, # RGB10_Packed 0x02300020, # RGB12_Packed 0x02400021, # RGB16_Packed 0x02180032 # BGR8_Packed ] return enGvspPixelType in color_formats def get_pixel_format_name(pixel_value): """根据像素值获取可读的名称""" for name, value in PIXEL_FORMATS.items(): if value == pixel_value: return name return f"未知格式: 0x{pixel_value:08X}" # 强制关闭线程 def async_raise(tid, exctype): """安全终止线程""" tid = ctypes.c_long(tid) if not inspect.isclass(exctype): exctype = type(exctype) res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype)) if res == 0: raise ValueError("invalid thread id") elif res != 1: ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None) raise SystemError("PyThreadState_SetAsyncExc failed") def stop_thread(thread): """停止指定线程""" async_raise(thread.ident, SystemExit) # ... 前面的导入和辅助函数保持不变 ... # 相机操作类 class CameraOperation: __public_methods__ = [ 'open_device', 'close_device', 'start_grabbing', 'stop_grabbing', 'set_trigger_mode', 'set_continue_mode', 'trigger_once', 'get_parameters', 'set_param', 'save_image', 'capture_frame', 'get_current_frame', 'is_frame_available' ] # 状态码定义 MV_OK = 0 MV_E_CALLORDER = -2147483647 MV_E_PARAMETER = -2147483646 MV_E_NO_DATA = -2147483645 MV_E_SAVE_IMAGE = -2147483644 MV_E_STATE = -2147483643 MV_E_HANDLE = -2147483642 MV_E_SUPPORT = -2147483641 def __init__(self, obj_cam, st_device_list, n_connect_num=0): """ 初始化相机操作对象 :param obj_cam: 相机对象 :param st_device_list: 设备列表 :param n_connect_num: 连接编号 """ self.device_handle = None self.is_open = False # 确保cam属性存在 self.cam = obj_cam # 使用cam作为相机对象的统一引用 self.obj_cam = obj_cam # 保持旧代码兼容性 self.st_device_list = st_device_list self.n_connect_num = n_connect_num # 状态标志 - 确保所有属性初始化 self.b_open_device = False self.b_start_grabbing = False self.b_thread_running = False self.b_exit = False self.connected = False # 连接状态标志 self.b_frame_received = False # 帧接收状态 # 线程相关 self.h_thread_handle = None # 图像数据 self.st_frame_info = None self.buf_save_image = None self.n_save_image_size = 0 self.current_frame = None # 参数 self.frame_rate = 0.0 self.exposure_time = 0.0 self.gain = 0.0 # 线程安全锁 self.buf_lock = threading.Lock() # 图像缓冲区锁 self.frame_lock = threading.Lock() # 当前帧锁 self.param_lock = threading.Lock() # 参数锁 self.frame_count = 0 # 帧计数 self.last_frame_time = None # 最后帧时间 self.is_streaming = False # 取流状态 logging.info("相机操作对象初始化完成") def is_frame_available(self): """检查是否有可用帧图像""" try: # 检查是否有有效帧图像 with self.frame_lock: return self.current_frame is not None and self.current_frame.size > 0 except Exception as e: logging.error(f"检查帧可用性失败: {str(e)}") return False def capture_frame(self): """捕获当前帧""" try: with self.frame_lock: if self.current_frame is not None: return self.current_frame.copy() return None except Exception as e: logging.error(f"捕获帧失败: {str(e)}") return None def get_current_frame(self): """获取当前帧图像""" try: with self.frame_lock: if self.current_frame is not None: return self.current_frame.copy() return None except Exception as e: logging.error(f"获取图像失败: {str(e)}") return None def get_frame_info(self): """ 获取当前帧的详细信息 :return: 帧信息字典 """ info = { "available": False, "width": 0, "height": 0, "format": "未知", "size": 0 } try: with self.frame_lock: if self.current_frame is not None: info["available"] = True info["width"] = self.current_frame.shape[1] info["height"] = self.current_frame.shape[0] info["format"] = f"{self.current_frame.dtype}" info["size"] = self.current_frame.size if self.st_frame_info: info["sdk_width"] = self.st_frame_info.nWidth info["sdk_height"] = self.st_frame_info.nHeight info["sdk_format"] = get_pixel_format_name(self.st_frame_info.enPixelType) info["sdk_size"] = self.st_frame_info.nFrameLen except Exception as e: logging.exception(f"获取帧信息异常: {str(e)}") return info def set_pixel_format(self, format_name): if format_name in PIXEL_FORMATS: format_value = PIXEL_FORMATS[format_name] ret = self.obj_cam.MV_CC_SetPixelType(format_value) if ret != self.MV_OK: logging.error(f"设置像素格式失败: {hex(ret)}") return ret else: logging.error(f"不支持的像素格式: {format_name}") return self.MV_E_PARAMETER def save_frame_to_file(self, frame, file_path, save_format="bmp", quality=95): """ 将帧保存到文件 :param frame: 要保存的帧 (numpy数组) :param file_path: 文件路径 :param save_format: 保存格式 (bmp, jpg, png, tiff) :param quality: 保存质量 (仅对jpg有效) :return: 保存结果 (MV_OK 或 错误码) """ if frame is None or frame.size == 0: logging.error("无法保存无效帧: 帧为") return self.MV_E_NO_DATA try: # 确保目录存在 directory = os.path.dirname(file_path) if directory and not os.path.exists(directory): os.makedirs(directory, exist_ok=True) # 根据格式保存图像 save_format = save_format.lower() if save_format == "bmp": cv2.imwrite(file_path, frame) elif save_format in ["jpg", "jpeg"]: cv2.imwrite(file_path, frame, [cv2.IMWRITE_JPEG_QUALITY, quality]) elif save_format == "png": cv2.imwrite(file_path, frame, [cv2.IMWRITE_PNG_COMPRESSION, 9]) elif save_format in ["tiff", "tif"]: cv2.imwrite(file_path, frame, [cv2.IMWRITE_TIFF_COMPRESSION, 1]) else: logging.error(f"不支持的图像格式: {save_format}") return self.MV_E_PARAMETER # 验证保存结果 if not os.path.exists(file_path): logging.error(f"文件保存失败: {file_path}") return self.MV_E_SAVE_IMAGE file_size = os.path.getsize(file_path) if file_size < 1024: # 小于1KB视为无效 logging.error(f"文件大小异常: {file_path} ({file_size} 字节)") os.remove(file_path) # 删除无效文件 return self.MV_E_SAVE_IMAGE logging.info(f"图像已保存: {file_path} ({file_size} 字节)") return self.MV_OK except Exception as e: logging.exception(f"保存图像异常: {str(e)}") return self.MV_E_SAVE_IMAGE def start_grabbing(self, hwnd=None): """开始取流""" ret = self.cam.start_grabbing(hwnd) if ret == MV_OK: self.is_grabbing = True return ret def stop_grabbing(self): """停止取流""" # 关键修复:添加状态检查 if not hasattr(self, 'cam') or self.cam is None: logging.error("无法停止采集:相机对象不存在") return self.MV_E_STATE if not self.connected: logging.error("无法停止采集:相机未连接") return self.MV_E_STATE try: ret = self.cam.stop_grabbing() if ret == MV_OK: self.is_grabbing = False self.last_frame = None # 清除最后帧 self.image_buffer = [] # 清缓冲区 return ret except Exception as e: logging.exception(f"停止采集时发生异常: {e}") return self.MV_E_STATE def is_ready(self): """检查设备是否就绪""" return self.b_open_device and not self.b_exit def open_device(self, device_index=0, access_mode=0): """打开相机设备""" if self.b_open_device: logging.warning("设备已打开,无需重复操作") return self.MV_OK if self.n_connect_num < 0: logging.error("无效的连接编号") return self.MV_E_CALLORDER try: # 选择设备并创建句柄 nConnectionNum = int(self.n_connect_num) stDeviceList = cast(self.st_device_list.pDeviceInfo[int(nConnectionNum)], POINTER(MV_CC_DEVICE_INFO)).contents # 确保相机对象存在 if self.obj_cam is None: self.obj_cam = MvCamera() self.cam = self.obj_cam # 确保cam属性存在 # 创建相机句柄 ret = self.obj_cam.MV_CC_CreateHandle(stDeviceList) if ret != self.MV_OK: self.obj_cam.MV_CC_DestroyHandle() logging.error(f"创建句柄失败: {hex(ret)}") return ret # 打开设备 ret = self.obj_cam.MV_CC_OpenDevice() if ret != self.MV_OK: logging.error(f"打开设备失败: {hex(ret)}") return ret # 设备已成功打开 self.b_open_device = True self.connected = True # 设置连接状态 self.b_exit = False logging.info("设备打开成功") # 探测网络最佳包大小(仅对GigE相机有效) if stDeviceList.nTLayerType in [MV_GIGE_DEVICE, MV_GENTL_GIGE_DEVICE]: nPacketSize = self.obj_cam.MV_CC_GetOptimalPacketSize() if int(nPacketSize) > 0: ret = self.obj_cam.MV_CC_SetIntValue("GevSCPSPacketSize", nPacketSize) if ret != self.MV_OK: logging.warning(f"设置包大小失败: {hex(ret)}") else: logging.warning(f"获取最佳包大小失败: {hex(nPacketSize)}") # 获取采集帧率使能状态 stBool = c_bool(False) ret = self.obj_cam.MV_CC_GetBoolValue("AcquisitionFrameRateEnable", stBool) if ret != self.MV_OK: logging.warning(f"获取帧率使能状态失败: {hex(ret)}") # 现在设备已打开,可以安全设置触发模式 ret = self.set_continue_mode() if ret != self.MV_OK: logging.warning(f"设置连续模式失败: {hex(ret)}") return self.MV_OK except Exception as e: logging.exception(f"打开设备异常: {str(e)}") # 尝试清理资源 try: if hasattr(self, 'obj_cam'): self.obj_cam.MV_CC_CloseDevice() self.obj_cam.MV_CC_DestroyHandle() except: pass self.b_open_device = False self.connected = False return self.MV_E_STATE def close_device(self): """关闭相机设备""" if not self.b_open_device: logging.warning("设备未打开,无需关闭") return self.MV_OK try: # 停止采集(如果正在采集) if self.b_start_grabbing: self.stop_grabbing() # 关闭设备 ret = self.obj_cam.MV_CC_CloseDevice() if ret != self.MV_OK: logging.error(f"关闭设备失败: {hex(ret)}") # 销毁句柄 ret = self.obj_cam.MV_CC_DestroyHandle() if ret != self.MV_OK: logging.error(f"销毁句柄失败: {hex(ret)}") self.b_open_device = False self.connected = False # 重置连接状态 self.b_exit = True logging.info("设备关闭成功") return self.MV_OK except Exception as e: logging.exception(f"关闭设备异常: {str(e)}") return self.MV_E_STATE def start_grabbing(self, winHandle=None): """开始图像采集""" if not self.b_open_device: logging.error("设备未打开,无法开始采集") return self.MV_E_CALLORDER if self.b_start_grabbing: logging.warning("采集已在进行中") return self.MV_OK try: # 开始采集 ret = self.obj_cam.MV_CC_StartGrabbing() if ret != self.MV_OK: logging.error(f"开始采集失败: {hex(ret)}") return ret self.b_start_grabbing = True self.b_exit = False # 启动采集线程 self.h_thread_handle = threading.Thread( target=self.work_thread, args=(winHandle,), daemon=True ) self.h_thread_handle.start() self.b_thread_running = True logging.info("图像采集已开始") return self.MV_OK except Exception as e: logging.exception(f"开始采集异常: {str(e)}") return self.MV_E_STATE def set_trigger_mode(self, enable=True, source=MV_TRIGGER_SOURCE_SOFTWARE): """ 设置触发模式 :param enable: 是否启用触发模式 :param source: 触发源 (默认软件触发) :return: 操作结果 """ if not self.b_open_device: logging.error("设备未打开,无法设置触发模式") return self.MV_E_CALLORDER try: mode = MV_TRIGGER_MODE_ON if enable else MV_TRIGGER_MODE_OFF ret = self.obj_cam.MV_CC_SetEnumValue("TriggerMode", mode) if ret != self.MV_OK: logging.error(f"设置触发模式失败: {hex(ret)}") return ret if enable: ret = self.obj_cam.MV_CC_SetEnumValue("TriggerSource", source) if ret != self.MV_OK: logging.error(f"设置触发源失败: {hex(ret)}") return ret logging.info(f"触发模式设置成功: {'启用' if enable else '禁用'}") return self.MV_OK except Exception as e: logging.exception(f"设置触发模式异常: {str(e)}") return self.MV_E_STATE def set_continue_mode(self): """设置连续采集模式""" # 添加设备状态检查 if not self.b_open_device: logging.error("设备未打开,无法设置连续模式") return self.MV_E_CALLORDER logging.info("尝试设置连续采集模式") try: # 禁用触发模式 ret = self.obj_cam.MV_CC_SetEnumValue("TriggerMode", MV_TRIGGER_MODE_OFF) if ret != self.MV_OK: logging.error(f"禁用触发模式失败: {hex(ret)}") return ret # 设置采集模式为连续 ret = self.obj_cam.MV_CC_SetEnumValue("AcquisitionMode", 2) # 2表示连续采集 if ret != self.MV_OK: logging.error(f"设置连续采集模式失败: {hex(ret)}") return ret logging.info("连续采集模式设置成功") return self.MV_OK except Exception as e: logging.exception(f"设置连续模式异常: {str(e)}") return self.MV_E_STATE def trigger_once(self): """执行一次软触发""" if not self.b_open_device: logging.error("设备未打开,无法触发") return self.MV_E_CALLORDER try: ret = self.obj_cam.MV_CC_SetCommandValue("TriggerSoftware") if ret != self.MV_OK: logging.error(f"软触发失败: {hex(ret)}") return ret logging.info("软触发成功") return self.MV_OK except Exception as e: logging.exception(f"软触发异常: {str(e)}") return self.MV_E_STATE def get_parameters(self): """获取相机参数""" if not self.b_open_device: logging.error("设备未打开,无法获取参数") return self.MV_E_CALLORDER try: # 使用锁保护参数访问 with self.param_lock: # 初始化返回值为整数错误码 return_code = self.MV_OK # 帧率 stFrameRate = MVCC_FLOATVALUE() memset(byref(stFrameRate), 0, sizeof(MVCC_FLOATVALUE)) ret = self.obj_cam.MV_CC_GetFloatValue("AcquisitionFrameRate", stFrameRate) if ret == self.MV_OK: self.frame_rate = stFrameRate.fCurValue logging.debug(f"获取帧率成功: {self.frame_rate}") else: logging.warning(f"获取帧率失败: {hex(ret)}") if return_code == self.MV_OK: # 保留第一个错误码 return_code = ret # 曝光时间 stExposure = MVCC_FLOATVALUE() memset(byref(stExposure), 0, sizeof(MVCC_FLOATVALUE)) ret = self.obj_cam.MV_CC_GetFloatValue("ExposureTime", stExposure) if ret == self.MV_OK: self.exposure_time = stExposure.fCurValue logging.debug(f"获取曝光时间成功: {self.exposure_time}") else: logging.warning(f"获取曝光时间失败: {hex(ret)}") if return_code == self.MV_OK: return_code = ret # 增益 stGain = MVCC_FLOATVALUE() memset(byref(stGain), 0, sizeof(MVCC_FLOATVALUE)) ret = self.obj_cam.MV_CC_GetFloatValue("Gain", stGain) if ret == self.MV_OK: self.gain = stGain.fCurValue logging.debug(f"获取增益成功: {self.gain}") else: logging.warning(f"获取增益失败: {hex(ret)}") if return_code == self.MV_OK: return_code = ret # 返回整数错误码 return return_code except Exception as e: logging.exception(f"获取参数异常: {str(e)}") return self.MV_E_STATE # 添加兼容拼写错误的方法 def get_parame(self): """兼容拼写错误的方法名:get_parame""" logging.warning("调用get_parame方法 - 可能是拼写错误,建议使用get_parameters()") return self.get_parameters() # 添加动态属性处理 def __getattr__(self, name): # 处理保存方法 if name == "save_image": logging.warning("动态处理save_image调用 - 映射到save_image") return self.save_image if name == "save_bmp": logging.warning("动态处理save_bmp调用 - 映射到save_bmp") return self.save_bmp # 处理其他可能的拼写错误 method_map = { "get_parame": self.get_parame, "get_parm": self.get_parameters, "get_parmeter": self.get_parameters, "get_parma": self.get_parameters, "get_parameter": self.get_parameters, "get_param": self.get_parameters } if name in method_map: logging.warning(f"动态处理{name}调用 - 可能是拼写错误") return method_map[name] # 关键修复:处理b_frame_received属性 if name == "b_frame_received": logging.warning("动态访问b_frame_received属性 - 提供默认值") return False # 关键修复:处理cam属性 if name == "cam": if not hasattr(self, 'obj_cam') or self.obj_cam is None: raise AttributeError( f"'{type(self).__name__}' 对象没有 'cam' 属性。" "可能原因: 1) 相机未初始化 2) 连接未建立" ) return self.obj_cam raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'") def set_param(self, frame_rate=None, exposure_time=None, gain=None): """ 设置相机参数 :param frame_rate: 帧率 (None表示不修改) :param exposure_time: 曝光时间 (None表示不修改) :param gain: 增益 (None表示不修改) :return: 操作结果 """ if not self.b_open_device: logging.error("设备未打开,无法设置参数") return self.MV_E_CALLORDER try: # 使用锁保护参数修改 with self.param_lock: # 禁用自动曝光 ret = self.obj_cam.MV_CC_SetEnumValue("ExposureAuto", 0) if ret != self.MV_OK: logging.warning(f"禁用自动曝光失败: {hex(ret)}") # 设置帧率 if frame_rate is not None: ret = self.obj_cam.MV_CC_SetFloatValue("AcquisitionFrameRate", float(frame_rate)) if ret != self.MV_OK: logging.error(f"设置帧率失败: {hex(ret)}") return ret self.frame_rate = float(frame_rate) # 设置曝光时间 if exposure_time is not None: ret = self.obj_cam.MV_CC_SetFloatValue("ExposureTime", float(exposure_time)) if ret != self.MV_OK: logging.error(f"设置曝光时间失败: {hex(ret)}") return ret self.exposure_time = float(exposure_time) # 设置增益 if gain is not None: ret = self.obj_cam.MV_CC_SetFloatValue("Gain", float(gain)) if ret != self.MV_OK: logging.error(f"设置增益失败: {hex(ret)}") return ret self.gain = float(gain) logging.info(f"参数设置成功: 帧率={self.frame_rate}, 曝光={self.exposure_time}, 增益={self.gain}") return self.MV_OK except Exception as e: logging.exception(f"设置参数异常: {str(e)}") return self.MV_E_STATE def work_thread(self, winHandle=None): """图像采集工作线程""" stOutFrame = MV_FRAME_OUT() memset(byref(stOutFrame), 0, sizeof(stOutFrame)) logging.info("采集线程启动") while not self.b_exit: try: # 获取图像缓冲区 ret = self.obj_cam.MV_CC_GetImageBuffer(stOutFrame, 1000) if ret != self.MV_OK: if ret != self.MV_E_NO_DATA: # 忽略无数据错误 logging.warning(f"获取图像缓冲区失败: {hex(ret)}") time.sleep(0.01) continue # 更新帧信息 self.st_frame_info = stOutFrame.stFrameInfo # 关键修复:标记帧已接收 self.b_frame_received = True # 记录像素格式信息 pixel_format = get_pixel_format_name(self.st_frame_info.enPixelType) logging.debug(f"获取到图像: {self.st_frame_info.nWidth}x{self.st_frame_info.nHeight}, " f"格式: {pixel_format}, 大小: {self.st_frame_info.nFrameLen}字节") # 分配/更新图像缓冲区 frame_size = stOutFrame.stFrameInfo.nFrameLen with self.buf_lock: if self.buf_save_image is None or self.n_save_image_size < frame_size: self.buf_save_image = (c_ubyte * frame_size)() self.n_save_image_size = frame_size # 复制图像数据 cdll.msvcrt.memcpy(byref(self.buf_save_image), stOutFrame.pBufAddr, frame_size) # 更新当前帧 self.update_current_frame() # 显示图像(如果指定了窗口句柄) if winHandle is not None: stDisplayParam = MV_DISPLAY_FRAME_INFO() memset(byref(stDisplayParam), 0, sizeof(stDisplayParam)) stDisplayParam.hWnd = int(winHandle) stDisplayParam.nWidth = self.st_frame_info.nWidth stDisplayParam.nHeight = self.st_frame_info.nHeight stDisplayParam.enPixelType = self.st_frame_info.enPixelType stDisplayParam.pData = self.buf_save_image stDisplayParam.nDataLen = frame_size self.obj_cam.MV_CC_DisplayOneFrame(stDisplayParam) # 释放图像缓冲区 self.obj_cam.MV_CC_FreeImageBuffer(stOutFrame) except Exception as e: logging.exception(f"采集线程异常: {str(e)}") time.sleep(0.1) # 清理资源 with self.buf_lock: if self.buf_save_image is not None: del self.buf_save_image self.buf_save_image = None self.n_save_image_size = 0 logging.info("采集线程退出") def update_current_frame(self): """将原始图像数据转换为OpenCV格式并存储""" if not self.st_frame_info or not self.buf_save_image: logging.warning("更新当前帧时缺少帧信息或缓冲区") return try: # 获取图像信息 width = self.st_frame_info.nWidth height = self.st_frame_info.nHeight pixel_type = self.st_frame_info.enPixelType # 复制缓冲区数据 with self.buf_lock: buffer_copy = bytearray(self.buf_save_image) # 转换为numpy数组 np_buffer = np.frombuffer(buffer_copy, dtype=np.uint8) # 根据像素类型处理图像 frame = None if is_mono_data(pixel_type): # 单色图像 frame = np_buffer.reshape(height, width) elif pixel_type == PIXEL_FORMATS["BAYER_BG8"]: # Bayer BG格式 frame = cv2.cvtColor(np_buffer.reshape(height, width), cv2.COLOR_BayerBG2RGB) elif pixel_type == PIXEL_FORMATS["BAYER_GB8"]: # Bayer GB格式 frame = cv2.cvtColor(np_buffer.reshape(height, width), cv2.COLOR_BayerGB2RGB) elif pixel_type == PIXEL_FORMATS["BAYER_GR8"]: # Bayer GR格式 frame = cv2.cvtColor(np_buffer.reshape(height, width), cv2.COLOR_BayerGR2RGB) elif pixel_type == PIXEL_FORMATS["BAYER_RG8"]: # Bayer RG格式 frame = cv2.cvtColor(np_buffer.reshape(height, width), cv2.COLOR_BayerRG2RGB) elif pixel_type == PIXEL_FORMATS["RGB8"]: # RGB格式 frame = np_buffer.reshape(height, width, 3) elif pixel_type in [PIXEL_FORMATS["YUV422"], PIXEL_FORMATS["YUV422_YUYV"]]: # YUV格式 frame = cv2.cvtColor(np_buffer.reshape(height, width, 2), cv2.COLOR_YUV2RGB_YUYV) else: # 尝试自动处理其他格式 if pixel_type in [PIXEL_FORMATS["MONO10"], PIXEL_FORMATS["MONO12"]]: # 10位或12位单色图像需要特殊处理 frame = self.process_high_bit_depth(np_buffer, width, height, pixel_type) else: logging.warning(f"不支持的像素格式: {get_pixel_format_name(pixel_type)}") return # 更新当前帧 - 使用线程安全锁 with self.frame_lock: self.current_frame = frame logging.debug(f"当前帧更新成功: {frame.shape if frame is not None else '无数据'}") except Exception as e: logging.exception(f"更新当前帧异常: {str(e)}") # 调试:保存原始数据用于分析 try: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") debug_path = f"frame_debug_{timestamp}.bin" with open(debug_path, "wb") as f: f.write(buffer_copy) logging.info(f"已保存原始帧数据到: {debug_path}") except: logging.error("保存调试帧数据失败") def process_high_bit_depth(self, buffer, width, height, pixel_type): """处理高位深度图像格式""" try: # 10位或12位图像处理 if pixel_type == PIXEL_FORMATS["MONO10"]: # 将10位数据转换为16位 data_16bit = np.frombuffer(buffer, dtype=np.uint16) # 10位数据存储方式:每个像素占用2字节,但只有10位有效 data_16bit = (data_16bit >> 6) # 右移6位使10位数据对齐到低10位 frame = data_16bit.reshape(height, width).astype(np.uint16) elif pixel_type == PIXEL_FORMATS["MONO12"]: # 将12位数据转换为16位 data_16bit = np.frombuffer(buffer, dtype=np.uint16) # 12位数据存储方式:每个像素占用2字节,但只有12位有效 data_16bit = (data_16bit >> 4) # 右移4位使12位数据对齐到低12位 frame = data_16bit.reshape(height, width).astype(np.uint16) else: logging.warning(f"不支持的高位深度格式: {get_pixel_format_name(pixel_type)}") return None # 归一化到8位用于显示(如果需要) frame_8bit = cv2.convertScaleAbs(frame, alpha=(255.0/4095.0)) return frame_8bit except Exception as e: logging.exception(f"处理高位深度图像异常: {str(e)}") return None def save_image(self, file_path, save_format="bmp", quality=95): """ 安全保存当前帧到文件 - 使用原始缓冲区数据 """ if not self.b_open_device or not self.b_start_grabbing: logging.error("设备未就绪,无法保存图像") return self.MV_E_CALLORDER # 使用缓冲区锁确保数据一致性 with self.buf_lock: if not self.buf_save_image or not self.st_frame_info: logging.error("无可用图像数据") return self.MV_E_NO_DATA # 获取图像信息 width = self.st_frame_info.nWidth height = self.st_frame_info.nHeight pixel_type = self.st_frame_info.enPixelType frame_size = self.st_frame_info.nFrameLen # 复制缓冲区数据 buffer_copy = bytearray(self.buf_save_image) try: # 确保目录存在 directory = os.path.dirname(file_path) if directory and not os.path.exists(directory): os.makedirs(directory, exist_ok=True) logging.info(f"创建目录: {directory}") # 根据像素类型处理图像 np_buffer = np.frombuffer(buffer_copy, dtype=np.uint8) # 根据像素格式转换图像 if is_mono_data(pixel_type): # 单色图像 frame = np_buffer.reshape(height, width) elif pixel_type == PIXEL_FORMATS["BAYER_BG8"]: # Bayer BG格式 frame = cv2.cvtColor(np_buffer.reshape(height, width), cv2.COLOR_BayerBG2BGR) elif pixel_type == PIXEL_FORMATS["BAYER_GB8"]: # Bayer GB格式 frame = cv2.cvtColor(np_buffer.reshape(height, width), cv2.COLOR_BayerGB2BGR) elif pixel_type == PIXEL_FORMATS["BAYER_GR8"]: # Bayer GR格式 frame = cv2.cvtColor(np_buffer.reshape(height, width), cv2.COLOR_BayerGR2BGR) elif pixel_type == PIXEL_FORMATS["BAYER_RG8"]: # Bayer RG格式 frame = cv2.cvtColor(np_buffer.reshape(height, width), cv2.COLOR_BayerRG2BGR) elif pixel_type == PIXEL_FORMATS["RGB8"]: # RGB格式 frame = np_buffer.reshape(height, width, 3) frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR) elif pixel_type in [PIXEL_FORMATS["YUV422"], PIXEL_FORMATS["YUV422_YUYV"]]: # YUV格式 frame = cv2.cvtColor(np_buffer.reshape(height, width, 2), cv2.COLOR_YUV2BGR_YUYV) else: # 尝试自动处理其他格式 if pixel_type in [PIXEL_FORMATS["MONO10"], PIXEL_FORMATS["MONO12"]]: # 10位或12位单色图像需要特殊处理 frame = self.process_high_bit_depth(np_buffer, width, height, pixel_type) else: logging.error(f"不支持的像素格式: {get_pixel_format_name(pixel_type)}") return self.MV_E_PARAMETER # 根据格式保存图像 save_format = save_format.lower() try: if save_format == "bmp": cv2.imwrite(file_path, frame) elif save_format in ["jpg", "jpeg"]: cv2.imwrite(file_path, frame, [cv2.IMWRITE_JPEG_QUALITY, quality]) elif save_format == "png": cv2.imwrite(file_path, frame, [cv2.IMWRITE_PNG_COMPRESSION, 9]) elif save_format in ["tiff", "tif"]: cv2.imwrite(file_path, frame, [cv2.IMWRITE_TIFF_COMPRESSION, 1]) else: logging.error(f"不支持的图像格式: {save_format}") return self.MV_E_PARAMETER # 验证保存结果 if not os.path.exists(file_path): logging.error(f"文件保存失败: {file_path}") return self.MV_E_SAVE_IMAGE file_size = os.path.getsize(file_path) if file_size < 1024: # 小于1KB视为无效 logging.error(f"文件大小异常: {file_path} ({file_size} 字节)") os.remove(file_path) # 删除无效文件 return self.MV_E_SAVE_IMAGE logging.info(f"图像已保存: {file_path} ({file_size} 字节)") return self.MV_OK except Exception as e: logging.exception(f"保存图像异常: {str(e)}") return self.MV_E_SAVE_IMAGE except Exception as e: logging.exception(f"图像处理异常: {str(e)}") return self.MV_E_SAVE_IMAGE # 兼容旧方法的保存接口 def save_jpg(self, file_path=None, quality=95): """保存为JPEG格式""" if file_path is None: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") file_path = f"capture_{timestamp}.jpg" return self.save_image(file_path, "jpg", quality) def save_bmp(self, file_path=None): """保存为BMP格式""" if file_path is None: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") file_path = f"capture_{timestamp}.bmp" return self.save_image(file_path, "bmp") def save_png(self, file_path=None): """保存为PNG格式""" if file_path is None: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") file_path = f"capture_{timestamp}.png" return self.save_image(file_path, "png") def save_tiff(self, file_path=None): """保存为TIFF格式""" if file_path is None: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") file_path = f"capture_{timestamp}.tiff" return self.save_image(file_path, "tiff") # 新增方法:获取帧状态详细信息 def get_frame_status(self): """获取当前帧状态详细信息""" # 安全访问b_frame_received属性 frame_received = getattr(self, 'b_frame_received', False) status = { "camera_open": self.b_open_device, "grabbing_started": self.b_start_grabbing, "thread_running": self.b_thread_running, "frame_received": frame_received, # 使用安全访问 "frame_size": self.n_save_image_size if self.buf_save_image else 0, "current_frame": self.current_frame is not None, "connected": self.connected # 新增连接状态 } if self.st_frame_info: status.update({ "width": self.st_frame_info.nWidth, "height": self.st_frame_info.nHeight, "pixel_format": get_pixel_format_name(self.st_frame_info.enPixelType), "frame_num": self.st_frame_info.nFrameNum }) return status # 析构函数确保资源释放 def __del__(self): """确保相机资源被正确释放""" try: if self.b_start_grabbing: self.stop_grabbing() if self.b_open_device: self.close_device() except Exception as e: logging.error(f"析构函数中释放资源失败: {str(e)}")
07-14
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值