Python调用海康SDK对接摄像机

2023-05-16

以前做过的项目都是通过 ffmpeg c++ 来捕获摄像机的 RSTP 视频流来处理视频帧,抽空看了一下海康的SDK说明,使用 python ctypes方式a实现了对海康SDK DLL的调用, 可以进行视频预览、抓图、抽帧、云台控制、布防等任务,由于调用的是C++库,速度也很快。如果不要求对视频帧进行实时智能算法分析的话,python的速度也能满足要求,而且开发效率高。

下面简介一下开发流程与关键步骤。

1、海康SDK开发包简介

在海康威视官网注册后,可以下载开发最新的SDK,其中包含说明书。

海康 SDK 构成与其它C/C++的SDK是类似的,主要由 头文件,库文件( ,lib, .so) 以及静态链接库DLL组成, linux下库文件只有.so。

python项目导入SDK 相当简单,只需要将相关DLL文件拷贝至python项目文件夹即可,主要是下面几个文件
HCNetSDK.dll
HCCore.dll
HCPreview.dll
PlayCtrl.dll
HCCoreDevCfg.dll
或者放入1个特定文件夹,将这个文件夹加入到系统环境变量path中。只要程序运行时可以找到 DLL 就可以。

2、调用SDK的基本流程

说明书上写得很详细,以实现预览的过程为例,流程如下:
在这里插入图片描述

3、主要步骤介绍

1)如何导入SDK DLL

python如何调用 C/C++ DLL,请参考本人文章:Python 使用 ctypes 调用 C/C++ DLL 动态链接库

由于海康的接口较多,对每个接口函数及参数重新申明,这是个体力活。因此建议大家在学习完ctypes以后,参考本文,自己用python ctypes 语法来封装你所需要的海康SDK 接口。
用 ctypes 调用sdk函数的方式,基本思路是:

  • 先用ctype来声明 SDK接口函数的形参与返回值,目的是python代码类型与 C++类型能匹配上
  • 建议采用签名函数方式,分别申明函数参数artstypes,返回值类型 restype,好处是使用结构清晰。

也可以使用 Cython 来调用海康SDK,调用速度可以直接与C++编程相比了。然而,Cython学习的难度明显高于 ctypes,如果C++平时用得少,不建议用 Cython.

用ctypes 声明 SDK接口函数的形参与返回值

首先要做的就是用python ctypes 将 DLL中的接口函数的形参重新定义。如 SDK中注册设备的方法:

LONG NET_DVR_Login_V40(
LPNET_DVR_USER_LOGIN_INFO pLoginInfo,
LPNET_DVR_DEVICEINFO_V40 lpDeviceInfo);
两个形参是结构指针,python中没有直接对应的类型,因此需要通过ctypes 类型重新定义:

NET_DVR_Login_V40()参数

# NET_DVR_Login_V40()参数
class NET_DVR_USER_LOGIN_INFO(Structure):
    _fields_ = [
        ("sDeviceAddress", c_char * 129),  # 设备地址,IP 或者普通域名
        ("byUseTransport", c_byte),  # 是否启用能力集透传:0- 不启用透传,默认;1- 启用透传
        ("wPort", c_uint16),  # 设备端口号,例如:8000
        ("sUserName", c_char * 64),  # 登录用户名,例如:admin
        ("sPassword", c_char * 64),  # 登录密码,例如:12345
        ("cbLoginResult", fLoginResultCallBack),  # 登录状态回调函数,bUseAsynLogin 为1时有效
        ("pUser", c_void_p),  # 用户数据
        ("bUseAsynLogin", c_uint32),  # 是否异步登录:0- 否,1- 是
        ("byProxyType", c_byte),  # 0:不使用代理,1:使用标准代理,2:使用EHome代理
        ("byUseUTCTime", c_byte),
        # 0-不进行转换,默认,1-接口上输入输出全部使用UTC时间,SDK完成UTC时间与设备时区的转换,2-接口上输入输出全部使用平台本地时间,SDK完成平台本地时间与设备时区的转换
        ("byLoginMode", c_byte),  # 0-Private 1-ISAPI 2-自适应
        ("byHttps", c_byte),  # 0-不适用tls,1-使用tls 2-自适应
        ("iProxyID", c_uint32),  # 代理服务器序号,添加代理服务器信息时,相对应的服务器数组下表值
        ("byVerifyMode", c_byte),  # 认证方式,0-不认证,1-双向认证,2-单向认证;认证仅在使用TLS的时候生效;
        ("byRes2", c_byte * 119)]
