软件测试学习 之 Python os._exit()&sys.exit()、exit(0)&exit(1) 的用法和区别

本文深入解析Python中的os._exit()与sys.exit()两种程序退出方式的用法与区别,包括它们如何影响程序流程及异常处理机制。同时,对比了exit(0)与exit(1)在退出状态上的差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载说明:
-------------------- 
python中 os._exit() 和 sys.exit(), exit(0)和exit(1) 的用法和区别
作者:everest33
出处:博客园
-------------------- 

目录

概述

Python的程序有两中退出方式:os._exit()sys.exit()。本文主要介绍这两种方式的区别和选择。

os._exit() vs sys.exit()

os._exit()会直接将python程序终止,之后的所有代码都不会继续执行。

sys.exit()会引发一个异常:SystemExit,如果这个异常没有被捕获,那么python解释器将会退出。如果有捕获此异常的代码,那么这些代码还是会执行。捕获这个异常可以做一些额外的清理工作。0为正常退出,其他数值(1-127)为不正常,可抛异常事件供捕获。

举例说明

os._exit()
print("os._exit(0):")
try:
    os._exit(0)
    # pass
except BaseException as ex:
    print(ex)
    print('die')
finally:
    print('cleanup')

pycharm编辑器中,os._exit(0)处会有告警:
Access to a protected member _exit of a module
(访问了模块受保护的成员_exit

并且,结果不会打出”die”,也不会打印“cleanup

os._exit(0):
sys._exit()
print("sys.exit(0):")
try:
    sys.exit(0)
except SystemExit as sys_exit:
    print(sys_exit)
    print('die')
finally:
    print('cleanup')

执行结果:

sys.exit(0):
0
die
cleanup

os._exit()和sys._exit()的区别

综上,

  • sys.exit()的退出比较优雅,调用后会引发SystemExit异常,可以捕获此异常做清理工作。
    os._exit()直接将python解释器退出,余下的语句不会执行。
  • 一般情况下使用sys.exit()即可,一般在fork出来的子进程中使用os._exit()
    一般来说,os._exit()用于在线程中退出sys.exit()用于在主线程中退出。
  • exit()跟 C 语言等其他语言的exit()应该是一样的。 os._exit()调用C语言的_exit()函数。
  • builtin.exit是一个Quitter对象,这个对象的call方法会抛出一个SystemExit异常。

exit(0) vs exit(1)

exit(0):无错误退出
exit(1):有错误退出
退出代码是告诉解释器的(或操作系统)
实际执行结果与sys.exit()类似,同样会抛出SystemExit异常

print("exit(0):")
try:
    exit(0)
except SystemExit as sys_exit:
    print(sys_exit)
    print('die')
finally:
    print('cleanup')
print()

print("exit(1):")
try:
    exit(1)
except SystemExit as sys_exit:
    print(sys_exit)
    print('die')
finally:
    print('cleanup')

执行结果:

exit(0):
0
die
cleanup

exit(1):
1
die
cleanup
import os import datetime import shutil import subprocess import re import platform import hashlib def get_commit_id(): cmd = ['git', 'rev-parse', 'HEAD'] try: result = subprocess.check_output(cmd) commit_id = result.decode().strip() if not commit_id: raise Exception('commit id not found') return commit_id except Exception as e: print(e) raise e def remove_pycache(path): for root, dirs, files in os.walk(path): for dir in dirs: if dir == '__pycache__': pycache_dir = os.path.join(root, dir) shutil.rmtree(pycache_dir) def use_shell(): if platform.system() == "Linux": return False return True class SdkPacker: def __init__(self): self.starttime = datetime.datetime.now() self.record('*' * 10 + 'SDK pack init' + '*' * 10) # pip self.pipUrl = os.getenv('SDK_PIP_URL') self.pipTrustHost = os.getenv('SDK_PIP_TRUST_HOST') self.pipArgs = os.getenv('SDK_PIP_ARGS') self.record(f'pipUrl: {self.pipUrl}') self.record(f'pipTrustHost: {self.pipTrustHost}') self.record(f'pipArgs: {self.pipArgs}') # sdk path self.sdkPath = os.path.dirname(os.path.abspath(__file__)) self.outPath = os.path.join(self.sdkPath, 'out') self.enginePath = os.path.join(self.outPath, 'engine') self.remove_path(os.path.join(self.sdkPath, 'build')) self.remove_path(os.path.join(self.sdkPath, 'rpa', 'build')) self.remove_path(self.enginePath) # commit id self.commitId = get_commit_id() self.record(f"commit id:{self.commitId}") # cache path self.cachePath = os.path.join(self.sdkPath, 'out', 'cache') self.cacheZipPath = os.path.join(self.cachePath, f'{self.commitId}.7z') # env self.RPA_PACK_PLATFORM = self.get_env('RPA_PACK_PLATFORM') self.RPA_PACK_ARCH = self.get_env('RPA_PACK_ARCH') self.RPA_VERSION = self.get_env('RPA_PACK_VERSION') if not self.RPA_VERSION or not re.search(r"\d", self.RPA_VERSION): self.RPA_VERSION = "15.0.0" self.RPA_FORCE_REBUILD = self.get_env('RPA_PACK_FORCE_REBUILD') self.platform = platform.system() self.record(f"System: {self.platform}") # tools path self.sdkToolsPath = os.path.join(self.sdkPath, 'out', 'sdk_tools') # output path self.python_out = os.path.join(self.enginePath) if self.RPA_PACK_PLATFORM == 'windows': self.reqsPath = os.path.join(self.sdkPath, 'rpa', 'requirements.txt') self.site_packages = os.path.join(self.enginePath, 'Lib', 'site-packages') elif self.RPA_PACK_PLATFORM in ['linux', 'UOS', 'kylinOS']: self.reqsPath = os.path.join(self.sdkPath, 'rpa', 'requirements_uos.txt') self.site_packages = os.path.join(self.enginePath, 'lib', 'python3.7', 'site-packages') else: raise Exception(f'not support platform: {self.RPA_PACK_PLATFORM} and arch: {self.RPA_PACK_ARCH}') self.seven_zip_out = os.path.join(self.site_packages, 'rpa', 'file_folder') self.ffmpeg_out = os.path.join(self.site_packages, 'rpa', 'win32') self.db2_out = os.path.join(self.site_packages) self.db2_cli_out = os.path.join(self.site_packages, 'ibm_db-3.1.4') self.pip_args = [] if self.pipUrl: self.pip_args.extend(['-i', self.pipUrl]) if self.pipTrustHost: self.pip_args.extend(['--trusted-host', self.pipTrustHost]) if self.pipArgs: self.pip_args.extend(self.pipArgs.split(',')) # self.pip_args.extend(['--no-cache-dir', '--no-warn-script-location']) self.record("sdk pack init end") def run_command(self, command, cwd=None): process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=cwd, shell=use_shell()) while True: output = process.stdout.readline() if output == b'' and process.poll() is not None: break if output: print(output.strip().decode('utf-8', errors='replace')) process.wait() if process.returncode is not 0: raise Exception(f'run command {command} error, return code: {process.returncode}') self.record(f"run command: {command}") def get_env(self, env_key): env_value = os.getenv(env_key) if env_value: self.record(f'{env_key}: {env_value}') else: raise Exception(f'{env_key} not found') return env_value def remove_path(self, path): if os.path.exists(path): try: if os.path.isfile(path): os.remove(path) elif os.path.isdir(path): shutil.rmtree(path) self.record(f"remove {path}: successfully") except Exception as e: self.record(f'remove {path}: {e} error') raise e else: self.record(f"remove {path}: not exists") def record(self, title): end_time = datetime.datetime.now() diff = (end_time - self.starttime) print(f"[{end_time.time()} - {diff.seconds}] {title}") def unzip(self, src, dst): # self.run_command(['7z', 'x', src, '-o' + dst, '-bb0'], cwd=os.path.join(self.sdkToolsPath, '7z')) os.system(f"7z x {src} -o{dst}") self.record(f"unzip {src} to {dict}") def calculate_md5(self, file_path): with open(file_path, "rb") as f: md5_hash = hashlib.md5() for chunk in iter(lambda: f.read(4096), b""): md5_hash.update(chunk) return md5_hash.hexdigest() def copy(self, package_name, *rpa_dir): package = os.path.join(self.sdkToolsPath, package_name) package_out = os.path.join(self.site_packages, 'rpa', *rpa_dir) for file in os.listdir(package): package_file = os.path.join(package, file) shutil.copy(package_file, package_out) self.record(f"{package_file} >> {package_out}") self.record(f"copy {package_name}") def pack(self): # encrypt sdk self.record('*' * 10 + 'SDK pack' + '*' * 10) if self.RPA_FORCE_REBUILD == 'false' and os.path.exists(self.cacheZipPath): self.record('SDK use cache') self.unzip(self.cacheZipPath, self.outPath) else: self.encrypt_sdk() # add version version_path = os.path.join(self.site_packages, 'rpa', 'version', 'version') content = f'{self.RPA_VERSION}\n{datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")}\n{self.commitId}' with open(version_path, 'w') as f: f.write(content) shutil.copy(version_path, self.enginePath) with open(os.path.join(self.enginePath, 'version'), 'r') as f: self.record(f.read()) # remove cache remove_pycache(self.enginePath) self.record(f"remove engine pycache: {self.enginePath}") self.record("SDK pack end") def link_python(self): pass def is_linux(self): return self.RPA_PACK_PLATFORM in ['linux', 'UOS', 'kylinOS'] def is_windows(self): return self.RPA_PACK_PLATFORM == 'windows' def encrypt_sdk(self): def fix_tk(): # remove mouseinfo tk sys.exit mouseinfo_init_path = os.path.join(self.site_packages, 'mouseinfo', '__init__.py') command = """sed -i "s/sys.exit.*/pass/g" """ + mouseinfo_init_path ret = os.system(command) msg = f"remove mouseinfo tk sys.exit code: {ret}" if ret: raise SystemError(msg) self.record(msg) def install_pywpsrpc(): wheel_path = 'pywpsrpc-2.3.3-cp37-cp37m-manylinux_2_5_x86_64.whl' if self.RPA_PACK_ARCH == "arm64": wheel_path = 'pywpsrpc-2.3.3-cp37-cp37m-manylinux_2_28_aarch64.whl' pywpsrpc_path = os.path.join(self.sdkToolsPath, 'pywpsrpc', wheel_path) self.run_command([python_path, '-m', 'pip', 'install', pywpsrpc_path]) def copy_depends(): shutil.copytree(os.path.join(self.sdkToolsPath, 'deps', 'at-spi2-core'), os.path.join(self.enginePath, 'deps', 'at-spi2-core')) shutil.copytree(os.path.join(self.sdkToolsPath, 'deps', 'wps'), os.path.join(self.enginePath, 'deps', 'wps')) shutil.copytree(os.path.join(self.sdkToolsPath, 'deps', 'xclip'), os.path.join(self.site_packages, 'xclip')) # move python self.record('SDK encrypt') use_cache = False requirements_md5 = self.calculate_md5(self.reqsPath) requirements_cache_path = os.path.join(self.sdkPath, 'out', 'cache', f'{requirements_md5}.7z') if self.RPA_FORCE_REBUILD == 'false': if os.path.exists(requirements_cache_path): use_cache = True python_source = "" python_path = "" if self.is_windows(): python_source = os.path.join(self.sdkToolsPath, 'python') python_path = os.path.join(self.enginePath, "python.exe") elif self.is_linux(): python_source = "/opt/python3.7" python_path = os.path.join(self.enginePath, "bin", "python") if not use_cache: shutil.copytree(python_source, self.enginePath) self.record(f"{python_source} >> {self.enginePath}") if self.is_linux(): os.system(f'apt-get install -y libcairo2-dev libgirepository1.0-dev unixodbc-dev') current_cwd = os.getcwd() self.record(f"current cwd:{current_cwd}") bin_path = os.path.join(self.enginePath, "bin") os.chdir(bin_path) os.system(f'ln -s python3.7 python') os.system(f'ln -s python3.7 python3') self.record("link python3.7 to python python3") os.chdir(current_cwd) # install requirements # comtypes<1.1.11 need 2to3, setuptools<58 support 2to3 self.run_command([python_path, '-m', 'pip', 'install', '--upgrade', 'pip'] + self.pip_args) self.run_command([python_path, '-m', 'pip', 'install', '--upgrade', 'setuptools < 58'] + self.pip_args) self.run_command([python_path, '-m', 'pip', 'install', '-r', self.reqsPath] + self.pip_args) if self.is_windows(): # install db2 shutil.copytree(os.path.join(self.sdkToolsPath, 'db2', 'ibm_db-3.1.4'), os.path.join(self.enginePath, 'Lib', 'site-packages', 'ibm_db-3.1.4')) shutil.copytree(os.path.join(self.sdkToolsPath, 'db2_cli', 'clidriver'), os.path.join(self.enginePath, 'Lib', 'site-packages', 'ibm_db-3.1.4', 'clidriver')) self.run_command([os.path.join(self.enginePath, 'python'), 'setup.py', 'install'], cwd=os.path.join(self.enginePath, 'Lib', 'site-packages', 'ibm_db-3.1.4')) elif self.is_linux(): # install db2 # shutil.copytree(os.path.join(self.sdkToolsPath, 'db2', 'ibm_db-3.1.4'), # os.path.join(self.site_packages, 'ibm_db-3.1.4')) # shutil.copytree(os.path.join(self.sdkToolsPath, 'db2_cli', 'clidriver'), # os.path.join(self.site_packages, 'ibm_db-3.1.4', 'clidriver')) # self.run_command([python_path, 'setup.py', 'install'], # cwd=os.path.join(self.site_packages, 'ibm_db-3.1.4')) fix_tk() install_pywpsrpc() copy_depends() # install cython self.run_command([python_path, '-m', 'pip', 'install', 'cython==0.29.24'] + self.pip_args) self.remove_path(requirements_cache_path) self.run_command(['7z', 'a', '-mx1', requirements_cache_path, self.enginePath], cwd=os.path.join(self.sdkToolsPath, '7z')) else: self.record("requirements use cache") self.unzip(requirements_cache_path, self.outPath) build_path = os.path.join(self.sdkPath, 'build', 'rpa') # encrypt sdk self.run_command([python_path, 'setup.py'], cwd=os.path.join(self.sdkPath, 'rpa')) # uninstall cython self.run_command([python_path, '-m', 'pip', 'uninstall', 'cython', '-y']) # remove pycache remove_pycache(build_path) self.record(f"remove rpa pycache: {build_path}") # copy sdk rpa_path = os.path.join(self.site_packages, 'rpa') shutil.move(build_path, rpa_path) self.record(f"move {build_path} >> {rpa_path}") if self.RPA_PACK_PLATFORM == 'windows': self.copy('activexinput', 'uia', 'activexinput') self.copy("7z", "file_folder") self.copy("ffmpeg", "win32") # save cache self.remove_path(self.cacheZipPath) self.run_command(['7z', 'a', '-mx1', self.cacheZipPath, self.enginePath], cwd=os.path.join(self.sdkToolsPath, '7z')) # self.run_command(['7z', 'a', '-tzip', self.cacheZipPath, '-r', self.enginePath, '-y', '-bb0'], # cwd=os.path.join(self.sdkToolsPath, '7z')) # remove paths self.remove_path(os.path.join(self.sdkPath, 'build')) self.remove_path(build_path) self.record("SDK encrypt end") if __name__ == '__main__': import sys if sys.platform == "win32": # for tests os.environ['RPA_PACK_PLATFORM'] = 'windows' os.environ['RPA_PACK_ARCH'] = 'x64' os.environ['RPA_TARGET_FORMAT'] = 'zip' os.environ['RPA_PACK_GITOKEN'] = 'pack_gitoken' os.environ['RPA_VERSION'] = '1.0.0' os.environ['RPA_GIT_TOKEN'] = 'git_token' os.environ['RPA_FORCE_REBUILD'] = 'false' os.environ['RPA_TOOLS_HOME'] = 'C:\\Repos\\tools' elif sys.platform == "darwin": sys.exit(0) else: os.environ['RPA_PACK_PLATFORM'] = 'linux' os.environ['RPA_PACK_ARCH'] = 'x64' os.environ['RPA_TARGET_FORMAT'] = 'deb' os.environ['RPA_PACK_GITOKEN'] = 'pack_gitoken' os.environ['RPA_VERSION'] = '1.0.0' os.environ['RPA_GIT_TOKEN'] = 'git_token' os.environ['RPA_FORCE_REBUILD'] = 'false' os.environ['RPA_TOOLS_HOME'] = '/home/uos/tools' os.environ['SDK_PIP_URL'] = 'https://repo.datagrand.com/repository/py/simple' packer = SdkPacker() packer.pack() 分解一下项目打包的流程,介绍如何实现打包,如何配置环境,依赖等等,以及是否实现可执行文件,将整个流程用图表表示
07-10
# -*- 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 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)}") 可是我这个Cam_Operation_class.py里的命名就是小写啊
最新发布
07-14
设备服务器 #include "serial.h" /*********************串口 start******************************/ #define FALSE -1 #define TRUE 0 int serial_Open(char *devpath) { /*分别为com1,com2, com3对应 ttyUSB0 ttyUSB1 ttyUSB2 */ int fd = open( devpath, O_RDWR|O_NOCTTY|O_NDELAY); if (FALSE == fd){ perror("Can't Open Serial Port"); return(FALSE); } //恢复串口为阻塞状态 if(fcntl(fd, F_SETFL, 0) < 0){ printf("fcntl failed!\n"); return(FALSE); } else{ printf("fcntl=%d\n",fcntl(fd, F_SETFL,0)); } //测试是否为终端设备 if(0 == isatty(fd)){ printf("standard input is not a terminal device\n"); return(FALSE); } else{ printf("isatty success!\n"); } printf("fd->open=%d\n",fd); return fd; } //fd串口号;speed传输波特率;databits数据位;flow_ctrl数据流;parity奇偶校验;stopbits停止位 int serial_Set(int fd,int speed,int flow_ctrl,int databits,int stopbits,int parity) { int i; int speed_arr[] = { B115200, B19200, B9600, B4800, B2400, B1200, B300}; int name_arr[] = {115200, 19200, 9600, 4800, 2400, 1200, 300}; struct termios options; /*tcgetattr(fd,&options)得到与fd指向对象的相关参数,并将它们保存于options,该函数还可以测试配置是否正确,该串口是否可用等。若调用成功,函数返回值为0,若调用失败,函数返回值为1. */ if ( tcgetattr( fd,&options) != 0) { perror("SetupSerial 1"); return(FALSE); } //设置串口输入波特率输出波特率 for ( i= 0; i < sizeof(speed_arr) / sizeof(int); i++) { if (speed == name_arr[i]) { cfsetispeed(&options, speed_arr[i]); cfsetospeed(&options, speed_arr[i]); } } //修改控制模式,保证程序不会占用串口 options.c_cflag |= CLOCAL; //修改控制模式,使得能够从串口中读取输入数据 options.c_cflag |= CREAD; options.c_oflag &= ~(ONLCR | OCRNL); options.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); options.c_iflag &= ~(ICRNL | INLCR); options.c_iflag &= ~(IXON | IXOFF | IXANY); //设置数据流控制 switch(flow_ctrl) { case 0 ://不使用流控制 options.c_cflag &= ~CRTSCTS; break; case 1 ://使用硬件流控制 options.c_cflag |= CRTSCTS; break; case 2 ://使用软件流控制 options.c_cflag |= IXON | IXOFF | IXANY; break; } //设置数据位 //屏蔽其他标志位 options.c_cflag &= ~CSIZE; switch (databits) { case 5 : options.c_cflag |= CS5; break; case 6 : options.c_cflag |= CS6; break; case 7 : options.c_cflag |= CS7; break; case 8: options.c_cflag |= CS8; break; default: fprintf(stderr,"Unsupported data size\n"); return (FALSE); } //设置校验位 switch (parity) { case 'n': case 'N': //无奇偶校验位。 options.c_cflag &= ~PARENB; options.c_iflag &= ~INPCK; break; case 'o': case 'O'://设置为奇校验 options.c_cflag |= (PARODD | PARENB); options.c_iflag |= INPCK; break; case 'e': case 'E'://设置为偶校验 options.c_cflag |= PARENB; options.c_cflag &= ~PARODD; options.c_iflag |= INPCK; break; case 's': case 'S': //设置为空格 options.c_cflag &= ~PARENB; options.c_cflag &= ~CSTOPB; break; default: fprintf(stderr,"Unsupported parity\n"); return (FALSE); } // 设置停止位 switch (stopbits) { case 1: options.c_cflag &= ~CSTOPB; break; case 2: options.c_cflag |= CSTOPB; break; default: fprintf(stderr,"Unsupported stop bits\n"); return (FALSE); } //修改输出模式,原始数据输出 options.c_oflag &= ~OPOST; options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);//我加的 //options.c_lflag &= ~(ISIG | ICANON); //设置等待时间最小接收字符 options.c_cc[VTIME] = 1; /* 读取一个字符等待0*(0/10)s */ options.c_cc[VMIN] = 1; /* 读取字符的最少个数为0 */ //如果发生数据溢出,接收数据,但是不再读取 刷新收到的数据但是不读 tcflush(fd,TCIFLUSH); //激活配置 (将修改后的termios数据设置到串口中) if (tcsetattr(fd,TCSANOW,&options) != 0) { perror("com set error!\n"); return (FALSE); } printf("serial set success\n"); return (TRUE); } int serial_init(char *devpath, int baudrate) { int fd_comport = FALSE; //打开串口 if((fd_comport=serial_Open(devpath))<0){ perror("serial_Open"); return FALSE; } //设置串口数据帧格式 if (serial_Set(fd_comport,baudrate,0,8,1,'N')<0) { perror("serial_Set"); return FALSE; } return fd_comport; } ssize_t serial_recv_exact_nbytes(int fd, void *buf, size_t count) { ssize_t ret; ssize_t total = 0; assert(buf != NULL); while (total != count) { ret = read(fd, buf + total, count - total); if (ret == -1) { perror("serial->recv"); break; } else if (ret == 0) { fprintf(stdout, "serial->recv: timeout or end-of-file\n"); break; } else total += ret; } return total; } ssize_t serial_send_exact_nbytes(int fd, unsigned char *buf, size_t count) { ssize_t ret; ssize_t total = 0; assert(buf != NULL); #if 0 int i = 0; for (i = 0; i < 36; i++){ printf("%.2x ", buf[i]); } printf("\n"); #endif while (total != count) { ret = write(fd, buf + total, count - total); if (ret == -1) { perror("serial->send"); break; } else total += ret; } return total; } int serial_exit(int fd) { if (close(fd)) { perror("serial->exit"); return -1; } return 0; } /*********************串口 end ******************************/ Serial.h #ifndef __LIB_H__ #define __LIB_H__ #include <stdio.h> /*标准输入输出定义*/ #include <stdlib.h> /*标准函数库定义*/ #include <unistd.h> /*Unix 标准函数定义*/ #include <string.h> #include <fcntl.h> /*文件控制定义*/ #include <termios.h> /*PPSIX 终端控制定义*/ #include <errno.h> /*错误号定义*/ #include <sys/types.h> #include <sys/stat.h> #include <sys/mman.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <sys/wait.h> #include <arpa/inet.h> #include <assert.h> #include <poll.h> int serial_init(char *devpath, int baudrate); ssize_t serial_recv_exact_nbytes(int fd, void *buf, size_t count); ssize_t serial_send_exact_nbytes(int fd, unsigned char *buf, size_t count); int serial_exit(int fd); #endif Server.c #include <stdio.h> #include <string.h> //socket,bind,listen #include <sys/types.h> #include <sys/socket.h> //struct sockaddr_in #include <netinet/in.h> #include <netinet/ip.h> //htons,htonl #include <arpa/inet.h> //read,write #include <unistd.h> #include <pthread.h> #include "serial.h" //服务器初始化 int server_init(short port) { //1.创建监听套接字 int listenfd = -1; listenfd = socket(AF_INET,SOCK_STREAM,0); if(-1 == listenfd){ perror("socket"); return -1; } //2.绑定IP及其端口号 //2.1.创建专用地址结构体存储IP端口号信息 struct sockaddr_in server_addr; memset(&server_addr,0,sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(port);//5000+ server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //2.2.完成绑定过程 int ret = -1; ret = bind(listenfd,(const struct sockaddr *)&server_addr,sizeof(server_addr)); if(-1 == ret){ perror("bind"); return -1; } //3.开启监听 ret = listen(listenfd,16); if(-1 == ret){ perror("listen"); return -1; } printf("server_init success\n"); return listenfd; } //服务器处理客户端连接请求 int server_wait_connect(int listenfd) { //4.处理连接请求 //关心客户端的连接信息 struct sockaddr_in client_addr; memset(&client_addr,0,sizeof(client_addr)); socklen_t len = sizeof(client_addr); int connfd = accept(listenfd,(struct sockaddr *)&client_addr,&len); if(-1 == connfd){ perror("accept"); return -1; } printf("connect success,client IP:%s,PORT:%d\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port)); return connfd; } int serial_fd = -1; int connfd = -1; pthread_mutex_t lock; //通信 void * server_exchange_data_pthread(void * arg) { int ret = -1; char buf[1024] = {0}; while(1){ memset(buf,0,sizeof(buf)); //pthread_mutex_lock(&lock); //接受客户端发送的数据 ret = read(connfd,buf,sizeof(buf)); if(-1 == ret){ perror("read"); return NULL; }else if(0 == ret){ printf("client exit\n"); return NULL; } ret = serial_send_exact_nbytes(serial_fd,buf,ret); //pthread_mutex_unlock(&lock); } } void * serial_recv_data_pthread(void * arg) { char buf[10]; int ret = -1; while(1){ memset(buf,0,10); if(connfd >= 0){ //pthread_mutex_lock(&lock); ret = serial_recv_exact_nbytes(serial_fd,buf,1); //printf("serial buf = %s\n",buf); write(connfd,buf,ret); //pthread_mutex_unlock(&lock); } } } int main() { signal(SIGPIPE,SIG_IGN); pthread_mutex_init(&lock,NULL); serial_fd = serial_init("/dev/rfcomm0",115200); pthread_t pid_serial; pthread_create(&pid_serial,NULL,serial_recv_data_pthread,NULL); //初始化服务器 int listenfd = server_init(8888); pthread_t pid; while(1){ connfd = server_wait_connect(listenfd); printf("connfd = %d\n",connfd); pthread_create(&pid,NULL,server_exchange_data_pthread,NULL); } } 详细解释代码,包括每一个函数的作用
05-30
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值