├── MANIFEST.in ├── hik_camera ├── __init__.py ├── get_all_ips.py ├── __info__.py ├── collect_img.py ├── bandwidth.py ├── MvCameraNode-CH.csv └── hik_camera.py ├── requirements.txt ├── test ├── test_base.py ├── test_continuous_adjust_exposure.py ├── test_raw.py └── MVS_grab_raw.py ├── .gitignore ├── example_opencv.py ├── doc ├── dev.md └── zhihu.md ├── setup.py ├── Dockerfile └── README.md /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include requirements.txt hik_camera/MvCameraNode-CH.csv README.md -------------------------------------------------------------------------------- /hik_camera/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from .__info__ import __version__ 4 | 5 | from .hik_camera import HikCamera 6 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | boxx>=0.10.7 2 | # for Windows User should install PiDNG first 3 | # git+https://github.com/schoolpost/PiDNG.git 4 | process_raw 5 | opencv-python>=4.6.0 -------------------------------------------------------------------------------- /test/test_base.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import boxx 5 | 6 | sys.path = [boxx.relfile("..")] + sys.path 7 | 8 | if __name__ == "__main__": 9 | from boxx import * 10 | -------------------------------------------------------------------------------- /hik_camera/get_all_ips.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import boxx 3 | 4 | if __name__ == "__main__": 5 | with boxx.inpkg(): 6 | from .hik_camera import HikCamera 7 | 8 | print(" ".join(sorted(HikCamera._get_dev_info()))) 9 | -------------------------------------------------------------------------------- /hik_camera/__info__.py: -------------------------------------------------------------------------------- 1 | __title__ = "hik_camera" 2 | __version__ = "0.2.16" 3 | __author__ = "Lei Yang" 4 | __author_email__ = "" 5 | __url__ = "" 6 | __description__ = "Provides an easy-to-use API to control Hik industrial cameras. (supports Linux/Docker/Windows)" 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | error.xml 2 | tmp_* 3 | *.dng 4 | MvSdkLog 5 | 6 | .vscode 7 | *.pyc 8 | __pycache__/ 9 | 10 | # vim swap file 11 | *.swp 12 | 13 | # coverage report 14 | test-results/ 15 | htmlcov/ 16 | .coverage 17 | 18 | # Distribution / packaging 19 | build/ 20 | dist/ 21 | eggs/ 22 | .eggs/ 23 | *.egg-info/ 24 | 25 | # sphinx 26 | /docs/api 27 | /docs/_build 28 | 29 | # mypy cache 30 | .mypy_cache/ 31 | 32 | # tmp files 33 | *~ 34 | 35 | # png or jpg image 36 | *.png 37 | *.jpg 38 | grab_img/ 39 | 40 | .venv/ 41 | -------------------------------------------------------------------------------- /example_opencv.py: -------------------------------------------------------------------------------- 1 | # On every platform you need the corresponding MVS SDK. 2 | # On Windows you need Visual Studio c++ build tools 3 | # On Ubuntu you need (assuming python3.10): 4 | # sudo apt-get install build-essential python3.10-dev 5 | 6 | from hik_camera.hik_camera import HikCamera 7 | 8 | import cv2 9 | 10 | ips = HikCamera.get_all_ips() 11 | 12 | print("All camera IP adresses:", ips) 13 | ip = ips[0] 14 | 15 | cam = HikCamera(ip=ip) 16 | 17 | with cam: 18 | cam["ExposureAuto"] = "Off" 19 | cam["ExposureTime"] = 50000 20 | 21 | while True: 22 | rgb = cam.robust_get_frame() 23 | cv2.imshow("rgb", rgb) 24 | cv2.waitKey(1) 25 | # print("Saving image to:", cam.save(rgb, ip + ".jpg")) 26 | -------------------------------------------------------------------------------- /test/test_continuous_adjust_exposure.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import boxx 4 | import test_base 5 | 6 | from hik_camera import HikCamera 7 | 8 | 9 | class Cam(HikCamera): 10 | def setting(self): 11 | # 每隔一定时间拍一次照片来调整自动曝光, 防止隔了太久, 曝光失效 12 | self.continuous_adjust_exposure(6) 13 | # 取 RGB 图 14 | self.pixel_format = "RGB8Packed" 15 | self.setitem("PixelFormat", self.pixel_format) 16 | 17 | 18 | if __name__ == "__main__": 19 | from boxx import * 20 | 21 | with Cam.get_all_cams() as cams: 22 | for i in range(5): 23 | frames = cams.robust_get_frame() 24 | p - "sleep" 25 | sleep(8) 26 | 27 | frames = cams.robust_get_frame() 28 | tree - frames 29 | -------------------------------------------------------------------------------- /test/test_raw.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import boxx 5 | import test_base 6 | 7 | from hik_camera import HikCamera 8 | 9 | 10 | class RawCam(HikCamera): 11 | def setting(self): 12 | super().setting() 13 | self.set_raw() 14 | 15 | 16 | if __name__ == "__main__": 17 | from boxx import * 18 | 19 | cam = RawCam.get_cam() 20 | with cam: 21 | raw = cam.robust_get_frame() 22 | if cam.is_raw: 23 | rgbs = [ 24 | cam.raw_to_uint8_rgb(raw, poww=1), 25 | cam.raw_to_uint8_rgb(raw, poww=0.3), 26 | ] 27 | boxx.shows(rgbs) 28 | dngp = "/tmp/test-raw.dng" 29 | cam.save_raw(raw, dngp) 30 | os.system(f"ls -lh {dngp}") 31 | boxx.showb(dngp) 32 | -------------------------------------------------------------------------------- /doc/dev.md: -------------------------------------------------------------------------------- 1 | # 开发文档 2 | 3 | ## Tips 4 | 1. SDK目录位于 `/opt/MVS` 5 | 2. `/opt/MVS/doc`包含详细SDK接口说明 6 | .xlsx为相机可选参数说明 7 | 3. `/opt/MVS/doc/samples`包含代码示例 8 | 4. 例如 `python/MvImport/MvCameraControl_class.py` 包含Python可调class.(根据需要可按照c代码重构). 9 | 5. 相机只缓存设置相关内容.图片缓存在MVS的SDK中,代码运行需要开辟内存空间,注意调用后释放,避免内存泄露. 10 | 6. 使用 `python -m hik_camera.bandwidth` 监控网口流量 11 | 12 | 13 | ## TODO 14 | - [x] python setup.py install 找不到 xls 15 | - [x] 异常重置相机 16 | - self.MV_CC_SetCommandValue("DeviceReset") 17 | - [x] 自研快速自动曝光算法? 18 | - 自动曝光 RoI 19 | - [x] 了解 rawpy 的 rgb=re['raw_obj'].postprocess(), 尝试加速转换为 RGB 20 | - 没有比采集图像显著的快 21 | - [x] ~~要不要考虑多帧融合 HDR?~~ 22 | - 12bit raw 图能同时获得亮暗细节 23 | - [ ] 跑通 ParametrizeCamera_LoadAndSave.py MV_CC_FeatureSave 24 | - [ ] DNG 支持正确的 meta 信息 25 | 26 | 27 | ## 图片存储空间 28 | ```bash 29 | # 对于 12 bit 的 raw12 图, 其不同存储形式的占用空间 30 | >>> tree-raw12 31 | └── /: (3036, 4024)uint16 32 | 33 | 412K ./color.jpg # uint8 34 | 8.7M ./color.png # uint8 35 | 14M ./raw.png # uint16 36 | 15M ./uint16.npz 37 | 18M ./int32.npz 38 | 24M ./uint16.pkl 39 | 47M ./int32.pkl 40 | ``` 41 | 42 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | import os 3 | 4 | here = os.path.dirname(os.path.realpath(__file__)) 5 | 6 | with open("requirements.txt") as f: 7 | requirements = [line.strip() for line in f] 8 | 9 | info = {} 10 | with open(os.path.join(here, "hik_camera", "__info__.py")) as f: 11 | exec(f.read(), info) 12 | 13 | setuptools.setup( 14 | name=info["__title__"], 15 | version=info["__version__"], 16 | author=info["__author__"], 17 | author_email=info["__author_email__"], 18 | description=info["__description__"], 19 | long_description=info["__description__"], 20 | long_description_content_type="text/markdown", 21 | url=info["__url__"], 22 | packages=setuptools.find_packages(), 23 | python_requires=">=3.6", 24 | install_requires=requirements, 25 | classifiers=[ 26 | "Programming Language :: Python :: 3", 27 | "License :: OSI Approved :: MIT License", 28 | "Operating System :: OS Independent", 29 | "Intended Audience :: Developers", 30 | "Topic :: Software Development :: Libraries :: Python Modules", 31 | ], 32 | include_package_data=True, 33 | ) 34 | -------------------------------------------------------------------------------- /doc/zhihu.md: -------------------------------------------------------------------------------- 1 | https://www.zhihu.com/question/424627189/answer/3069074313 2 | 3 | 4 | # 海康 GigE Vision 网口工业相机 Python 控制库 hik_camera 5 | 6 | 我从 2019 年开始从事工业视觉研发工作,在过去的四年里,我测试和使用过超过10种工业相机型号,并在不同项目中部署了50多台工业相机。 7 | 8 | 我主要使用海康网口工业相机, 由于海康官方的 Python API过于复杂和繁琐,因此我开发并封装了一个更加Pythonic的海康工业相机控制库:[hik_camera](https://github.com/DIYer22/hik_camera/) 9 | 10 | 该库具有以下特点: 11 | - 模块化设计,极为简洁的 Pythonic API,便于进行业务研发且易于他人快速上手 12 | - 将工业相机的各种知识和经验沉淀为代码 13 | - 功能丰富, 支持 Windows 和 Linux 系统,并提供预编译好的 Docker 镜像 14 | 15 | 16 | 安装hik_camera有两种方式: 17 | - Docker 方案 18 | - `docker run --net=host -v /tmp:/tmp -it diyer22/hik_camera` 19 | - 手动安装方案 20 | 1. 安装官方驱动: 在[海康机器人官网](https://www.hikrobotics.com/cn/machinevision/service/download)下载安装对应操作系统的 "机器视觉工业相机客户端 MVS SDK" 21 | 2. `pip install hik_camera` 22 | 23 | ```bash 24 | # 接入相机, 测试安装是否成功 25 | python -m hik_camera.hik_camera 26 | ``` 27 | 28 | 取图示例代码: 29 | ```Python 30 | from hik_camera import HikCamera 31 | ips = HikCamera.get_all_ips() 32 | print("All camera IP adresses:", ips) 33 | ip = ips[0] 34 | cam = HikCamera(ip) 35 | with cam: 36 | img = cam.robust_get_frame() 37 | print("Saveing image to:", cam.save(img)) 38 | # 图像将自动保存至临时文件夹下 39 | ``` 40 | 更详细的示例以及相机参数配置方法(如曝光、增益、像素格式等),请访问 hik_camera GitHub 主页: 41 | 42 | https://github.com/DIYer22/hik_camera/ 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # FROM jjanzic/docker-python3-opencv:opencv-4.0.1 2 | FROM diyer22/tiny_cv2:4.6.0-py38-ubuntu20.04 3 | 4 | ENV LC_ALL=C.UTF-8 LANG=C.UTF-8 DEBIAN_FRONTEND=noninteractive TZ=Asia/Shanghai 5 | 6 | RUN apt update && \ 7 | apt install -y unzip wget make g++ iputils-ping net-tools iputils-ping traceroute 8 | 9 | # https://github.com/schoolpost/PiDNG/issues/66#issuecomment-1378632756 10 | RUN apt-get install -y python3-dev && \ 11 | pip install --no-cache-dir pidng && \ 12 | apt-get remove -y python3-dev && \ 13 | apt-get autoremove -y && \ 14 | apt-get clean 15 | 16 | # RUN ln -sf `which python3` /usr/bin/python && ln -sf `which pip3` /usr/bin/pip 17 | RUN pip install --no-cache-dir -U pip wheel setuptools 18 | 19 | RUN pip install --no-cache-dir boxx process_raw 20 | 21 | # V2.1.2_221208 == libMvCameraControl.so.3.2.2.1 22 | RUN wget https://www.hikrobotics.com/cn2/source/support/software/MVS_STD_GML_V2.1.2_221208.zip \ 23 | && unzip MVS_STD_GML_V2.1.2_221208.zip \ 24 | && dpkg -i MVS-2.1.2_x86_64_20221208.deb \ 25 | && rm MVS*.deb MVS*.gz MVS*.zip 26 | 27 | ENV MVCAM_COMMON_RUNENV=/opt/MVS/lib LD_LIBRARY_PATH=/opt/MVS/lib/64:/opt/MVS/lib/32:$LD_LIBRARY_PATH PYTHONPATH=/hik_camera:$PYTHONPATH 28 | 29 | COPY . /hik_camera 30 | WORKDIR /hik_camera 31 | RUN pip install --no-cache-dir -r requirements.txt 32 | 33 | CMD python -m hik_camera.hik_camera 34 | 35 | # docker build -t diyer22/hik_camera ./ && docker run --net=host -v /tmp:/tmp -it diyer22/hik_camera; 36 | 37 | -------------------------------------------------------------------------------- /hik_camera/collect_img.py: -------------------------------------------------------------------------------- 1 | import boxx, cv2 2 | from hik_camera import HikCamera 3 | 4 | 5 | class CvShow: 6 | destroyed = False 7 | 8 | def __init__(self): 9 | pass 10 | 11 | def __enter__(self): 12 | CvShow.destroyed = False 13 | self.get_key() 14 | return self 15 | 16 | def __exit__(*args): 17 | CvShow.destroyed = True 18 | cv2.destroyAllWindows() 19 | 20 | @staticmethod 21 | def imshow(rgb, window="default"): 22 | rgb = rgb[..., ::-1] if rgb.ndim == 3 and rgb.shape[-1] == 3 else rgb 23 | cv2.imshow(window, rgb) 24 | 25 | def get_key(self): 26 | key_idx = cv2.waitKey(1) 27 | if 0 < key_idx and key_idx < 256: 28 | return chr(key_idx) 29 | return key_idx 30 | 31 | def __next__(self): 32 | return self.get_key() 33 | 34 | def __iter__(self): 35 | return self 36 | 37 | 38 | class Hik(HikCamera): 39 | def setting(self): 40 | # self.setitem("GevSCPD", 200) # 包延时, 单位 ns, 防止丢包, 6 个百万像素相机推荐 15000 41 | # self.set_OptimalPacketSize() # 最优包大小 42 | 43 | self.setitem("ExposureAuto", "Continuous") 44 | try: 45 | self.set_rgb() # 取 RGB 图 46 | except AssertionError: 47 | self.set_raw(8) # 有的相机不支持 RGB, 只支持 raw 图 48 | 49 | 50 | if __name__ == "__main__": 51 | from boxx import * 52 | 53 | dirr = "/tmp/hik_camera_collect" 54 | boxx.makedirs(dirr) 55 | ips = Hik.get_all_ips() 56 | print("All camera IP adresses:", ips) 57 | print("Press 'q' to quit") 58 | print("Press 'space' to save the current frame from all cameras") 59 | cams = Hik.get_all_cams() 60 | with cams, CvShow() as cvshow: 61 | for idx, key in enumerate(cvshow): 62 | if key == "q": 63 | break 64 | timestr = localTimeStr(1) 65 | for ip, cam in cams.items(): 66 | img = cam.get_frame() 67 | if cam.is_raw: # 如果是采集的 raw 图, demosaicing 为 RGB 68 | img = cam.raw_to_uint8_rgb(img, poww=0.5) 69 | cvshow.imshow(img[::3, ::3], window=ip) 70 | if key == " ": 71 | imgp = pathjoin(dirr, f"{timestr}~{ip}.jpg") 72 | print("Save to", imgp) 73 | boxx.imsave(imgp, img) 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 海康 GigE Vision 网口工业相机 Python 控制库 2 | 使用 Pythonic 风格封装海康网口工业相机 Python SDK, 灵活易用, 方便集成. 3 | 4 | ## ▮ 特性 Features 5 | - **易用**的 Pythonic API: 6 | - 采用面向对象封装, 方便多相机管理 7 | - 支持 `with` 语法来调用: `with HikCamera() as cam:` 8 | - 简洁直观的控制语法: `cam["ExposureTime"]=100000`, `print(cam["ExposureTime"])` 9 | - **鲁棒(robust)**: 遇到错误, 会自动 reset 相机并 retry 10 | - 接口为: `cams.robust_get_frame()` 11 | - 支持获得/处理/存取 **raw 图**, 并保存为 **`.dng` 格式** 12 | - Example 见 [./test/test_raw.py](./test/test_raw.py) 13 | - 支持每隔一定时间自动拍一次照片来调整自动曝光, 以防止太久没触发拍照, 导致曝光失效 14 | - Example 见 [./test/test_continuous_adjust_exposure.py](./test/test_continuous_adjust_exposure.py) 15 | - 支持 **Windows/Linux** 系统, 有编译好的 **Docker 镜像** (`diyer22/hik_camera`) 16 | - 支持 CS/CU/CE/CA/CH 系列的 GigE Vision 网口工业相机 17 | - 方便封装为 ROS 节点 18 | 19 | ## ▮ 安装 Install 20 | - Docker 一键运行: 21 | - `docker run --net=host -v /tmp:/tmp -it diyer22/hik_camera` 22 | - 手动安装方案: 23 | 1. 安装官方驱动: 在[海康机器人官网](https://www.hikrobotics.com/cn/machinevision/service/download)下载安装对应系统的 “机器视觉工业相机客户端 MVS SDK” 24 | - 官网下载需要注册, 也可以在 [Dockerfile](Dockerfile) 里面找到 Linux 版的下载链接 25 | 2. `pip install hik_camera` 26 | 3. 若遇到问题, 可以参考 [Dockerfile](Dockerfile), 一步一步手动安装 27 | - 验证: 接入相机, 验证 hik_camera 是否安装成功: 28 | ```bash 29 | $ python -m hik_camera.hik_camera 30 | 31 | All camera IP adresses: ['10.101.68.102', '10.101.68.103'] 32 | Saveing image to: /tmp/10.101.68.102.jpg 33 | "cam.get_frame" spend time: 0.072249 34 | ---------------------------------------- 35 | imgs = cams.robust_get_frame() 36 | └── /: dict 2 37 | ├── 10.101.68.102: (3036, 4024, 3)uint8 38 | └── 10.101.68.103: (3036, 4024, 3)uint8 39 | "cams.get_frame" spend time: 0.700901 40 | ``` 41 | 42 | ## ▮ 用法 Usage 43 | ### 图像采集 Demo 44 | ```bash 45 | python -m hik_camera.collect_img 46 | ``` 47 | - 运行后会弹出 opencv 窗口显示相机图像流, 按 `"空格"` 键拍照, `Q` 键退出 48 | - 采图 demo 源代码在 [hik_camera/collect_img.py](hik_camera/collect_img.py) 49 | 50 | ### Python 接口 51 | ```Python 52 | from hik_camera import HikCamera 53 | ips = HikCamera.get_all_ips() 54 | print("All camera IP adresses:", ips) 55 | ip = ips[0] 56 | cam = HikCamera(ip) 57 | with cam: # 用 with 的上下文的方式来 OpenDevice 58 | cam["ExposureAuto"] = "Off" # 配置参数和海康官方 API 一致 59 | cam["ExposureTime"] = 50000 # 单位 ns 60 | rgb = cam.robust_get_frame() # rgb's shape is np.uint8(h, w, 3) 61 | print("Saveing image to:", cam.save(rgb, ip + ".jpg")) 62 | ``` 63 | - 更全面的 Example 见 [hik_camera/hik_camera.py](hik_camera/hik_camera.py) 最底部的 "\_\_main\_\_" 代码 64 | - 更多相机参数配置示例(曝光/Gain/PixelFormat等)见 [hik_camera/hik_camera.py](hik_camera/hik_camera.py#L91) 中, `HikCamera.setting()` 的注释 65 | - 海康官方的配置项列表: [MvCameraNode-CH.csv](hik_camera/MvCameraNode-CH.csv) 66 | - 推荐通过继承 `HikCamera` 类, 并 override setting 函数来配置相机参数, 例子: [hik_camera/collect_img.py](hik_camera/collect_img.py) 67 | 68 | 广告:相机标定推荐使用 [calibrating](https://github.com/DIYer22/calibrating) 库,能方便标定相机内外参、并快速构建双目深度相机 69 | -------------------------------------------------------------------------------- /hik_camera/bandwidth.py: -------------------------------------------------------------------------------- 1 | import psutil 2 | import time 3 | from datetime import datetime 4 | import curses 5 | import argparse 6 | 7 | 8 | def getNetworkData(): 9 | # 获取网卡流量信息 10 | recv = {} 11 | sent = {} 12 | data = psutil.net_io_counters(pernic=True) 13 | interfaces = data.keys() 14 | for interface in interfaces: 15 | recv.setdefault(interface, data.get(interface).bytes_recv) 16 | sent.setdefault(interface, data.get(interface).bytes_sent) 17 | return interfaces, recv, sent 18 | 19 | 20 | def getNetworkRate(num): 21 | # 计算网卡流量速率 22 | interfaces, oldRecv, oldSent = getNetworkData() 23 | time.sleep(num) 24 | interfaces, newRecv, newSent = getNetworkData() 25 | networkIn = {} 26 | networkOut = {} 27 | for interface in interfaces: 28 | networkIn.setdefault( 29 | interface, 30 | float("%.3f" % ((newRecv.get(interface) - oldRecv.get(interface)) / num)), 31 | ) 32 | networkOut.setdefault( 33 | interface, 34 | float("%.3f" % ((newSent.get(interface) - oldSent.get(interface)) / num)), 35 | ) 36 | return interfaces, networkIn, networkOut 37 | 38 | 39 | def output(num, unit): 40 | # 将监控输出到终端 41 | stdscr = curses.initscr() 42 | curses.start_color() 43 | # curses.init_pair(1, curses.COLOR_RED, curses.COLOR_WHITE) 44 | curses.noecho() 45 | curses.cbreak() 46 | stdscr.clear() 47 | try: 48 | # 第一次初始化 49 | interfaces, _, _ = getNetworkData() 50 | currTime = datetime.now() 51 | timeStr = datetime.strftime(currTime, "%Y-%m-%d %H:%M:%S") 52 | stdscr.addstr(0, 0, timeStr) 53 | i = 1 54 | for interface in interfaces: 55 | if ( 56 | interface != "lo" 57 | and bool(1 - interface.startswith("veth")) 58 | and bool(1 - interface.startswith("蓝牙")) 59 | and bool(1 - interface.startswith("VMware")) 60 | ): 61 | if unit == "K" or unit == "k": 62 | netIn = "%12.2fKB/s" % 0 63 | netOut = "%11.2fKB/s" % 0 64 | elif unit == "M" or unit == "m": 65 | netIn = "%12.2fMB/s" % 0 66 | netOut = "%11.2fMB/s" % 0 67 | elif unit == "G" or unit == "g": 68 | netIn = "%12.3fGB/s" % 0 69 | netOut = "%11.3fGB/s" % 0 70 | else: 71 | netIn = "%12.1fB/s" % 0 72 | netOut = "%11.1fB/s" % 0 73 | stdscr.addstr(i, 0, interface) 74 | stdscr.addstr(i + 1, 0, "Input:%s" % netIn) 75 | stdscr.addstr(i + 2, 0, "Output:%s" % netOut) 76 | stdscr.move(i + 3, 0) 77 | i += 4 78 | stdscr.refresh() 79 | # 第二次开始循环监控网卡流量 80 | while True: 81 | _, networkIn, networkOut = getNetworkRate(num) 82 | currTime = datetime.now() 83 | timeStr = datetime.strftime(currTime, "%Y-%m-%d %H:%M:%S") 84 | stdscr.erase() 85 | stdscr.addstr(0, 0, timeStr) 86 | i = 1 87 | for interface in interfaces: 88 | if ( 89 | interface != "lo" 90 | and bool(1 - interface.startswith("veth")) 91 | and bool(1 - interface.startswith("蓝牙")) 92 | and bool(1 - interface.startswith("VMware")) 93 | ): 94 | if unit == "K" or unit == "k": 95 | netIn = "%12.2fKB/s" % (networkIn.get(interface) / 1024) 96 | netOut = "%11.2fKB/s" % (networkOut.get(interface) / 1024) 97 | elif unit == "M" or unit == "m": 98 | netIn = "%12.2fMB/s" % (networkIn.get(interface) / 1024 / 1024) 99 | netOut = "%11.2fMB/s" % ( 100 | networkOut.get(interface) / 1024 / 1024 101 | ) 102 | elif unit == "G" or unit == "g": 103 | netIn = "%12.3fGB/s" % ( 104 | networkIn.get(interface) / 1024 / 1024 / 1024 105 | ) 106 | netOut = "%11.3fGB/s" % ( 107 | networkOut.get(interface) / 1024 / 1024 / 1024 108 | ) 109 | else: 110 | netIn = "%12.1fB/s" % networkIn.get(interface) 111 | netOut = "%11.1fB/s" % networkOut.get(interface) 112 | stdscr.addstr(i, 0, interface) 113 | stdscr.addstr(i + 1, 0, "Input:%s" % netIn) 114 | stdscr.addstr(i + 2, 0, "Output:%s" % netOut) 115 | stdscr.move(i + 3, 0) 116 | i += 4 117 | stdscr.refresh() 118 | except KeyboardInterrupt: 119 | # 还原终端 120 | curses.echo() 121 | curses.nocbreak() 122 | curses.endwin() 123 | except Exception as e: 124 | curses.echo() 125 | curses.nocbreak() 126 | curses.endwin() 127 | print("ERROR: %s!" % e) 128 | print("Please increase the terminal size!") 129 | finally: 130 | curses.echo() 131 | curses.nocbreak() 132 | curses.endwin() 133 | 134 | 135 | if __name__ == "__main__": 136 | parser = argparse.ArgumentParser( 137 | description="A command for monitoring the traffic of network interface! Ctrl + C: exit" 138 | ) 139 | parser.add_argument( 140 | "-t", "--time", type=int, help="the interval time for ouput", default=1 141 | ) 142 | parser.add_argument( 143 | "-u", 144 | "--unit", 145 | type=str, 146 | choices=["b", "B", "k", "K", "m", "M", "g", "G"], 147 | help="the unit for ouput", 148 | default="M", 149 | ) 150 | parser.add_argument( 151 | "-v", 152 | "--version", 153 | help="output version information and exit", 154 | action="store_true", 155 | ) 156 | args = parser.parse_args() 157 | if args.version: 158 | print("v1.0") 159 | exit(0) 160 | num = args.time 161 | unit = args.unit 162 | output(num, unit) 163 | -------------------------------------------------------------------------------- /test/MVS_grab_raw.py: -------------------------------------------------------------------------------- 1 | # -- coding: utf-8 -- 2 | """ 3 | Created on 2021.01.03 4 | hik grab images 5 | 6 | @author: yanziwei 7 | """ 8 | import base64 9 | import os 10 | import queue 11 | import sys 12 | import termios 13 | import threading 14 | import time 15 | import urllib 16 | from ctypes import * 17 | 18 | import cv2 19 | import matplotlib.pyplot as plt 20 | import numpy as np 21 | 22 | import tqdm 23 | from boxx import * 24 | from PIL import Image 25 | from skimage import io 26 | 27 | sys.path.append("/opt/MVS/Samples/64/Python/MvImport") 28 | from MvCameraControl_class import * 29 | 30 | g_bExit = False 31 | 32 | 33 | class HikMvCamera: 34 | def __init__(self): 35 | 36 | # ch:创建相机实例 | en:Creat Camera Object 37 | self.nConnectionNum = self.find_device() 38 | self.cam = {} 39 | self.stParam = {} 40 | for i in range(self.nConnectionNum): 41 | self.cam[str(i)] = MvCamera() 42 | self.cam[str(i)] = self.create_cam(self.cam[str(i)], i) 43 | self.cam[str(i)], self.stParam[str(i)] = self.set_info(self.cam[str(i)]) 44 | 45 | def set_info(self, cam): 46 | 47 | # ch:设置触发模式为off | en:Set trigger mode as off 48 | ret = cam.MV_CC_SetEnumValue("TriggerMode", MV_TRIGGER_MODE_OFF) 49 | if ret != 0: 50 | print("set trigger mode fail! ret[0x%x]" % ret) 51 | sys.exit() 52 | 53 | # #设置PixelFormat为RGB-8 54 | ret = cam.MV_CC_SetEnumValue("PixelFormat", 0x02180014) 55 | if ret != 0: 56 | print("set RBG8 fail! ret[0x%x]" % ret) 57 | sys.exit() 58 | 59 | # 设置ROI宽高 60 | # ret = cam.MV_CC_SetIntValue("Width", 5472) #5472 61 | # if ret != 0: 62 | # print("set width fail! ret[0x%x]" % ret) 63 | # sys.exit() 64 | # ret = cam.MV_CC_SetIntValue("Height", 3648) #3648 65 | # if ret != 0: 66 | # print("set height fail! ret[0x%x]" % ret) 67 | # sys.exit() 68 | # # 设置ROI偏移位置 69 | # ret = cam.MV_CC_SetIntValue("OffsetX", 2096) #2096 70 | # if ret != 0: 71 | # print("set width fail! ret[0x%x]" % ret) 72 | # sys.exit() 73 | # ret = cam.MV_CC_SetIntValue("OffsetY", 1184) #1184 74 | # if ret != 0: 75 | # print("set height fail! ret[0x%x]" % ret) 76 | # sys.exit() 77 | # 设置曝光 78 | ret = cam.MV_CC_SetFloatValue("ExposureTime", 250000) 79 | if ret != 0: 80 | print("set ExposureTime fail! ret[0x%x]" % ret) 81 | sys.exit() 82 | 83 | ret = cam.MV_CC_SetIntValue("GevSCPD", 1000) 84 | if ret != 0: 85 | print("set GevSCPD fail! ret[0x%x]" % ret) 86 | sys.exit() 87 | 88 | # 设置SDK图像缓存个数 89 | # cam.MV_CC_SetImageNodeNum(10) 90 | # ch:获取数据包大小 | en:Get payload size 91 | stParam = MVCC_INTVALUE() 92 | memset(byref(stParam), 0, 2 * sizeof(MVCC_INTVALUE)) 93 | ret = cam.MV_CC_GetIntValue("PayloadSize", stParam) 94 | if ret != 0: 95 | print("get payload size fail! ret[0x%x]" % ret) 96 | sys.exit() 97 | 98 | return cam, stParam 99 | 100 | def find_device(self): 101 | # 创建相机 102 | # 获得版本号 103 | self.SDKVersion = MvCamera.MV_CC_GetSDKVersion() 104 | print("SDKVersion[0x%x] :v3.0" % self.SDKVersion) 105 | self.deviceList = MV_CC_DEVICE_INFO_LIST() 106 | self.tlayerType = MV_GIGE_DEVICE | MV_USB_DEVICE 107 | tlayerType = self.tlayerType 108 | deviceList = self.deviceList 109 | # ch:枚举设备 | en:Enum device 110 | ret = MvCamera.MV_CC_EnumDevices(tlayerType, deviceList) 111 | if ret != 0: 112 | print("enum devices fail! ret[0x%x]" % ret) 113 | sys.exit() 114 | 115 | if deviceList.nDeviceNum == 0: 116 | print("find no device!") 117 | sys.exit() 118 | 119 | print("Find %d devices!" % deviceList.nDeviceNum) 120 | for i in range(0, deviceList.nDeviceNum): 121 | mvcc_dev_info = cast( 122 | deviceList.pDeviceInfo[i], POINTER(MV_CC_DEVICE_INFO) 123 | ).contents 124 | if mvcc_dev_info.nTLayerType == MV_GIGE_DEVICE: 125 | print("\ngige device: [%d]" % i) 126 | strModeName = "" 127 | for per in mvcc_dev_info.SpecialInfo.stGigEInfo.chModelName: 128 | strModeName = strModeName + chr(per) 129 | print("device model name: %s" % strModeName) 130 | 131 | nip1 = ( 132 | mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0xFF000000 133 | ) >> 24 134 | nip2 = ( 135 | mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0x00FF0000 136 | ) >> 16 137 | nip3 = ( 138 | mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0x0000FF00 139 | ) >> 8 140 | nip4 = mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0x000000FF 141 | print("current ip: %d.%d.%d.%d\n" % (nip1, nip2, nip3, nip4)) 142 | 143 | return deviceList.nDeviceNum 144 | 145 | def create_cam(self, cam, nConnectionNum): 146 | 147 | deviceList = self.deviceList 148 | # ch:选择设备并创建句柄| en:Select device and create handle 149 | stDeviceList = cast( 150 | deviceList.pDeviceInfo[int(nConnectionNum)], POINTER(MV_CC_DEVICE_INFO) 151 | ).contents 152 | 153 | ret = cam.MV_CC_CreateHandle(stDeviceList) 154 | if ret != 0: 155 | print("create handle fail! ret[0x%x]" % ret) 156 | sys.exit() 157 | 158 | # ch:打开设备 | en:Open device 159 | ret = cam.MV_CC_OpenDevice(MV_ACCESS_Exclusive, 0) 160 | if ret != 0: 161 | print("open device fail! ret[0x%x]" % ret) 162 | sys.exit() 163 | 164 | # ch:探测网络最佳包大小(只对GigE相机有效) | en:Detection network optimal package size(It only works for the GigE camera) 165 | if stDeviceList.nTLayerType == MV_GIGE_DEVICE: 166 | nPacketSize = cam.MV_CC_GetOptimalPacketSize() 167 | if int(nPacketSize) > 0: 168 | ret = cam.MV_CC_SetIntValue("GevSCPSPacketSize", nPacketSize) 169 | if ret != 0: 170 | print("Warning: Set Packet Size fail! ret[0x%x]" % ret) 171 | else: 172 | print("Warning: Get Packet Size fail! ret[0x%x]" % nPacketSize) 173 | 174 | return cam 175 | 176 | def get_frame(self, img_root=None): 177 | 178 | now_time = time.time() 179 | local_time = time.localtime(now_time) 180 | dt = time.strftime("%y%m%d_%H%M%S_", local_time) 181 | 182 | for cam_id, cam in self.cam.items(): 183 | ret = cam.MV_CC_StartGrabbing() 184 | if ret != 0: 185 | print("start grabbing fail! ret[0x%x]" % ret) 186 | sys.exit() 187 | print(cam_id) 188 | 189 | nPayloadSize = self.stParam[cam_id].nCurValue 190 | data_buf = (c_ubyte * nPayloadSize)() 191 | pData = byref(data_buf) 192 | # 获取帧信息结构体 193 | stFrameInfo = MV_FRAME_OUT_INFO_EX() 194 | 195 | # memset(byref(stFrameInfo), 0, 2*sizeof(stFrameInfo)) 196 | 197 | img_save_name = img_root + dt + str(cam_id) + ".jpg" 198 | print(img_save_name) 199 | ret = cam.MV_CC_GetOneFrameTimeout(pData, nPayloadSize, stFrameInfo, 1000) 200 | # what-pData 201 | if ret == 0: 202 | # MV_Image_Undefined = 0, 203 | # MV_Image_Bmp = 1, //BMP 204 | # MV_Image_Jpeg = 2, //JPEG 205 | # MV_Image_Png = 3, //PNG 206 | # MV_Image_Tif = 4, //TIF 207 | # 图片在pData._obj内,reshape即可获得 208 | # image = np.asarray(pData._obj) 209 | # image = image.reshape( 210 | # (stFrameInfo.nHeight, stFrameInfo.nWidth, 3) 211 | 212 | stConvertParam = MV_SAVE_IMAGE_PARAM_EX() 213 | stConvertParam.nWidth = stFrameInfo.nWidth 214 | stConvertParam.nHeight = stFrameInfo.nHeight 215 | stConvertParam.pData = data_buf 216 | stConvertParam.nDataLen = stFrameInfo.nFrameLen 217 | stConvertParam.enPixelType = stFrameInfo.enPixelType 218 | stConvertParam.nJpgQuality = 99 # range[50-99] 219 | stConvertParam.enImageType = MV_Image_Jpeg 220 | bmpsize = nPayloadSize 221 | stConvertParam.nBufferSize = bmpsize 222 | bmp_buf = (c_ubyte * bmpsize)() 223 | stConvertParam.pImageBuffer = bmp_buf 224 | ret = cam.MV_CC_SaveImageEx2(stConvertParam) 225 | if ret != 0: 226 | print("save file executed failed0:! ret[0x%x]" % ret) 227 | del data_buf 228 | sys.exit() 229 | img_buff = (c_ubyte * stConvertParam.nImageLen)() 230 | memmove( 231 | byref(img_buff), 232 | stConvertParam.pImageBuffer, 233 | stConvertParam.nImageLen, 234 | ) 235 | with open(img_save_name, "wb+") as f: 236 | f.write(img_buff) 237 | 238 | else: 239 | print("no data[0x%x]" % ret) 240 | 241 | ret = cam.MV_CC_StopGrabbing() 242 | if ret != 0: 243 | print("stop grabbing fail! ret[0x%x]" % ret) 244 | del data_buf 245 | sys.exit() 246 | 247 | return True 248 | 249 | def close_cam(self): 250 | for cam in self.cam.values(): 251 | 252 | # ch:关闭设备 | Close device 253 | ret = cam.MV_CC_CloseDevice() 254 | if ret != 0: 255 | print("close deivce fail! ret[0x%x]" % ret) 256 | del data_buf 257 | sys.exit() 258 | 259 | # ch:销毁句柄 | Destroy handle 260 | ret = cam.MV_CC_DestroyHandle() 261 | if ret != 0: 262 | print("destroy handle fail! ret[0x%x]" % ret) 263 | del data_buf 264 | sys.exit() 265 | print("Stop grab image") 266 | # del data_buf 267 | 268 | 269 | def press_any_key_exit(): 270 | fd = sys.stdin.fileno() 271 | old_ttyinfo = termios.tcgetattr(fd) 272 | new_ttyinfo = old_ttyinfo[:] 273 | new_ttyinfo[3] &= ~termios.ICANON 274 | new_ttyinfo[3] &= ~termios.ECHO 275 | # sys.stdout.write(msg) 276 | # sys.stdout.flush() 277 | termios.tcsetattr(fd, termios.TCSANOW, new_ttyinfo) 278 | try: 279 | os.read(fd, 7) 280 | except: 281 | pass 282 | finally: 283 | termios.tcsetattr(fd, termios.TCSANOW, old_ttyinfo) 284 | 285 | 286 | def grab_img(): 287 | cap = HikMvCamera() 288 | WINDOW_NAME = "hik detector" 289 | while True: 290 | img_root = "./grab_img/" 291 | if not os.path.exists(img_root): 292 | os.makedirs(img_root) 293 | press_any_key_exit() 294 | start_time = time.time() 295 | ret = cap.get_frame(img_root) 296 | print("run once time:" + str(time.time() - start_time)) 297 | 298 | cap.stop_cam() 299 | 300 | 301 | if __name__ == "__main__": 302 | # 打开巨帧模式,先ifconfig找到网卡名称(enp0s31f6),第一次开启需要输入密码 303 | os.system("sudo ifconfig enp0s31f6 mtu 9000") 304 | print("巨帧模式已开启!") 305 | grab_img() 306 | -------------------------------------------------------------------------------- /hik_camera/MvCameraNode-CH.csv: -------------------------------------------------------------------------------- 1 | ,"名称(key) 2 | A节点[B节点]:表示A节点依赖于B节点",数据类型,数值范围定义,"访问模式 3 | R:可读 4 | W:可写 5 | W):非取流状态可写",描述, 6 | Device Control,DeviceType,IEnumeration,"0:Transmitter 7 | 1:Receiver 8 | 2:Transceiver 9 | 3:Peripheral",R,设备类型, 10 | ,DeviceScanType,IEnumeration,"0:Areascan 11 | 1:Linescan",R/(W),"设备sensor的扫描方式,表明是线阵相机还是面阵相机", 12 | ,DeviceVendorName,IString,任意空终止字符串,R,设备的制造商名称, 13 | ,DeviceModelName,IString,任意空终止字符串,R,设备型号, 14 | ,DeviceManufacturerInfo,Istring,任意空终止字符串,R,设备的制造商信息, 15 | ,DeviceVersion,IString,任意空终止字符串,R,设备版本, 16 | ,DeviceFirmwareVersion,IString,任意空终止字符串,R,固件版本, 17 | ,DeviceSerialNumber,IString,任意空终止字符串,R,设备序列号, 18 | ,DeviceID,Istring,任意空终止字符串,R,设备ID, 19 | ,DeviceUserID,IString,任意空终止字符串,R/W,用户自定义的名称, 20 | ,DeviceUptime,IInteger,≥0,R,设备运行时间, 21 | ,BoardDeviceType,IInteger,≥0,R,设备类型, 22 | ,DeviceConnectionSelector,IInteger,≥0,R/(W),设备连接选择, 23 | ,DeviceConnectionSpeed,IInteger,≥0,单位Mbps,R,设备连接速度, 24 | ,DeviceConnectionStatus,IEnumeration,"0:Active 25 | 1:Inactive",R,设备连接状态, 26 | ,DeviceLinkSelector,IInteger,≥0,R/(W),设备连接选择, 27 | ,DeviceLinkSpeed,IInteger,≥0,R,设备连接速度, 28 | ,DeviceLinkConnectionCount,IInteger,≥0,R,设备连接数量, 29 | ,DeviceLinkHeartbeatMode,IEnumeration ,"0:Off 30 | 1:On 31 | ",R/W ,是否需要心跳, 32 | ,DeviceLinkHeartbeatTimeout,IInteger,500-600000,R/W ,心跳超时时间, 33 | ,DeviceStreamChannelCount,IInteger,≥0,R,流通道数量, 34 | ,DeviceStreamChannelSelector,IInteger,≥0,R/W,流通道选择, 35 | ,DeviceStreamChannelType,IEnumeration,"0:Transmitter 36 | 1:Receiver ",R,流通道类型, 37 | ,DeviceStreamChannelLink,IInteger,≥0,R/(W),流通道连接数量, 38 | ,DeviceStreamChannelEndianness,IEnumeration,"0: Little 39 | 1: Big",R/(W),图像数据的字节序, 40 | ,DeviceStreamChannelPacketSize,IInteger,与相机相关。一般范围在220-9156,步进为8;,R/(W),接收端流数据的包大小, 41 | ,DeviceEventChannelCount,IInteger,≥0,R,设备支持的, 42 | ,DeviceCharacterSet,IEnumeration,"1:UTF-8 43 | 2: ASCII",R,设备寄存器中使用的字符集, 44 | ,DeviceReset,ICommand, - ,W,重启设备, 45 | ,DeviceTemperatureSelector,IEnumeration,"0: Sensor 46 | 1: Mainboard",R/W,选择某个部件温度测量, 47 | ,DeviceTemperature,IFloat ,单位摄氏度,R,选中的设备部件温度, 48 | ,FindMe,ICommand,-,W,发现当前设备, 49 | ,DeviceMaxThroughput,IInteger ,≥0,R,设备最大吞吐量(带宽), 50 | ImageFormatControl,WidthMax ,IInteger ,>0,R ,图像最大宽度,binning之后的数据, 51 | ,HeightMax ,IInteger ,>0,R ,图像最大高度,binning之后的数据, 52 | ,RegionSelector ,IEnumeration ,"0:Region0 53 | 1:Region1 54 | 2:Region2 55 | 8: All",R/(W),ROI选择器, 56 | ,RegionDestination,IEnumeration ,"0:Stream0 57 | 1:Stream1 58 | 2: Stream2",R/(W),该ROI对应的码流, 59 | ,Width,IInteger ,>0,R/(W),ROI的宽, 60 | ,Height,IInteger ,>0,R/(W),ROI的高, 61 | ,OffsetX,IInteger ,≥0,R/W ,ROI的水平方向偏移量, 62 | ,OffsetY,IInteger ,≥0,R/W ,ROI的竖直方向偏移量, 63 | ,ReverseX,IBoolean ,"True 64 | False",R/W ,是否需要水平翻转, 65 | ,ReverseY,IBoolean ,"True 66 | False",R/W ,是否需要竖直翻转, 67 | ,ReverseScanDirection,IBoolean ,≥0,R/(W),转换扫描方向, 68 | ,PixelFormat,IEnumeration ,"0x01080001:Mono8 69 | 0x01100003:Mono10 70 | 0x010C0004:Mono10Packed 71 | 0x01100005:Mono12 72 | 0x010C0006:Mono12Packed 73 | 0x01100007:Mono16 74 | 0x02180014:RGB8Packed 75 | 0x02100032:YUV422_8 76 | 0x0210001F:YUV422_8_UYVY 77 | 0x02180020:YUV8_UYV 78 | 0x020C001E:YUV411_8_UYYVYY 79 | 0x01080008:BayerGR8 80 | 0x01080009:BayerRG8 81 | 0x0108000A:BayerGB8 82 | 0x0108000B:BayerBG8 83 | 0x0110000c:BayerGR10 84 | 0x0110000d:BayerRG10 85 | 0x0110000e:BayerGB10 86 | 0x0110000f:BayerBG10 87 | 0x010C0029:BayerBG10Packed 88 | 0x010C0026:BayerGR10Packed 89 | 0x010C0027:BayerRG10Packed 90 | 0x010C0028:BayerGB10Packed 91 | 0x01100010:BayerGR12 92 | 0x01100011:BayerRG12 93 | 0x01100012:BayerGB12 94 | 0x01100013:BayerBG12 95 | 0x010C002D:BayerBG12Packed 96 | 0x010C002A:BayerGR12Packed 97 | 0x010C002B:BayerRG12Packed 98 | 0x010C002C:BayerGB12Packed 99 | 0x0110002E:BayerGR16 100 | 0x0110002F:BayerRG16 101 | 0x01100030:BayerGB16 102 | 0x01100031:BayerBG16",R/(W) ,图像像素格式,不同型号的相机,支持的像素格式有差异,以实际的为准, 103 | ,PixelSize,IEnumeration ,"8 :Bpp8 104 | 10:Bpp10 105 | 12:Bpp12 106 | 16:Bpp16 107 | 24:Bpp24 108 | 32:Bpp32 ",R/(W) ,一个像素包含的比特数, 109 | ,ImageCompressionMode,IEnumeration ,"0:Off 110 | 1:JPEG ",R/W,图像压缩模式, 111 | ,ImageCompressionQuality,IInteger ,≥50,R/(W) ,图像压缩质量, 112 | ,TestPatternGeneratorSelector,IEnumeration ,"8:Sensor 113 | 0:Region0 114 | 1: Region1 115 | 2: Region2",R/W,测试图像生成器选择, 116 | ,TestPattern[TestPatternGeneratorSelector],IEnumeration ,"0 :Off 117 | 1:Black 118 | 2:White 119 | 3:GreyHorizontalRamp 120 | 4:GreyVerticalRamp 121 | 5:GreyHorizontalRampMoving 122 | 6:GreyVerticalRampMoving 123 | 7:HorizontalLineMoving 124 | 8:VerticalLineMoving 125 | 9:ColorBar 126 | 10:FrameCounter 127 | 11:MonoBar 128 | 12:TestImage12 129 | 13:TestImage13 130 | 14:ObliqueMonoBar 131 | 15:ObliqueColorBar 132 | 16:GradualMonoBar",R/W,测试图像选择, 133 | ,BinningSelector,IEnumeration ,"0:Region0 134 | 1: Region1 135 | 2: Region2",R/W,像素合并选择, 136 | ,BinningHorizontal[BinningSelector],IEnumeration ,"1:BinningHorizontal1 137 | 2: BinningHorizontal2 138 | 3: BinningHorizontal3 139 | 4:BinningHorizontal4",R/W,水平像素合并, 140 | ,BinningVertical[BinningSelector],IEnumeration ,"1:BinningVertical1 141 | 2: BinningVertical2 142 | 3: BinningVertical3 143 | 4:BinningVertical4",R/W,垂直像素合并, 144 | ,DecimationHorizontal,IEnumeration ,"1:DecimationHorizontal1 145 | 2: DecimationHorizontal2 146 | 3: DecimationHorizontal3 147 | 4:DecimationHorizontal4",R/W,水平像素采样, 148 | ,DecimationVertical,IEnumeration ,"1:DecimationVertical1 149 | 2: DecimationVertical2 150 | 3: DecimationVertical3 151 | 4:DecimationVertical4",R/W,垂直像素采样, 152 | ,Deinterlacing,IEnumeration ,"0:Off 153 | 1:LineDuplication 154 | 2:Weave ",R/W ,, 155 | ,FrameSpecInfoSelector,IEnumeration ,"0 :Timestamp 156 | 1:Gain 157 | 2:Exposure 158 | 3:BrightnessInfo 159 | 4:WhiteBalance 160 | 5:Framecounter 161 | 6:ExtTriggerCount 162 | 7:LineInputOutput 163 | 8:ROIPosition",R/(W) ,水印信息选择, 164 | ,FrameSpecInfo,IBoolean ,"True 165 | False",R/W,是否使能该水印信息, 166 | AcquisitionControl ,AcquisitionMode,IEnumeration ,"0:SingleFrame 167 | 1:MultiFrame 168 | 2:Continuous ",R/(W) ,采集模式,单帧、多帧、连续, 169 | ,AcquisitionStart,ICommand , -,(R)/W,开始取流, 170 | ,AcquisitionStop,ICommand , -,(R)/W,结束取流, 171 | ,AcquisitionBurstFrameCount ,IInteger ,≥1,R/(W) ,一次触发采集的帧数, 172 | ,AcquisitionFrameRate,IFloat ,≥0.0,单位fps,R/W ,Trigger Mode是off的时候该值生效, 173 | ,AcquisitionFrameRateEnable,IBoolean ,,R/W ,设置的帧速率是否起效, 174 | ,AcquisitionLineRate,IInteger ,≥1,R/W ,行频设置, 175 | ,AcquisitionLineRateEnable,IBoolean ,"True 176 | False",R/W ,行频控制使能, 177 | ,ResultingLineRate,IInteger ,"≥0,单位hz",R,实际行频, 178 | ,ResultingFrameRate,IFloat ,≥0.0,单位fps,R,相机的实际采集帧率, 179 | ,TriggerSelector,IEnumeration ,"0 :AcquisitionStart 180 | 1:AcquisitionEnd 181 | 2:AcquisitionActive 182 | 3:FrameStart 183 | 4:FrameEnd 184 | 5:FrameActive 185 | 6:FrameBurstStart 186 | 7:FrameBurstEnd 187 | 8:FrameBurstActive 188 | 9:LineStart 189 | 10:ExposureStart 190 | 11:ExposureEnd 191 | 12:ExposureActive",R/W ,触发事件选择, 192 | ,TriggerMode[TriggerSelector],IEnumeration ,"0:Off 193 | 1:On",R/W ,触发模式, 194 | ,TriggerSoftware[TriggerSelector],ICommand,-,(R)/W,执行一次软触发, 195 | ,TriggerSource[TriggerSelector],IEnumeration ,"0:Line0 196 | 1:Line1 197 | 2:Line2 198 | 3.Line3 199 | 4:Counter0 200 | 7:Software 201 | 8:FrequencyConverter",R/W ,触发源, 202 | ,TriggerActivation[TriggerSelector],IEnumeration ,"0:RisingEdge 203 | 1:FallingEdge 204 | 2.LevelHigh 205 | 3.LevelLow",R/W ,触发上升沿、下降沿、高电平、低电平等, 206 | ,TriggerDelay[TriggerSelector],IFloat ,≥0.0,单位us,R/W ,触发延时, 207 | ,TriggerCacheEnable,IBoolean ,"1 208 | 0",R/W ,是否使能触发缓存, 209 | ,SensorShutterMode,IEnumeration ,"0:GlobalReset 210 | 1:TriggerRolling",R/W,设置传感器曝光模式, 211 | ,ExposureMode,IEnumeration ,"0:Timed 212 | 1:TriggerWidth ",R/W ,曝光模式选择, 213 | ,ExposureTime,IFloat ,≥0.0,单位us,R/W ,曝光时间, 214 | ,ExposureAuto,IEnumeration ,"0:Off 215 | 1:Once 216 | 2.Continuous",R/W ,自动曝光, 217 | ,AutoExposureTimeLowerLimit,IInteger ,≥2,单位us,R/W ,自动曝光下限, 218 | ,AutoExposureTimeUpperLimit,IInteger ,≥2,单位us,R/W ,自动曝光上限, 219 | ,GainShutPrior,IEnumeration ,"0:Shut 220 | 1:Gain",R/W,设置增益和曝光的优先级, 221 | ,FrameTimeoutEnable,IBoolean ,"0:Off 222 | 1:On",R/W ,帧超时使能, 223 | ,FrameTimeoutTime,IInteger ,≥87,单位ms,R/W ,帧超时时间, 224 | ,HDREnable,IBoolean ,"1 225 | 0",R/W ,是否使能宽动态, 226 | ,HDRSelector,IInteger ,"≥0,≤3",R/W ,HDR选择器, 227 | ,HDRShuter,IInteger ,≥32,R/W ,HDR曝光值, 228 | ,HDRGain,IFloat ,≥0,R/W ,HDR增益值, 229 | DigitalIOControl,LineSelector,IEnumeration ,"0:Line0 230 | 1:Line1 231 | 2:Line2 232 | 3:Line3 233 | 4:Line4",R/W,I/O选择, 234 | ,LineMode[LineSelector],IEnumeration ,"0:Input 235 | 1:Output 236 | 2:Trigger 237 | 8:Strobe",R/W,I/O模式, 238 | ,LineInverter[LineSelector],IBoolean ,"1 239 | 0",R/W ,I/O电平转换, 240 | ,LineTermination,IBoolean ,"1 241 | 0",R/W ,I/O单端差分选择, 242 | ,LineStatus[LineSelector],IBoolean,-,R/(W) ,I/O 状态, 243 | ,LineStatusAll,IInteger,≥0,R,所有I/O状态, 244 | ,LineSource[LineSelector],IEnumeration ,0:ExposureActive,R/W ,输出的事件源, 245 | ,StrobeEnable,IBoolean ,"1 246 | 0",R/W ,, 247 | ,LineDebouncerTime,IInteger,-,R/W,I/O去抖时间, 248 | ,StrobeLineDuration,IInteger ,≥0,R/W ,输出电平持续时间,单位us, 249 | ,StrobeLineDelay,IInteger ,≥0,R/W ,输出延时,单位us, 250 | ,StrobeLinePreDelay,IInteger ,≥0,R/W ,预延时,单位us, 251 | Counter And Timer Control,CounterSelector,IEnumeration ,"0:Counter0 252 | 1:Counter1 253 | 2:Counter2",R/W ,计数器选择, 254 | ,CounterEventSource[CounterSelector],IEnumeration ,"0:Off 255 | 11:Line0 256 | 12:Line1 257 | 13:Line2 258 | 1:AcquisitionTrigger 259 | 2:AcquisitionStart 260 | 3:AcquisitionEnd 261 | 4:FrameTrigger 262 | 5:FrameStart 263 | 6:FrameEnd 264 | 7:FrameBurstStart 265 | 8:FrameBurstEnd 266 | 9:FrameBurstEnd 267 | 10:LineEnd",R/W ,计数器事件源, 268 | ,CounterResetSource[CounterSelector],IEnumeration ,"0: Off 269 | 1:CounterTrigger 270 | 3:Software 271 | 5:FrameTrigger 272 | 6:FrameStart",R/W ,计数器复位源, 273 | ,CounterReset[CounterSelector],ICommand,-,(R)/W,计数器复位, 274 | ,CounterValue[CounterSelector],IInteger ,≥1,R/W ,计数器值, 275 | ,CounterCurrentValue,IInteger ,-,R,计数器当前值, 276 | Analog Control,Gain[GainSelector],IFloat ,≥0.0,单位dB,R/W ,增益值, 277 | ,GainAuto[GainSelector],IEnumeration ,"0:Off 278 | 1:Once 279 | 2.Continuous",R/W ,自动增益, 280 | ,AutoGainLowerLimit,IFloat ,≥0.0,单位dB,R/W ,自动增益下限, 281 | ,AutoGainUpperLimit,IFloat ,≥0.1,单位dB,R/W ,自动增益上限, 282 | ,ADCGainEnable,IBoolean ,"0:Off 283 | 1:On",R/W ,ADC 增益使能, 284 | ,DigitalShift,IFloat ,≥0.0,R,数字偏移调节, 285 | ,DigitalShiftEnable,IBoolean ,"0:Off 286 | 1:On",R/W ,数字偏移使能, 287 | ,Brightness,IInteger ,≥0,R/W ,亮度, 288 | ,BlackLevel[BlackLevelSelector],Iinteger,≥0,R/W ,黑电平调节, 289 | ,BlackLevelEnable,IBoolean ,"0:Off 290 | 1:On",R/W ,黑电平调节使能, 291 | ,BlackLevelAuto[BlackLevelSelector],IEnumeration ,"0:Off 292 | 1:Continuous 293 | 2:Once ",R/W ,黑电平调节方式, 294 | ,BalanceWhiteAuto,Ienumeration,"0: Off 295 | 2: Once 296 | 1. Continuous",R/W ,自动白平衡, 297 | ,BalanceRatioSelector,Ienumeration,"0: Red 298 | 1: Green 299 | 2. Blue",R/W,白平衡比例选择, 300 | ,BalanceRatio[BalanceRatioSelector],Iinteger,≥0,R ,白平衡值, 301 | ,Gamma ,IFloat ,>0.0,R/W ,伽马调节, 302 | ,GammaSelector,Ienumeration,"1:User 303 | 2:sRGB",R/W ,Gamma选择, 304 | ,GammaEnable,IBoolean ,"0:Off 305 | 1:On",R/W ,Gamma使能, 306 | ,Sharpness,IInteger ,≥0,R/W ,图像锐度, 307 | ,SharpnessEnable,IBoolean ,,R/W ,启用/禁用锐度调节, 308 | ,SharpnessAuto,IEnumeration ,"0:Off 309 | 1:Continuous 310 | 2:Once ",R/W ,锐度自动调节类型, 311 | ,Hue,IInteger ,≥0,R,色度值调节, 312 | ,HueEnable,IBoolean ,"0:Off 313 | 1:On",R/W,色度使能, 314 | ,HueAuto,IEnumeration ,"0:Off 315 | 1:Continuous 316 | 2:Once ",R/W ,灰度自动调节类型, 317 | ,Saturation,IInteger ,≥0,R,饱和度值调节, 318 | ,SaturationEnable,IBoolean ,"0:Off 319 | 1:On",R/W,饱和度使能, 320 | ,SaturationAuto,IEnumeration ,"0:Off 321 | 1:Continuous 322 | 2:Once ",R/W ,饱和度自动调节类型, 323 | ,DigitalNoiseReductionMode,IEnumeration ,"0:Off 324 | 1:Normal 325 | 2:Expert ",R/W,数字降噪等级选择, 326 | ,NoiseReduction,IInteger ,≥1,R/W,降噪值,3D降噪版本特有 327 | ,AirspaceNoiseReduction,IInteger ,≥1,R/W,空域降噪, 328 | ,TemporalNoiseReduction,IInteger ,≥1,R/W,时域降噪, 329 | ,AutoFunctionAOISelector,IEnumeration ,"0: AOI1 330 | 1: AOI2",R/W,自动AOI选择, 331 | ,AutoFunctionAOIWidth,IInteger ,≥0,R/W,自动AOI宽, 332 | ,AutoFunctionAOIHeight,IInteger ,≥0,R/W,自动AOI高, 333 | ,AutoFunctionAOIOffsetX,IInteger ,≥0,R,自动AOI水平方向偏移, 334 | ,AutoFunctionAOIOffsetY,IInteger ,≥0,R,自动AOI竖直方向偏移, 335 | ,AutoFunctionAOIUsageIntensity,IBoolean ,"0:Off 336 | 1:On",R/W,根据AOI区域自动曝光, 337 | ,AutoFunctionAOIUsageWhiteBalance,IBoolean ,"0:Off 338 | 1:On",R,根据AOI区域自动白平衡, 339 | LUT Control,LUTSelector,IEnumeration ,"0:Luminance 340 | 1:Red 341 | 2:Green 342 | 3:Blue ",R/W ,亮度、R\G\B, 343 | ,LUTEnable[LUTSelector],IBoolean ,"True 344 | False",R/W ,使能, 345 | ,LUTIndex[LUTSelector],IInteger ,≥0,R/W ,索引号, 346 | ,LUTValue[LUTSelector][LUTIndex],IInteger ,Device-specific,R/W ,值, 347 | ,LUTValueAll[LUTSelector],Register,Device-specific,R/W,LUT所有的值, 348 | EncoderControl,EncoderSelector,IEnumeration,"0 :Encoder0 349 | 1:Encoder1 350 | 2:Encoder2",R/W ,编码器选择, 351 | ,EncoderSourceA,IEnumeration,"0 :Line0 352 | 1:Line1 353 | 2:Line2 354 | 3:Line3",R/W ,编码器A源选择, 355 | ,EncoderSourceB,IEnumeration,"0 :Line0 356 | 1:Line1 357 | 2:Line2 358 | 3:Line3",R/W ,编码器B源选择, 359 | ,EncoderTriggerMode,IEnumeration,"0 :AnyDirection 360 | 1:ForwardOnly",R/W ,编码器触发模式, 361 | ,EncoderCounterMode,IEnumeration,"0 :IgnoreDirection 362 | 1:FollowDirection",R/W ,编码器计数模式, 363 | ,EncoderCounter,IInteger ,≥0,R,编码器计数器值调节, 364 | ,EncoderCounterMax,IInteger ,≥1,R/W ,编码器计数器最大值, 365 | ,EncoderCounterReset,ICommand ,-,R/W ,编码器计数器复位, 366 | ,EncoderMaxReverseCounter,IInteger ,≥1,R/W ,编码器最大反转计数器值, 367 | ,EncoderReverseCounterReset,ICommand ,-,R/W ,编码器反转计数器复位, 368 | FrequencyConverterControl,InputSource,IEnumeration,"0 :Line0 369 | 1:Line1 370 | 2:Line2 371 | 3:Line3",R/W ,分频器输入源, 372 | ,SignalAlignment,IEnumeration,"0 :RisingEdge 373 | 1:FallingEdge ",R/W ,分频器信号方向, 374 | ,PreDivider,IInteger ,≥1,R/W,前置分频器调节, 375 | ,Multiplier,IInteger ,≥1,R/W,倍频器调节, 376 | ,PostDivider,IInteger ,≥1,R/W,后置分频器调节, 377 | ShadingCorrection,ShadingSelector,IEnumeration ,"0:FPNCCorrection 378 | 1:PRNUCCorrection",R/W,明暗场校正选择, 379 | ,ActivateShading,ICommand ,-,R/(W) ,主动校正, 380 | ,NUCEnable,IBoolean,"0:Off 381 | 1:ON",R/W,NUC使能开关, 382 | ,FPNCEnable,IBoolean,"1:On 383 | 0:Off",R,FPNC状态开关, 384 | ,PRNUCEnable,IBoolean,"0:Off 385 | 1:ON",R,PRNUC状态开关, 386 | User Set Control,UserSetCurrent,IInteger ,>=0,R,当前用户参数, 387 | ,UserSetSelector,IEnumeration ,"0:Default 388 | 1:User set 1 389 | 2:User set 2 390 | 3:User set 3 391 | ",R/W ,设置载入的参数, 392 | ,UserSetLoad[UserSetSelector],ICommand , -,R/W,加载, 393 | ,UserSetSave[UserSetSelector],ICommand ,-,(R)/W,用户参数保存, 394 | ,UserSetDefault,IEnumeration ,"0:Default 395 | 1:User set 1 396 | 2:User set 2 397 | 3:User set 3",R/W ,默认状态, 398 | Transport Layer Control,PayloadSize,IInteger ,≥0,R ,一帧数据的大小, 399 | ,GevVersionMajor,IInteger,GEV主版本号,R,GEV主版本号, 400 | ,GevVersionMinor,IInteger,GEV副版本号,R,GEV子版本号, 401 | ,GevDeviceModeIsBigEndian,IBoolean,"0:not BigEndian 402 | 1: Is BigEndian",R,大端, 403 | ,GevDeviceModeCharacterSet,IEnumeration ,1:UTF8,R,字符集, 404 | ,GevInterfaceSelector,IInteger,>=0,R/W,GEV接口数, 405 | ,GevMACAddress,IInteger,Mac地址,R,MAC地址, 406 | ,GevSupportedOptionSelector,IEnumeration ,"31 :UserDefinedName 407 | 30:SerialNumber 408 | 29:HeartbeatDisable 409 | 28:LinkSpeed 410 | 27 :CCPApplicationSocket 411 | 26:ManifestTable 412 | 25:TestData 413 | 24:DiscoveryAckDelay 414 | 23 :DiscoveryAckDelayWritable 415 | 22:ExtendedStatusCodes 416 | 21:PrimaryApplicationSwitchover 417 | 6 :Action 418 | 5:PendingAck 419 | 4:EventData 420 | 3:Event 421 | 2 :PacketResend 422 | 1:WriteMem 423 | 0:CommandsConcatenation 424 | 34:IPConfigurationLLA 425 | 33:IPConfigurationDHCP 426 | 32:IPConfigurationPersistentIP 427 | 63:PAUSEFrameReception 428 | 66:StreamChannelSourceSocket",R/W,相机可以支持的功能选项, 429 | ,GevSupportedOption[GevSupportedOptionSelector],IBoolean,"0:Off 430 | 1:On",R,相机是否支持Selector 选择的功能, 431 | ,GevCurrentIPConfigurationLLA,IBoolean,"0:Off 432 | 1:On",R,IP是否为LLA, 433 | ,GevCurrentIPConfigurationDHCP[GevInterfaceSelector],IBoolean,"0:Off 434 | 1:On",R/W,IP是否为DHCP, 435 | ,GevCurrentIPConfigurationPersistentIP[GevInterfaceSelector],IBoolean,"0:Off 436 | 1:On",R/W,IP是否为静态IP, 437 | ,GevPAUSEFrameReception[GevInterfaceSelector],IBoolean,"0:Off 438 | 1:On",R/W,控制PAUSE帧是否开启, 439 | ,GevCurrentIPAddress[GevInterfaceSelector],IInteger ,ip地址,R,IP地址, 440 | ,GevCurrentSubnetMask[GevInterfaceSelector],IInteger ,子网掩码,R,子网掩码, 441 | ,GevCurrentDefaultGateway[GevInterfaceSelector],IInteger ,默认网关,R,默认网关, 442 | ,GevFirstURL,IString,-,R,XML第一选择路径, 443 | ,GevSecondURL,IString,-,R,XML第二选择路径, 444 | ,GevNumberOfInterfaces,IInteger ,≥0,R,GEV接口数, 445 | ,GevPersistentIPAddress[GevInterfaceSelector],IInteger ,≥0,R/W,静态IP地址, 446 | ,GevPersistentSubnetMask[GevInterfaceSelector],IInteger ,≥0,R/W,静态子网掩码, 447 | ,GevPersistentDefaultGateway[GevInterfaceSelector],IInteger ,≥0,R/W,静态默认网关, 448 | ,GevLinkSpeed,IInteger ,≥0,R,网络速率, 449 | ,GevMessageChannelCount,IInteger ,≥0,R,消息通道数, 450 | ,GevStreamChannelCount,IInteger ,≥0,R,流通道, 451 | ,GevHeartbeatTimeout,IInteger ,≥0,R/W ,心跳超时时间, 452 | ,GevGVCPHeartbeatDisable,IBoolean,"0:Off 453 | 1:On",R/W ,关闭心跳, 454 | ,GevTimestampTickFrequency,IInteger ,≥0,单位hz,R,时间戳频率, 455 | ,GevTimestampControlLatch,ICommand ,-,W,获取时间戳, 456 | ,GevTimestampControlReset,ICommand ,-,W,复位时间戳, 457 | ,GevTimestampControlLatchReset,ICommand ,-,W,复位时间戳同时获取时间戳, 458 | ,GevTimestampValue,IInteger ,-,R,时间戳值, 459 | ,GevCCP,IEnumeration ,"0:OpenAcess 460 | 1:ExclusiveAccess 461 | 2:ControlAccess",R/W ,App端的控制权限, 462 | ,GevStreamChannelSelector,IInteger ,>=0,R/W ,流通道选择, 463 | ,GevSCPInterfaceIndex[GevStreamChannelSelector],IInteger ,>=0,R/(W),GEV接口索引, 464 | ,GevSCPHostPort[GevStreamChannelSelector],IInteger ,>=0,R/W,主机端口, 465 | ,GevSCPDirectionGevStreamChannelSelector],IInteger ,>=0,R,表明流通道方向, 466 | ,GevSCPSFireTestPacket[GevStreamChannelSelector],IBoolean,"0:Off 467 | 1:On",R/W ,Fire Test Packet使能, 468 | ,GevSCPSDoNotFragment[GevStreamChannelSelector],IBoolean,"0:Off 469 | 1:On",R/W ,Fire Test Packet使能, 470 | ,GevSCPSBigEndian[GevStreamChannelSelector],IBoolean,"0:Off 471 | 1:On",R/W ,流数据大小端, 472 | ,PacketUnorderSupport,IBoolean,"0:Off 473 | 1:On",R/W ,是否支持GVSP包乱序发送, 474 | ,GevSCPSPacketSize,IInteger,">0,与相机相关。一般范围在220-9156,步进为8;",R/(W) ,网络包大小, 475 | ,GevSCPD[GevStreamChannelSelector],IInteger,≥0,R/W ,发包延时, 476 | ,GevSCDA[GevStreamChannelSelector],IInteger,IP地址,R/W ,流数据的目的地址, 477 | ,GevSCSP[GevStreamChannelSelector],IInteger,端口号,R,流数据的源端口, 478 | ,TLParamsLocked,IInteger,"≥0,≤1",R/W,取流时为1, 479 | -------------------------------------------------------------------------------- /hik_camera/hik_camera.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | Python wrapper for MVS camera SDK. 5 | 6 | Provides a class `HikCamera` that wraps the MVS camera SDK. 7 | Underlines the SDK's C APIs with ctypes library. 8 | """ 9 | 10 | import ctypes 11 | from ctypes import byref, POINTER, cast, sizeof, memset 12 | import os 13 | import sys 14 | from threading import Lock, Thread 15 | import time 16 | from typing import Any 17 | 18 | import boxx 19 | import numpy as np 20 | 21 | 22 | # Retrieve the path to the Hikrobot MVS SDK given the operating system 23 | if sys.platform.startswith("win"): 24 | # Hikrobot MVS SDK Location on Windows systems 25 | MVCAM_SDK_PATH = os.environ.get("MVCAM_SDK_PATH", r"C:\Program Files (x86)\MVS") 26 | MvImportDir = os.path.join(MVCAM_SDK_PATH, r"Development\Samples\Python\MvImport") 27 | else: 28 | # Hikrobot MVS SDK Location on UNIX systems 29 | MVCAM_SDK_PATH = os.environ.get("MVCAM_SDK_PATH", "/opt/MVS") 30 | MvImportDir = os.path.join(MVCAM_SDK_PATH, "Samples/64/Python/MvImport") 31 | 32 | # Import SDK python wrapper from Hikrobot MVS SDK 33 | with boxx.impt(MvImportDir): 34 | try: 35 | import MvCameraControl_class as hik 36 | except ModuleNotFoundError as e: 37 | boxx.pred( 38 | "ERROR: can't find MvCameraControl_class.py in: %s, please install MVS SDK" 39 | % MvImportDir 40 | ) 41 | raise e 42 | 43 | _lock_name_to_lock = {None: boxx.withfun()} 44 | 45 | # Convert 32-bit integer to IP address 46 | int_to_ip = ( 47 | lambda i: f"{(i & 0xff000000) >> 24}.{(i & 0x00ff0000) >> 16}.{(i & 0x0000ff00) >> 8}.{i & 0x000000ff}" 48 | ) 49 | 50 | # Convert IP address to 32-bit integer 51 | ip_to_int = lambda ip: sum( 52 | [int(s) << shift for s, shift in zip(ip.split("."), [24, 16, 8, 0])] 53 | ) 54 | 55 | 56 | def get_host_ip_by_target_ip(target_ip: str) -> str: 57 | """ 58 | Returns the IP address of the network interface 59 | that is used to connect to the camera with the given IP address. 60 | 61 | Args: 62 | target_ip (str): IP address of the camera. 63 | 64 | Returns: 65 | IP address of the network interface that is used to connect to the camera. 66 | """ 67 | import socket 68 | 69 | return [ 70 | (s.connect((target_ip, 80)), s.getsockname()[0], s.close()) 71 | for s in [socket.socket(socket.AF_INET, socket.SOCK_DGRAM)] 72 | ][0][1] 73 | 74 | 75 | def get_setting_df() -> boxx.pd.DataFrame: 76 | """ 77 | Read the MvCameraNode-CH.csv file and return a pandas DataFrame 78 | which contains the camera settings key names, dependencies, and data types. 79 | """ 80 | csv = boxx.pd.read_csv(__file__.replace("hik_camera.py", "MvCameraNode-CH.csv")) 81 | setting_df = boxx.pd.DataFrame() 82 | 83 | def to_key(key): 84 | if "[" in key: 85 | key = key[: key.index("[")] 86 | return key.strip() 87 | 88 | def get_depend(key): 89 | key = key.strip() 90 | if "[" in key: 91 | return key[key.index("[") + 1 : -1] 92 | return "" 93 | 94 | setting_df["key"] = csv[list(csv)[1]].map(to_key) 95 | setting_df["depend"] = csv[list(csv)[1]].map(get_depend) 96 | setting_df["dtype"] = csv[list(csv)[2]].map(lambda x: x.strip().lower()) 97 | return setting_df 98 | 99 | 100 | class HikCamera(hik.MvCamera): 101 | """ 102 | Class that wraps the MVS camera SDK, implementing it as a context manager. 103 | 104 | API reference: %MVCAM_SDK_PATH%\Development\Documentations\Machine Vision Camera SDK Developer Guide Windows (C) V4.3.0.chm 105 | """ 106 | 107 | continuous_adjust_exposure_cams = {} 108 | _continuous_adjust_exposure_thread_on = False 109 | 110 | def __init__( 111 | self, 112 | ip: str = None, 113 | host_ip: str = None, 114 | setting_items: dict = None, 115 | config: dict = None, 116 | ) -> None: 117 | """ 118 | Constructor for the HikCamera class. 119 | 120 | Args: 121 | ip (str, optional): 相机 IP. Defaults to ips[0]. 122 | host_ip (str, optional): 从哪个网口. Defaults to None. 123 | setting_items (dict, optional): 海康相机 xls 的标准命令和值, 更推荐 override setting. Defaults to None. 124 | config (dict, optional): 该库的 config . Defaults to dict(lock_name=None(no_lock), repeat_trigger=1). 125 | """ 126 | super().__init__() 127 | self.lock = ( 128 | Lock() 129 | ) # Instantiate a lock used to prevent multiple threads from accessing the camera at the same time during critical operations 130 | self.TIMEOUT_MS = 40000 131 | self.is_open = False 132 | self.last_time_get_frame = 0 133 | self.setting_items = setting_items 134 | self.config = config 135 | if ip is None: 136 | # Get all camera IP addresses 137 | ip = self.get_all_ips()[0] 138 | if host_ip is None: 139 | # Get the IP address of the network interface 140 | host_ip = get_host_ip_by_target_ip(ip) 141 | self._ip = ip 142 | self.host_ip = host_ip 143 | self._init() 144 | 145 | def _init(self) -> None: 146 | self._init_by_spec_ip() 147 | 148 | def setting(self) -> None: 149 | try: 150 | self.set_rgb() # 取 RGB 图 151 | 152 | except AssertionError: 153 | pass 154 | # self.set_raw() # 取 raw 图 155 | 156 | # 手动曝光, 单位秒 157 | # self.set_exposure_by_second(0.1) 158 | 159 | # 设置为自动曝光 160 | self.setitem("ExposureAuto", "Continuous") 161 | 162 | # 每隔 120s 拍一次照片来调整自动曝光, 以防止太久没调整自动曝光, 导致曝光失效 163 | # self.continuous_adjust_exposure(120) 164 | 165 | # 初始化时候, 花两秒来调整自动曝光 166 | # self.adjust_auto_exposure(2) 167 | # self.setitem("GevSCPD", 200) # 包延时, 单位 ns, 防止多相机同时拍摄丢包, 6 个百万像素相机推荐 15000 168 | 169 | def _get_one_frame_to_buf(self) -> None: 170 | """ 171 | Store camera frame and frame information in the corresponding buffers by reference. 172 | """ 173 | # Thread-safe (atomic) camera triggering (single frame) 174 | with self.lock: 175 | # Software camera trigger 176 | assert not self.MV_CC_SetCommandValue("TriggerSoftware") 177 | # Frame acquisition: 178 | # SDK C API will save the frame data to the buffer by reference (byref(self.data_buf)) 179 | # and will save the frame information to the frame information structure by reference 180 | # (self.stFrameInfo, called by reference in the python wrapper for the C API) 181 | assert not self.MV_CC_GetOneFrameTimeout( 182 | byref(self.data_buf), 183 | self.nPayloadSize, 184 | self.stFrameInfo, 185 | self.TIMEOUT_MS, 186 | ), self.ip 187 | self.last_time_get_frame = time.time() 188 | 189 | def get_frame_with_config(self) -> None: 190 | """ 191 | Frame acquisition from the camera. 192 | """ 193 | # Get user-defined configuration (if any) 194 | config = self.config if self.config else {} 195 | # Get lock name from user configuration 196 | lock_name = config.get("lock_name") 197 | # Get lock from lock name, otherwise create a new lock 198 | lock = ( 199 | _lock_name_to_lock[lock_name] 200 | if lock_name in _lock_name_to_lock 201 | else _lock_name_to_lock.setdefault(lock_name, Lock()) 202 | ) 203 | # Get number of times to trigger the camera from user configuration. 204 | # Default is 1. 205 | repeat_trigger = config.get("repeat_trigger", 1) 206 | # Thread-safe (atomic) camera triggering for the given number of times 207 | with lock: 208 | for i in range(repeat_trigger): 209 | self._get_one_frame_to_buf() 210 | 211 | def get_frame(self) -> np.ndarray: 212 | """ 213 | Get a frame from the camera. 214 | """ 215 | # Retrieve the frame information generated at camera initialization 216 | stFrameInfo = self.stFrameInfo 217 | 218 | # Get frame from the camera 219 | self.get_frame_with_config() 220 | # Frame is stored in data_buf 221 | # Frame information is stored in stFrameInfo 222 | 223 | # Get the frame width and height from the frame information 224 | h, w = stFrameInfo.nHeight, stFrameInfo.nWidth 225 | # Get the frame bit depth from the frame information 226 | self.bit = bit = self.nPayloadSize * 8 // h // w 227 | self.shape = h, w 228 | if bit == 8: 229 | # BW image 230 | img = np.array(self.data_buf).copy().reshape(*self.shape) 231 | elif bit == 24: 232 | # RGB image 233 | self.shape = (h, w, 3) 234 | img = np.array(self.data_buf).copy().reshape(*self.shape) 235 | elif bit == 16: 236 | # TODO is this a 16bit raw image? 237 | raw = np.array(self.data_buf).copy().reshape(h, w, 2) 238 | img = raw[..., 1].astype(np.uint16) * 256 + raw[..., 0] 239 | elif bit == 12: 240 | # TODO is this 12bit raw image? 241 | self.shape = h, w 242 | arr = np.array(self.data_buf).copy().astype(np.uint16) 243 | arr2 = arr[1::3] 244 | arrl = (arr[::3] << 4) + ((arr2 & ~np.uint16(15)) >> 4) 245 | arrr = (arr[2::3] << 4) + (arr2 & np.uint16(15)) 246 | img = np.concatenate([arrl[..., None], arrr[..., None]], 1).reshape( 247 | self.shape 248 | ) 249 | return img 250 | 251 | def reset(self) -> None: 252 | """ 253 | Reset the camera. 254 | """ 255 | # Thread-safe (atomic) camera reset. 256 | with self.lock: 257 | try: 258 | self.MV_CC_SetCommandValue("DeviceReset") 259 | except Exception as e: 260 | print(e) 261 | time.sleep(5) # reset 后需要等一等 262 | self.waite() 263 | self._init() 264 | self.__enter__() 265 | 266 | def robust_get_frame(self) -> np.ndarray: 267 | """ 268 | Returns a frame from the camera. 269 | If an error occurs, the camera is reset and the frame is reacquired. 270 | 271 | - Returns: 272 | A numpy array of the frame. 273 | 274 | 遇到错误, 会自动 reset device 并 retry 的 get frame 275 | - 支持断网重连后继续工作 276 | """ 277 | try: 278 | return self.get_frame() 279 | except Exception as e: 280 | print(boxx.prettyFrameLocation()) 281 | boxx.pred(type(e).__name__, e) 282 | self.reset() 283 | return self.get_frame() 284 | 285 | def _ping(self) -> bool: 286 | """ 287 | Returns True if the camera is connected to the network. 288 | """ 289 | if sys.platform.startswith("win"): 290 | return not os.system("ping -n 1 " + self.ip + " > nul") 291 | else: 292 | if os.system("which ping>/dev/null"): 293 | print("Not found ping in os.system") 294 | boxx.sleep(18) 295 | return True 296 | return not os.system("ping -c 1 -w 1 " + self.ip + " > /dev/null") 297 | 298 | def waite(self, timeout: int = 20) -> None: 299 | """ 300 | Check if the camera is connected to the network. 301 | """ 302 | begin = time.time() 303 | while not self._ping(): 304 | boxx.sleep(0.1) 305 | if time.time() - begin > timeout: 306 | raise TimeoutError(f"Lost connection to {self.ip} for {timeout}s!") 307 | 308 | @classmethod 309 | def get_all_ips(cls) -> list[str]: 310 | """ 311 | Class method that returns a list of all connected camera IP addresses. 312 | 313 | Returns: 314 | List of strings of all connected Hik camera IP addresses. 315 | """ 316 | # 通过新的进程, 绕过 hik sdk 枚举后无法 "无枚举连接相机"(使用 ip 直连)的 bug 317 | get_all_ips_py = boxx.relfile("./get_all_ips.py") 318 | ips = boxx.execmd(f'"{sys.executable}" "{get_all_ips_py}"').strip().split(" ") 319 | return list(filter(None, ips)) 320 | 321 | @classmethod 322 | def get_cams(cls, ips=None) -> dict[str, "HikCamera"]: 323 | """ 324 | Class method that returns a dictionary of all connected cameras. 325 | 326 | args: 327 | ips (list[str], optional): List of IP addresses of the cameras to connect to. Defaults to None. 328 | 329 | Returns: 330 | Dictionary of all connected Hik cameras. 331 | """ 332 | if ips is None: 333 | ips = cls.get_all_ips() 334 | else: 335 | ips = sorted(ips) 336 | cams = MultiHikCamera({ip: cls(ip) for ip in ips}) 337 | return cams 338 | 339 | get_all_cams = get_cams 340 | 341 | def set_rgb(self) -> None: 342 | """ 343 | Set camera pixel format to RGB. 344 | """ 345 | self.pixel_format = "RGB8Packed" 346 | self.setitem("PixelFormat", self.pixel_format) 347 | 348 | def set_raw(self, bit=12, packed=True) -> None: 349 | if packed: 350 | packed = bit % 8 351 | pixel_formats = [ 352 | "Bayer%s%d%s" % (color_format, bit, "Packed" if packed else "") 353 | for color_format in ["GB", "GR", "RG", "BG"] 354 | ] 355 | for pixel_format in pixel_formats: 356 | try: 357 | self.pixel_format = pixel_format 358 | self.setitem("PixelFormat", self.pixel_format) 359 | return 360 | except AssertionError: 361 | pass 362 | raise NotImplementedError( 363 | f"This camera's pixel_format not support any {bit}bit of {pixel_formats}" 364 | ) 365 | 366 | def get_exposure(self) -> int: 367 | """ 368 | Exposure time getter. 369 | """ 370 | return self["ExposureTime"] 371 | 372 | def set_exposure(self, t) -> None: 373 | """ 374 | Exposure time setter. 375 | """ 376 | assert not self.MV_CC_SetEnumValueByString("ExposureAuto", "Off") 377 | assert not self.MV_CC_SetFloatValue("ExposureTime", t) 378 | 379 | def get_exposure_by_second(self) -> float: 380 | """ 381 | Exposure time getter, in seconds. 382 | """ 383 | return self.get_exposure() * 1e-6 384 | 385 | def set_exposure_by_second(self, t) -> None: 386 | """ 387 | Exposure time setter, in seconds. 388 | """ 389 | self.set_exposure(int(t / 1e-6)) 390 | 391 | def adjust_auto_exposure(self, t=2): 392 | boxx.sleep(0.1) 393 | try: 394 | self.MV_CC_StartGrabbing() 395 | print("before_exposure", self.get_exposure()) 396 | boxx.sleep(t) 397 | print("after_exposure", self.get_exposure()) 398 | finally: 399 | self.MV_CC_StopGrabbing() 400 | 401 | def continuous_adjust_exposure(self, interval=60): 402 | """ 403 | Set camera to continuous exposure mode. 404 | 405 | 触发模式下, 会额外起一条全局守护线程, 对每个注册的相机每隔大致 interval 秒, 拍一次照 406 | 以调整自动曝光. 407 | 如果某个相机正常拍照了, 守护线程也会得知那个相机更新过曝光 408 | 该功能会避免任意两次拍照的时间间隔过小, 而导致网络堵塞 409 | # TODO 考虑分 lock_name 来起 n 个全局线程分开管理设备? 410 | """ 411 | self.setitem("ExposureAuto", "Continuous") 412 | self.interval = interval 413 | HikCamera.continuous_adjust_exposure_cams[self.ip] = self 414 | if not HikCamera._continuous_adjust_exposure_thread_on: 415 | HikCamera._continuous_adjust_exposure_thread_on = True 416 | boxx.setTimeout(self._continuous_adjust_exposure_thread, interval) 417 | 418 | @classmethod 419 | def _continuous_adjust_exposure_thread(cls): 420 | cams = [ 421 | cam for cam in cls.continuous_adjust_exposure_cams.values() if cam.is_open 422 | ] 423 | if not len(cams): 424 | cls._continuous_adjust_exposure_thread_on = False 425 | return 426 | now = time.time() 427 | last_get_frame = max([cam.last_time_get_frame for cam in cams]) 428 | 429 | # 选择最需要拍照的相机 430 | cam = sorted( 431 | cams, key=lambda cam: now - cam.last_time_get_frame - cam.interval 432 | )[-1] 433 | # 避免任意两次拍照的时间间隔过小, 而导致网络堵塞 434 | min_get_frame_gap = max(cam.interval / len(cams) / 4, 1) 435 | # 表示当前相机距离上次拍照的时间是否大于等于用户设置的拍照时间间隔(cam.interval)。 436 | sufficient_time_since_last_frame = now - cam.last_time_get_frame >= cam.interval 437 | # 表示距离所有相机中最近一次拍照的时间是否大于等于计算出的最小拍照间隔 438 | sufficient_gap_between_frames = now - last_get_frame >= min_get_frame_gap 439 | if sufficient_time_since_last_frame and sufficient_gap_between_frames: 440 | # boxx.pred("adjust", cam.ip, time.time()) 441 | try: 442 | cam.get_frame_with_config() 443 | except Exception as e: 444 | boxx.pred(type(e).__name__, e) 445 | boxx.setTimeout(cls._continuous_adjust_exposure_thread, 1) 446 | 447 | def get_shape(self) -> tuple[int, int]: 448 | """ 449 | Returns the camera frame shape. 450 | """ 451 | if not hasattr(self, "shape"): 452 | self.robust_get_frame() 453 | return self.shape 454 | 455 | @property 456 | def is_raw(self): 457 | return "Bayer" in self.__dict__.get("pixel_format", "RGB8") 458 | 459 | @property 460 | def ip(self) -> str: 461 | if not hasattr(self, "_ip"): 462 | self._ip = self.getitem("GevCurrentIPAddress") 463 | return self._ip 464 | 465 | def raw_to_uint8_rgb(self, raw, poww=1, demosaicing_method="Malvar2004"): 466 | from process_raw import RawToRgbUint8 467 | 468 | transfer_func = RawToRgbUint8( 469 | bit=self.bit, 470 | poww=poww, 471 | demosaicing_method=demosaicing_method, 472 | pattern=self.get_bayer_pattern(), 473 | ) 474 | rgb = transfer_func(raw) 475 | return rgb 476 | 477 | def save_raw(self, raw, dng_path, compress=False): 478 | from process_raw import DngFile 479 | 480 | pattern = self.get_bayer_pattern() 481 | DngFile.save(dng_path, raw, bit=self.bit, pattern=pattern, compress=compress) 482 | return dng_path 483 | 484 | def save(self, img: np.ndarray, path: str = "") -> None: 485 | """ 486 | Save an image to the specified path. 487 | """ 488 | if self.is_raw: 489 | return self.save_raw(img, path or f"/tmp/{self.ip}.dng") 490 | path = path or f"/tmp/{self.ip}.jpg" 491 | boxx.imsave(path, img) 492 | return path 493 | 494 | def get_bayer_pattern(self): 495 | assert self.is_raw 496 | if "BayerGB" in self.pixel_format: 497 | return "GBRG" 498 | elif "BayerGR" in self.pixel_format: 499 | return "GRBG" 500 | elif "BayerRG" in self.pixel_format: 501 | return "RGGB" 502 | elif "BayerBG" in self.pixel_format: 503 | return "BGGR" 504 | raise NotImplementedError(self.pixel_format) 505 | 506 | def __enter__(self) -> "HikCamera": 507 | """ 508 | Camera initialization : open, setup, and start grabbing frames from the device. 509 | """ 510 | 511 | # Open the camera with MVS SDK with exclusive access 512 | assert not self.MV_CC_OpenDevice(hik.MV_ACCESS_Exclusive, 0) 513 | 514 | # Initialize the camera with a fixes set of settings 515 | # TODO rember setting 516 | self.setitem("TriggerMode", hik.MV_TRIGGER_MODE_ON) 517 | self.setitem("TriggerSource", hik.MV_TRIGGER_SOURCE_SOFTWARE) 518 | self.setitem("AcquisitionFrameRateEnable", False) 519 | self.setting() 520 | 521 | # Set the camera settings to the user-defined settings 522 | if self.setting_items is not None: 523 | if isinstance(self.setting_items, dict): 524 | self.setting_items = self.setting_items.values() 525 | for key, value in self.setting_items: 526 | self.setitem(key, value) 527 | 528 | # Instantiate a structure to hold the payload size 529 | stParam = hik.MVCC_INTVALUE() 530 | # Initialize the payload size structure to zero 531 | memset(byref(stParam), 0, sizeof(hik.MVCC_INTVALUE)) 532 | # Get the payload size from the camera and store it in the payload size structure by reference 533 | assert not self.MV_CC_GetIntValue("PayloadSize", stParam) 534 | # Store the payload size in the camera object 535 | self.nPayloadSize = stParam.nCurValue 536 | # Allocate a buffer to store the frame data. 537 | # You'll need memory for self.nPayloadSize unsigned 8-bit integers (0-255) 538 | self.data_buf = (ctypes.c_ubyte * self.nPayloadSize)() 539 | 540 | # Instantiate a structure to hold the frame information 541 | self.stFrameInfo = hik.MV_FRAME_OUT_INFO_EX() 542 | # Initialize the frame information structure to zero 543 | memset(byref(self.stFrameInfo), 0, sizeof(self.stFrameInfo)) 544 | 545 | # Start grabbing frames from the camera 546 | assert not self.MV_CC_StartGrabbing() 547 | 548 | self.is_open = True # Mark the camera as open 549 | return self 550 | 551 | def set_OptimalPacketSize(self): 552 | # ch:探测网络最佳包大小(只对GigE相机有效) | en:Detection network optimal package size(It only works for the GigE camera) 553 | # print("GevSCPSPacketSize", self["GevSCPSPacketSize"]) 554 | with self.high_speed_lock: 555 | nPacketSize = self.MV_CC_GetOptimalPacketSize() 556 | assert nPacketSize 557 | assert not self.MV_CC_SetIntValue("GevSCPSPacketSize", nPacketSize) 558 | 559 | def __exit__(self, *l) -> None: 560 | """ 561 | Run camera termination code: stop grabbing frames and close the device. 562 | """ 563 | self.setitem("TriggerMode", hik.MV_TRIGGER_MODE_OFF) 564 | self.setitem("AcquisitionFrameRateEnable", True) 565 | assert not self.MV_CC_StopGrabbing() 566 | self.MV_CC_CloseDevice() 567 | self.is_open = False 568 | 569 | def __del__(self) -> None: 570 | self.MV_CC_DestroyHandle() 571 | 572 | def MV_CC_CreateHandle(self, mvcc_dev_info: hik.MV_CC_DEVICE_INFO) -> None: 573 | """ 574 | Create a handle to a GigE camera given its device info. 575 | """ 576 | self.mvcc_dev_info = mvcc_dev_info 577 | self._ip = int_to_ip(mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp) 578 | assert not super().MV_CC_CreateHandle(mvcc_dev_info) 579 | 580 | high_speed_lock = Lock() 581 | setting_df = get_setting_df() 582 | 583 | def getitem(self, key: str) -> Any: 584 | """ 585 | Get a camera setting value given its key. 586 | """ 587 | # Get setting dataframe 588 | df = self.setting_df 589 | # Get key setting data type 590 | dtype = df[df.key == key]["dtype"].iloc[0] 591 | # Retrieve parameter getter from MVS SDK for the given data type 592 | if dtype == "iboolean": 593 | get_func = self.MV_CC_GetBoolValue 594 | value = ctypes.c_bool() 595 | if dtype == "icommand": 596 | get_func = self.MV_CC_GetCommandValue 597 | if dtype == "ienumeration": 598 | get_func = self.MV_CC_GetEnumValue 599 | value = ctypes.c_uint32() 600 | if dtype == "ifloat": 601 | get_func = self.MV_CC_GetFloatValue 602 | value = ctypes.c_float() 603 | if dtype == "iinteger": 604 | get_func = self.MV_CC_GetIntValue 605 | value = ctypes.c_int() 606 | if dtype == "istring": 607 | get_func = self.MV_CC_GetStringValue 608 | value = (ctypes.c_char * 50)() 609 | if dtype == "register": 610 | get_func = self.MV_CC_RegisterEventCallBackEx 611 | # print(get_func, value) 612 | # Thread-safe (atomic) parameter reading from the camera. 613 | with self.lock: 614 | assert not get_func( 615 | key, value 616 | ), f"{get_func.__name__}('{key}', {value}) not return 0" 617 | return value.value 618 | 619 | def setitem(self, key: str, value: Any) -> None: 620 | """ 621 | Set a camera setting to a given value. 622 | """ 623 | # Get setting dataframe 624 | df = self.setting_df 625 | # Get key setting data type 626 | dtype = df[df.key == key]["dtype"].iloc[0] 627 | # Retrieve parameter setter from MVS SDK for the given data type 628 | if dtype == "iboolean": 629 | set_func = self.MV_CC_SetBoolValue 630 | if dtype == "icommand": 631 | set_func = self.MV_CC_SetCommandValue 632 | if dtype == "ienumeration": 633 | if isinstance(value, str): 634 | set_func = self.MV_CC_SetEnumValueByString 635 | else: 636 | set_func = self.MV_CC_SetEnumValue 637 | if dtype == "ifloat": 638 | set_func = self.MV_CC_SetFloatValue 639 | if dtype == "iinteger": 640 | set_func = self.MV_CC_SetIntValue 641 | if dtype == "istring": 642 | set_func = self.MV_CC_SetStringValue 643 | if dtype == "register": 644 | set_func = self.MV_CC_RegisterEventCallBackEx 645 | # Thread-safe (atomic) parameter setting of the camera. 646 | with self.lock: 647 | assert not set_func( 648 | key, value 649 | ), f"{set_func.__name__}('{key}', {value}) not return 0" 650 | 651 | __getitem__ = getitem 652 | __setitem__ = setitem 653 | 654 | def _init_by_spec_ip(self) -> None: 655 | """ 656 | Create a handle to reference a GigE camera 657 | given its IP address and the IP address of the network interface. 658 | 659 | MVS SDK 有 Bug, 在 linux 下 调用完"枚举设备" 接口后, 再调用"无枚举连接相机" 会无法打开相机. 660 | 同一个进程的 SDK 枚举完成后不能再直连. 需要新建一个进程. 或者不枚举 直接直连就没问题 661 | """ 662 | # Instantiate a device info structure 663 | stDevInfo = hik.MV_CC_DEVICE_INFO() 664 | # Instantiate a GigE device info structure 665 | stGigEDev = hik.MV_GIGE_DEVICE_INFO() 666 | # Set the GigE device info structure's IP address to the camera's IP address 667 | stGigEDev.nCurrentIp = ip_to_int(self.ip) 668 | # Set the GigE device info structure's network interface IP address to the network interface's IP address 669 | stGigEDev.nNetExport = ip_to_int(self.host_ip) 670 | stDevInfo.nTLayerType = hik.MV_GIGE_DEVICE # When using GigE cameras 671 | # Set the device info structure's GigE device info to the GigE device info structure 672 | stDevInfo.SpecialInfo.stGigEInfo = stGigEDev 673 | # Create a handle to reference the camera given its device info 674 | assert not self.MV_CC_CreateHandle(stDevInfo) 675 | 676 | def _init_by_enum(self) -> None: 677 | stDevInfo = self._get_dev_info(self.ip) 678 | assert not self.MV_CC_CreateHandle(stDevInfo) 679 | 680 | @classmethod 681 | def _get_dev_info(cls, ip: str = None) -> dict[str, hik.MV_CC_DEVICE_INFO]: 682 | """ 683 | Class method that returns a list of all connected camera IP addresses 684 | and their corresponding device info. 685 | 686 | Args: 687 | ip (str, optional): IP address of the camera. Defaults to None. 688 | 689 | Returns: 690 | Dict of all connected Hik camera IP addresses and their device info. 691 | """ 692 | if not hasattr(cls, "ip_to_dev_info"): 693 | ip_to_dev_info = {} 694 | # Instantiate a device info list structure 695 | deviceList = hik.MV_CC_DEVICE_INFO_LIST() 696 | # Set device communication protocol 697 | tlayerType = hik.MV_GIGE_DEVICE # | MV_USB_DEVICE 698 | # Enumerate all devices on the network by MVS SDK APIs call. 699 | assert not hik.MvCamera.MV_CC_EnumDevices(tlayerType, deviceList) 700 | # Iterate through all devices on the network and retrieve devices IPs 701 | for i in range(0, deviceList.nDeviceNum): 702 | # Cast MVS device info structure pointer to ctypes device info structure pointer and retrieve device info 703 | mvcc_dev_info = cast( 704 | deviceList.pDeviceInfo[i], POINTER(hik.MV_CC_DEVICE_INFO) 705 | ).contents 706 | # Get the device IP address from the device info structure 707 | _ip = int_to_ip(mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp) 708 | if mvcc_dev_info.nTLayerType == hik.MV_GIGE_DEVICE: 709 | ip_to_dev_info[_ip] = mvcc_dev_info 710 | cls.ip_to_dev_info = { 711 | ip: ip_to_dev_info[ip] for ip in sorted(ip_to_dev_info) 712 | } 713 | if ip is None: 714 | return cls.ip_to_dev_info 715 | return cls.ip_to_dev_info[ip] 716 | 717 | @classmethod 718 | def get_cam(cls) -> "HikCamera": 719 | """ 720 | Returns the first connected camera. 721 | """ 722 | ips = cls.get_all_ips() 723 | cam = cls(ips[0]) 724 | return cam 725 | 726 | 727 | class MultiHikCamera(dict): 728 | def __getattr__(self, attr): 729 | if not callable(getattr(next(iter(self.values())), attr)): 730 | return {k: getattr(cam, attr) for k, v in self.items()} 731 | 732 | def func(*args, **kwargs): 733 | threads = [] 734 | res = {} 735 | 736 | def _func(ip, cam): 737 | res[ip] = getattr(cam, attr)(*args, **kwargs) 738 | 739 | for ip, cam in self.items(): 740 | thread = Thread(target=_func, args=(ip, cam)) 741 | thread.start() 742 | threads.append(thread) 743 | # thread.join() 744 | [thread.join() for thread in threads] 745 | res = {ip: res[ip] for ip in sorted(res)} 746 | return res 747 | 748 | return func 749 | 750 | def __enter__(self): 751 | self.__getattr__("__enter__")() 752 | return self 753 | 754 | def __exit__(self, *l): 755 | self.__getattr__("__exit__")(*l) 756 | 757 | 758 | if __name__ == "__main__": 759 | 760 | ips = HikCamera.get_all_ips() 761 | print("All camera IP adresses:", ips) 762 | ip = ips[0] 763 | cam = HikCamera(ip) 764 | with cam, boxx.timeit("cam.get_frame"): 765 | img = cam.robust_get_frame() # Default is RGB 766 | print("Saveing image to:", cam.save(img)) 767 | 768 | print("-" * 40) 769 | 770 | cams = HikCamera.get_all_cams() 771 | with cams, boxx.timeit("cams.get_frame"): 772 | imgs = cams.robust_get_frame() # 返回一个 dict, key 是 ip, value 是图像 773 | print("imgs = cams.robust_get_frame()") 774 | boxx.tree(imgs) 775 | --------------------------------------------------------------------------------