LPNET_DVR_USER_LOGIN_INFO = POINTER(NET_DVR_USER_LOGIN_INFO)

设备参数结构体 V40

# 设备参数结构体 V40
class NET_DVR_DEVICEINFO_V40(ctypes.Structure):
    _fields_ = [
        ('struDeviceV30', NET_DVR_DEVICEINFO_V30),  # 设备信息
        ('bySupportLock', c_byte),  # 设备支持锁定功能,该字段由SDK根据设备返回值来赋值的。bySupportLock为1时,dwSurplusLockTime和byRetryLoginTime有效
        ('byRetryLoginTime', c_byte),  # 剩余可尝试登陆的次数,用户名,密码错误时,此参数有效
        ('byPasswordLevel', c_byte),  # admin密码安全等级
        ('byProxyType', c_byte),  # 代理类型,0-不使用代理, 1-使用socks5代理, 2-使用EHome代理
        ('dwSurplusLockTime', c_uint32),  # 剩余时间,单位秒,用户锁定时,此参数有效
        ('byCharEncodeType', c_byte),  # 字符编码类型
        ('bySupportDev5', c_byte),  # 支持v50版本的设备参数获取,设备名称和设备类型名称长度扩展为64字节
        ('bySupport', c_byte),   # 能力集扩展,位与结果:0- 不支持,1- 支持
        ('byLoginMode', c_byte),  # 登录模式:0- Private登录,1- ISAPI登录
        ('dwOEMCode', c_uint32),  # OEM Code
        ('iResidualValidity', c_uint32),  # 该用户密码剩余有效天数,单位:天,返回负值,表示密码已经超期使用,例如“-3表示密码已经超期使用3天”
        ('byResidualValidity', c_byte),  # iResidualValidity字段是否有效,0-无效,1-有效
        ('bySingleStartDTalkChan', c_byte),  # 独立音轨接入的设备,起始接入通道号,0-为保留字节,无实际含义,音轨通道号不能从0开始
        ('bySingleDTalkChanNums', c_byte),  # 独立音轨接入的设备的通道总数,0-表示不支持
        ('byPassWordResetLevel', c_byte),  # 0-无效,
        # 1- 管理员创建一个非管理员用户为其设置密码,该非管理员用户正确登录设备后要提示“请修改初始登录密码”,未修改的情况下,用户每次登入都会进行提醒;
        # 2- 当非管理员用户的密码被管理员修改,该非管理员用户再次正确登录设备后,需要提示“请重新设置登录密码”,未修改的情况下,用户每次登入都会进行提醒。
        ('bySupportStreamEncrypt', c_byte),  # 能力集扩展,位与结果:0- 不支持,1- 支持
        # bySupportStreamEncrypt & 0x1 表示是否支持RTP/TLS取流
        # bySupportStreamEncrypt & 0x2 表示是否支持SRTP/UDP取流
        # bySupportStreamEncrypt & 0x4 表示是否支持SRTP/MULTICAST取流
        ('byMarketType', c_byte),  # 0-无效(未知类型),1-经销型,2-行业型
        ('byRes2', c_byte * 238)  #保留,置为0
    ]
LPNET_DVR_DEVICEINFO_V40 = POINTER(NET_DVR_DEVICEINFO_V40)

调用sdk函数示例

下面用注册设备函数 NET_DVR_Login_V40 为例,展示初始化参数,赋值,调用dll函数步骤:
def LoginDev(sdk):
    # 登录注册设备
    '''
    device_info = NET_DVR_DEVICEINFO_V30()
    lUserId = Objdll.NET_DVR_Login_V30(
        DEV_IP, DEV_PORT, DEV_USER_NAME, DEV_PASSWORD, byref(device_info))
    '''
    # 用户注册设备
    # c++传递进去的是byte型数据,需要转成byte型传进去,否则会乱码
    # 登录参数,包括设备地址、登录用户、密码等
    struLoginInfo = NET_DVR_USER_LOGIN_INFO()
    struLoginInfo.bUseAsynLogin = 0  # 同步登录方式
    struLoginInfo.sDeviceAddress = bytes("192.168.99.247", "ascii")  # 设备IP地址
    struLoginInfo.wPort = 8000  # 设备服务端口
    struLoginInfo.sUserName = bytes("admin", "ascii")  # 设备登录用户名
    struLoginInfo.sPassword = bytes("Admin&123", "ascii")  # 设备登录密码
    struLoginInfo.byLoginMode = 0
    struDeviceInfoV40 = NET_DVR_DEVICEINFO_V40()
    UserID = sdk.NET_DVR_Login_V40(
        byref(struLoginInfo), byref(struDeviceInfoV40))

    return (UserID, struDeviceInfoV40)

其它sdk函数调用过程也是类似的。

2) 开发框架说明l

因为只是1个练习,为了省事,采用了 python Tkinter 来开发界面,也可以使用QT来做。

3) 几个技术点说明

设备注册后,必须要调用 NET_DVR_RealPlay_V40() 函数进行预览画面,后面的抓图、读帧以及云台控制均要求先执行这一步。
该函数定义如下:
LONG NET_DVR_RealPlay_V40(
LONG lUserID,
LPNET_DVR_PREVIEWINFO lpPreviewInfo,
REALDATACALLBACK fRealDataCallBack_V30,
void *pUser);

可以有1个回调函数 REALDATACALLBACK
typedef void(CALLBACK *REALDATACALLBACK)(
LONG lRealHandle,
DWORD dwDataType,
BYTE *pBuffer,
DWORD dwBufSize,
void *pUser);

注意要计划好显示视频的窗口控件,获取该窗口的句柄,传给NET_DVR_RealPlay_V40,或回调函数。

捕获视频码流以及解码显示均由该回调函数完成,捕获原始的YUV视频帧也在此进行。
如果要对视频进行分析,有两种方法
1)通过sdk的抓图接口函数抓图进行分析,如 NET_DVR_CapturePicture
2) 实时性要求高,可将预览码流中的原始YUV帧l转换为RGB后,再进行
处理。

  1. 代码如下
# coding=utf-8

import os
import platform
import tkinter
from tkinter import *
from tkinter import ttk
from HCNetSDK import *
from PlayCtrl import *
from time import sleep
import ctypes

# 登录的设备信息
WINDOWS_FLAG = True
win = None  # 预览窗口
funcRealDataCallBack_V30 = None  # 实时预览回调函数,需要定义为全局的

PlayCtrl_Port = c_long(-1)  # 播放句柄
Playctrldll = None  # 播放库
FuncDecCB = None   # 播放库解码回调函数,需要定义为全局的

# 获取当前系统环境


def GetPlatform():
    sysstr = platform.system()
    print('' + sysstr)
    if sysstr != "Windows":
        global WINDOWS_FLAG
        WINDOWS_FLAG = False

# 设置SDK初始化依赖库路径


def SetSDKInitCfg():
    # 设置HCNetSDKCom组件库和SSL库加载路径
    # print(os.getcwd())
    if WINDOWS_FLAG:
        strPath = os.getcwd().encode('gbk')
        sdk_ComPath = NET_DVR_LOCAL_SDK_PATH()
        sdk_ComPath.sPath = strPath
        Objdll.NET_DVR_SetSDKInitCfg(2, byref(sdk_ComPath))
        Objdll.NET_DVR_SetSDKInitCfg(3, create_string_buffer(
            strPath + b'\libcrypto-1_1-x64.dll'))
        Objdll.NET_DVR_SetSDKInitCfg(
            4, create_string_buffer(strPath + b'\libssl-1_1-x64.dll'))
    else:
        strPath = os.getcwd().encode('utf-8')
        sdk_ComPath = NET_DVR_LOCAL_SDK_PATH()
        sdk_ComPath.sPath = strPath
        Objdll.NET_DVR_SetSDKInitCfg(2, byref(sdk_ComPath))
        Objdll.NET_DVR_SetSDKInitCfg(
            3, create_string_buffer(strPath + b'/libcrypto.so.1.1'))
        Objdll.NET_DVR_SetSDKInitCfg(
            4, create_string_buffer(strPath + b'/libssl.so.1.1'))


def LoginDev(sdk):
    # 登录注册设备
    '''
    device_info = NET_DVR_DEVICEINFO_V30()
    lUserId = Objdll.NET_DVR_Login_V30(
        DEV_IP, DEV_PORT, DEV_USER_NAME, DEV_PASSWORD, byref(device_info))
    '''
    # 用户注册设备
    # c++传递进去的是byte型数据,需要转成byte型传进去,否则会乱码
    # 登录参数,包括设备地址、登录用户、密码等
    struLoginInfo = NET_DVR_USER_LOGIN_INFO()
    struLoginInfo.bUseAsynLogin = 0  # 同步登录方式
    struLoginInfo.sDeviceAddress = bytes("192.168..200", "ascii")  # 设备IP地址
    struLoginInfo.wPort = 8000  # 设备服务端口
    struLoginInfo.sUserName = bytes("admin", "ascii")  # 设备登录用户名
    struLoginInfo.sPassword = bytes("123456", "ascii")  # 设备登录密码
    struLoginInfo.byLoginMode = 0
    struDeviceInfoV40 = NET_DVR_DEVICEINFO_V40()
    UserID = sdk.NET_DVR_Login_V40(
        byref(struLoginInfo), byref(struDeviceInfoV40))

    return (UserID, struDeviceInfoV40)


def DecCBFun(nPort, pBuf, nSize, pFrameInfo, nUser, nReserved2):
    # 解码回调函数
    if pFrameInfo.contents.nType == 3:
        # 解码返回视频YUV数据,将YUV数据转成jpg图片保存到本地
        # 如果有耗时处理,需要将解码数据拷贝到回调函数外面的其他线程里面处理,避免阻塞回调导致解码丢帧
        sFileName = ('pic/test_stamp[%d].jpg' %
                     pFrameInfo.contents.nStamp)
        nWidth = pFrameInfo.contents.nWidth
        nHeight = pFrameInfo.contents.nHeight
        nType = pFrameInfo.contents.nType
        dwFrameNum = pFrameInfo.contents.dwFrameNum
        nStamp = pFrameInfo.contents.nStamp
        print(nWidth, nHeight, nType, dwFrameNum, nStamp, sFileName)

        lRet = Playctrldll.PlayM4_ConvertToJpegFile(
            pBuf, nSize, nWidth, nHeight, nType, c_char_p(sFileName.encode()))
        if lRet == 0:
            print('PlayM4_ConvertToJpegFile fail, error code is:',
                  Playctrldll.PlayM4_GetLastError(nPort))
        else:
            print('PlayM4_ConvertToJpegFile success')


def RealDataCallBack_V30(lPlayHandle, dwDataType, pBuffer, dwBufSize, pUser):
    # 码流回调函数
    if dwDataType == NET_DVR_SYSHEAD:
        # 设置流播放模式
        Playctrldll.PlayM4_SetStreamOpenMode(PlayCtrl_Port, 0)
        # 打开码流,送入40字节系统头数据
        if Playctrldll.PlayM4_OpenStream(PlayCtrl_Port, pBuffer, dwBufSize, 1024*1024):
            # 设置解码回调,可以返回解码后YUV视频数据
            global FuncDecCB
            FuncDecCB = DECCBFUNWIN(DecCBFun)
            Playctrldll.PlayM4_SetDecCallBackExMend(
                PlayCtrl_Port, FuncDecCB, None, 0, None)
            # 开始解码播放
            if Playctrldll.PlayM4_Play(PlayCtrl_Port, cv.winfo_id()):
                print(u'播放库播放成功')
            else:
                print(u'播放库播放失败')
        else:
            print(u'播放库打开流失败')
    elif dwDataType == NET_DVR_STREAMDATA:
        Playctrldll.PlayM4_InputData(PlayCtrl_Port, pBuffer, dwBufSize)
    else:
        print(u'其他数据,长度:', dwBufSize)


def OpenPreview(Objdll, lUserId, callbackFun):
    '''
    打开预览
    '''
    preview_info = NET_DVR_PREVIEWINFO()
    preview_info.hPlayWnd = 0
    preview_info.lChannel = 1  # 通道号lk在
    preview_info.dwStreamType = 0  # 主码流
    preview_info.dwLinkMode = 0  # TCP
    preview_info.bBlocked = 1  # 阻塞取流
    preview_info.dwDisplayBufNum = 15  # 缓冲区大小,15*1024*1024

    # 开始预览并且设置回调函数回调获取实时流数据
    lRealPlayHandle = Objdll.NET_DVR_RealPlay_V40(
        lUserId, byref(preview_info), callbackFun, None)
    return lRealPlayHandle


def InputData(fileMp4, Playctrldll):
    while True:
        pFileData = fileMp4.read(4096)
        if pFileData is None:
            break

        if not Playctrldll.PlayM4_InputData(PlayCtrl_Port, pFileData, len(pFileData)):
            break


def click_capture():
    print("clicked capture button")
    # Objdll.NET_DVR_SetCapturePictureMode(1)
    pFileName = ctypes.c_char_p()
    pFileName.value = bytes("pic/image.jpg", "utf-8")
    # 开始抓图。
    res = Objdll.NET_DVR_CapturePicture(lRealPlayHandle, pFileName)
    if res:
        print("Successfullly capture picture, ", pFileName.value)


def click_left():
    print("clicked button up")
    # 因无测试条件,暂略


def click_right():
    print("clicked button")


def click_up():
    print("clicked button")


def click_down():
    print("clicked button")


if __name__ == '__main__':
    # 创建窗口
    win = tkinter.Tk()
    # 固定窗口大小
    win.resizable(0, 0)
    win.overrideredirect(True)

    sw = win.winfo_screenwidth()
    # 得到屏幕宽度
    sh = win.winfo_screenheight()
    # 得到屏幕高度

    # 窗口宽高
    ww = 800
    wh = 650
    x = (sw - ww) / 2
    y = (sh - wh) / 2
    win.geometry("%dx%d+%d+%d" % (ww, wh, x, y))
    # 创建一个Canvas,设置其背景色为白色
    cv = Canvas(
        win,
        width=760,
        height=460,
        bg="white",
    )
    cv.place(x=20, y=10)

    # 创建退出按键

    btn_left = Button(win, text="  左  ",
                      command=click_left).place(x=100, y=530)
    btn_right = Button(win, text="  右  ",
                       command=click_right).place(x=180, y=530)
    btn_top = Button(win, text="  上  ", command=click_up).place(x=145, y=495)
    btn_down = Button(win, text="  下  ",
                      command=click_down).place(x=145, y=565)
    btn_capture = Button(win, text="  放大  ",
                         command=click_capture).place(x=280, y=500)
    btn_capture = Button(win, text="  缩小  ",
                         command=click_capture).place(x=280, y=530)
    btn_capture = Button(win, text="  截图  ",
                         command=click_capture).place(x=280, y=560)
    lbl_ip = Label(win, text="IP地址", fg="#111").place(x=480, y=490)
    ent_ip = Entry(win).place(x=550, y=490)
    lbl_port = Label(win, text="端口", fg="#111").place(x=480, y=515)
    ent_port = Entry(win).place(x=550, y=515)
    lbl_name = Label(win, text="登录名", fg="#111").place(x=480, y=540)
    ent_name = Entry(win).place(x=550, y=540)
    lbl_password = Label(win, text="密码", fg="#111").place(x=480, y=565)
    password = StringVar()
    password_entry = ttk.Entry(
        win,
        textvariable=password,
        show='*'
    )
    password_entry.place(x=550, y=565)

    separator = ttk.Separator(win, orient='horizontal')
    separator.place(x=10, y=600, width=790)
    btn_q = Button(win, text=' 退出 ', command=win.quit)
    btn_q.place(x=660, y=610)

    # 加载库,先加载依赖库
    dname = 'D:\workplace\dependency\hik_lib\HCNetSDK.dll'
    # Objdll = ctypes.CDLL('lib/HCNetSDK.dll')  # 加载网络库
    Objdll = ctypes.cdll.LoadLibrary(dname)
    # Playctrldll = ctypes.CDLL('lib/PlayCtrl.dll')  # 加载播放库
    dname = 'D:\workplace\dependency\hik_lib\PlayCtrl.dll'
    Playctrldll = ctypes.cdll.LoadLibrary(dname)
    print("load dll successfully")
    # SetSDKInitCfg()  # 设置组件库和SSL库加载路径

    # 初始化DLL
    Objdll.NET_DVR_Init()
    print("init device successfully ")
    # 启用SDK写日志
    Objdll.NET_DVR_SetLogToFile(
        3, bytes('./SdkLog_Python/', encoding="utf-8"), False)
    print("config log to SdkLog_Python ")

    # 获取一个播放句柄
    if not Playctrldll.PlayM4_GetPort(byref(PlayCtrl_Port)):
        print(u'获取播放库句柄失败')

    # 登录设备
    (lUserId, device_info) = LoginDev(Objdll)
    if lUserId < 0:
        err = Objdll.NET_DVR_GetLastError()
        print('Login device fail, error code is: %d' %
              Objdll.NET_DVR_GetLastError())
        # 释放资源
        Objdll.NET_DVR_Cleanup()
        exit()
    print("login device ")
    # 定义码流回调函数
    funcRealDataCallBack_V30 = REALDATACALLBACK(RealDataCallBack_V30)
    # 开启预览
    lRealPlayHandle = OpenPreview(Objdll, lUserId, funcRealDataCallBack_V30)
    if lRealPlayHandle < 0:
        print('Open preview fail, error code is: %d' %
              Objdll.NET_DVR_GetLastError())
        # 登出设备
        Objdll.NET_DVR_Logout(lUserId)
        # 释放资源
        Objdll.NET_DVR_Cleanup()
        exit()

    # show Windows
    win.mainloop()

    # 关闭预览
    Objdll.NET_DVR_StopRealPlay(lRealPlayHandle)

    # 停止解码,释放播放库资源
    if PlayCtrl_Port.value > -1:
        Playctrldll.PlayM4_Stop(PlayCtrl_Port)
        Playctrldll.PlayM4_CloseStream(PlayCtrl_Port)
        Playctrldll.PlayM4_FreePort(PlayCtrl_Port)
        PlayCtrl_Port = c_long(-1)

    # 登出设备
    Objdll.NET_DVR_Logout(lUserId)

    # 释放资源
    Objdll.NET_DVR_Cleanup()


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Python调用海康SDK对接摄像机 的相关文章

  • 解决Maven打包的文件不带依赖项

    学习笔记 xff0c 参考别人的教程 Maven打包时不会自带依赖项Jar xff0c 导致运行失败 xff0c Pom文件直接加入以下语句即可 xff1a lt build gt lt plugins gt lt plugin gt lt
  • Git 回退(Revert)操作后无法重新合并的问题

    问题背景 xff1a 公司使用码云企业版作为代码托管平台 xff0c 采用master dev分支分类进行代码管理 xff0c matser分支为保护分支 xff0c 只能审核后在网页端提交合并 xff08 请求评审 xff09 此时dev
  • Synchronized锁失效的几种情况

    1 多例模式 Synchronized关键字注释在非静态方法上时 xff0c 锁对象是当前对象 xff0c 如果此时非单例调用 xff0c 会导致锁失效 xff01 解决方法 xff1a 1 使用单例模式 xff0c 或锁定唯一对象 2 事
  • SpringBoot字段注入和构造函数注入的区别

    文章背景 在使用Spring开发项目时 xff0c 我们经常需要使用依赖注入来管理对象之间的依赖关系 Spring提供了多种依赖注入方式 xff0c 如构造函数注入 Setter方法注入和字段注入等 这些方式各有优缺点 xff0c 需要根据
  • python和尚念经:实例化对象、调用方法、最全属性、最全内置函数

    一 xff1a 先搞懂定义 span class token keyword class span span class token class name Ball span span class token punctuation spa
  • 谈谈自媒体的流量变现。

    我两天前发了一条广告 xff0c 关于按摩颈椎仪的广告 自媒体做广告这事 xff0c 有些读者不喜欢 xff0c 有些读者见惯不惯 xff0c 我觉得没关系 xff0c 今天不谈具体广告 xff0c 今天就这个引子 xff0c 谈谈我对自媒
  • 在Windows上安装Ubuntu子系统系统,报错WslRegisterDistribution failed with error: 0x8007019e

    在Windows应用商店安装Ubuntu系统 xff0c 报错WslRegisterDistribution failed with error 0x8007019e 1 报错内容 Installing span class token p
  • 生成器(建造者)模式

    文章目录 思考生成者模式1 生成器模式的本质2 何时选用生成器模式3 优缺点4 生成器模式的结构5 实现生成器模式构建对象的多种表示形式生成器模式链式构建对象 思考生成者模式 生成器模式就是将对象构建和对象内部构建分离 xff0c 将一个复
  • 基于AList实现网盘挂载和WebDAV本地挂载网盘

    AList AList是一个支持多种存储 xff0c 支持网页浏览和 WebDAV 的文件列表程序 xff0c 由 gin 和 Solidjs 驱动 AList官方文档 xff1a https alist nn ci AList官方GitH
  • 常用Windows快捷键大全

    0 简要 要将电脑玩的溜 xff0c 快捷键是必须要掌握的技能 xff0c 本文汇总了一些常用的快捷键 xff0c 相信加以练习 xff0c 一定能提高你的工作效率 笔者将常用快捷键分为四个系列 xff0c 如下所示 xff1a Win 系
  • Centos8安装MySql,完美解决

    本文使用yum安装mysql linux版本为 centos 8 参考 xff1a MySQL官网yum源 MySQL官网Linux yum安装Mysql CentOS 8 yum安装软件时 xff0c 提示无法从AppStream下载 c
  • aws亚马逊服务器Ubuntu18脚本一键重装系统为centos7

    这两天注册了aws xff0c 送了一年的最低配服务器嘛 但是可使用的系统就是有Ubuntu和Redhat 都试了试不太好用 今天就在网上看到了一键重装的脚本 就记录分享一下 先后执行下列两条命令就可以 xff1a apt get inst
  • 利用excel求特定条件下的最大/小值(maxif/minif)

    欢迎关注我的公众号 xff1a Smilecoc的杂货铺 在Excel中有sumif countif等函数可以实现求特定条件下数值的加总和计数 xff0c 那么如何在一个或多个条件下求出此时的最大值或者最小值呢 xff1f 其实sumif函
  • 时间序列(一):时间序列数据与时间序列预测模型

    时间序列系列文章 xff1a 时间序列 xff08 一 xff09 xff1a 时间序列数据与时间序列预测模型 时间序列 xff08 二 xff09 xff1a 时间序列平稳性检测 时间序列 xff08 三 xff09 xff1a ARIM
  • Windows下解压tar.gz压缩文件

    一 tar gz是什么文件 xff1f 以 tar gz为后缀的文件是一种压缩文件 xff0c 在Linux和macOS下常见 xff0c Linux和macOS都可以直接解压使用这种压缩文件 二 怎么解压tar gz 一些软件支持解压ta
  • Python安装模块(包/库)的方法

    这里写目录标题 通过pip安装正常在线安装pip命令补全更改下载镜像 离线包安装库的下载库的安装whl的安装 tar gz的安装源码安装 本地安装报错 xff08 依赖 xff09 Pycharm中安装手动安装终端命令行安装 Jupyter
  • win10 安装visual studio C++ build tools 【visualcppbuildtools_full.exe】提示安装包丢失 毁坏

    win10 安装visual studio C 43 43 build tools visualcppbuildtools full exe 提示安装包丢失 毁坏 1 问题 xff1a 安装visualcppbuildtools full
  • Excel:使用powerquery进行多表合并

    注 xff1a 本文原创为 xff1a https www cnblogs com fanyu2019 p 11175827 html 本文在原创的基础上添加修改了一点内容 目录 一 单工作簿多工作表合并二 多工作簿单工作表合并三 多工作簿
  • 利用Python调用outlook自动发送邮件

    欢迎关注我的公众号 xff0c 在这里有数据相关技术经验的优质原创文章 使用Python发送邮件有两种方式 xff0c 一种是使用smtp调用邮箱的smtp服务器 xff0c 另一种是直接调用程序直接发送邮件 而在outlook中我们一般是
  • 从 Tableau文件中获取数据方法汇总

    欢迎关注我的公众号 xff0c 在这里有数据相关技术经验的优质原创文章 在实际使用Tableau中经常会遇到需要从已有的tableau文件或仪表板中导出 提取 复制数据 xff0c 本篇文章整理了相关从Tableau文件中获取数据的方法 一

随机推荐

  • Excel中的数字转文本和文本转数字

    公式方法 xff1a 数字转文本 xff1a 61 TEXT A1 34 34 文本转数字 xff1a 直接乘以1即可 数字转文本 xff1a 61 A1 1 或者使用value函数 61 value 分列方法 xff1a 在数据工具 下选
  • vlookup查找匹配值超过255个字符显示#Value的解决办法

    错误原因 这一个错误的起源于在匹配字符串是否相等时出现 Value错误 xff0c 如下图黄色标注的部分 在Excel中提示的错误是 公式中所用的某个值是错误的数据类型 xff08 a value used in the formula i
  • HEXO部署博客内容到github报错

    今天在更新部署博客内容时出现了如下报错 xff1a 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 6
  • GO通过HTTP获取API的返回值(response)

    目录 net httpnet url net http span class token keyword import span span class token string 34 net http 34 span Go语言提供了HTTP
  • STM32F103学习笔记(2.3)——读GPIO 按键

    为了读取引脚的高低电平 xff0c 就需要将引脚配置成输入模式 xff0c 并读取IDR寄存器 目录 寄存器配置 端口配置低寄存器 GPIOx CRL x 61 A E 端口输入数据寄存器 GPIOx IDR x 61 A E 按键点灯 寄
  • Windows系统下,Ubuntu安装至移动硬盘(简单分析与详细安装教程)

    前期说明 博主因学业要求 xff0c 需要同时使用Windows系统与Linux系统 xff0c 故而考虑安装双系统 但个人电脑硬盘仅剩100G左右大小 xff0c 安装双系统可能导致硬盘容量不足 xff0c 恰好博主手中有个空闲的移动硬盘
  • QT开发学习4(远程调试 Qt 程序)

    2 5 1 rsync 方式 Qt 远程调试 在 Qt Creator 中默认情况下 xff0c 会使用 sftp 或 rsync 发送程序到板卡 由于正点原子 I MX6 U 出厂 Qt 文件系统 xff08 文件系统 V1 9 及之后的
  • 使用 python requests 模块发送 http 请求及接收响应

    内容概要 如何构建GET 与 POST request 请求消息对 request 的header query string message body 定制化http header参数 content type 的设置分析request r
  • 汇编.section和.text以及入口地址解释

    section data 汇编程序中以 开头的名称并不是指令的助记符 xff0c 不会被翻译成机器指令 xff0c 而是给汇编器一些特殊指示 xff0c 称为汇编指示 xff08 Assembler Directive xff09 或伪操作
  • Linux - 配置Linux用户的环境变量- Anaconda3的环境变量配置

    目录 临时生效变量环境变量的分类 xff08 永久生效 xff09 如何让某个命令永久生效环境变量配置文件的运行顺序参考链接 Linux 操作系统的环境变量 xff0c 看似很复杂 xff0c 其实不然 我们通常用到的Windows 操作系
  • 兔子繁殖问题

    首先读懂题目 xff0c 知道运算规律后在使用斐波那契数列九很好解决啦 7 26 兔子繁殖问题 10 分 已知有一对兔子 xff0c 每个月可以生一对兔子 xff0c 而小兔子一个月后又可以生一对小兔子 比如 2月份出生的小兔子4月份可以生
  • 华为Atlas200DK环境配置指南(版本20.0.0)

    官方参考文档 https support huaweicloud com usermanual A200dk 3000 atlas200dk 02 0024 html 务必保证配置时版本 20 0 0 一致 1 配置开发环境 自己电脑 若不
  • 软件工程(速成)——第三章 需求分析

    一 需求分析 1 需求分析的概念与任务 xff1a 需求分析是软件定义时期的最后一个阶段 xff0c 它的基本任务是准确地回答 系统必须做什么 这个问题 二 分析建模与规格说明 需求分析应该建立三种模型 xff1a 数据模型 功能模型 行为
  • Pytorch 深度学习实战:视频自动打码

    点击上方 小白学视觉 xff0c 选择加 34 星标 34 或 置顶 重磅干货 xff0c 第一时间送达 人脸识别 人脸识别是一门比较成熟的技术 它的身影随处可见 xff0c 刷脸支付 xff0c 信息审核 xff0c 监控搜索 xff0c
  • 基于深度学习的视觉目标跟踪方法

    点击上方 小白学视觉 xff0c 选择加 34 星标 34 或 置顶 重磅干货 xff0c 第一时间送达 以前写过一个 自动驾驶中的目标跟踪 介绍 xff0c 这次重点放在深度学习和摄像头数据方面吧 先提一下以前说的那篇综述 xff1a 3
  • 递归解决赶鸭子问题,角骨定理

    一 题目分析 用递归方法设计下列各题 xff0c 并给出每道题目的递归出口 xff08 递归结束的条件 xff09 和递归表达式 同时考虑题目可否设计为非递归方法 xff0c 如果可以 xff0c 设计出非递归的算法 1 一个人赶着鸭子去每
  • 最详细ubuntu16.04安装nvidia显卡驱动(完全无经验小白教程)

    ubuntu16 04安装nvidia显卡驱动 1 禁用nouveau ubuntu 16 04默认安装了第三方开源的驱动程序nouveau xff0c 安装nvidia显卡驱动首先需要禁用nouveau xff0c 不然会碰到冲突的问题
  • 修改ssh端口重启服务报错error: Bind to port 8822 on :: failed: Permission denied

    root 64 BabyishRecent VM vi etc ssh sshd config root 64 BabyishRecent VM systemctl restart sshdJob for sshd service fail
  • Linux之iptables(一、防火墙的概念)

    Linux之iptables 一 防火墙的概念 防火墙的概念 一 安全技术 入侵检测与管理系统 xff08 Intrusion Detection Systems xff09 xff1a 特点是不阻断任何网络访问 xff0c 量化 定位来自
  • Python调用海康SDK对接摄像机

    以前做过的项目都是通过 ffmpeg c 43 43 来捕获摄像机的 RSTP 视频流来处理视频帧 xff0c 抽空看了一下海康的SDK说明 xff0c 使用 python ctypes方式a实现了对海康SDK DLL的调用 可以进行视频预