标签: Python

  • 在 Ubuntu 上安装 MariaDB 并使用 PyMySQL 操作

    本文主要内容:在 Ubuntu 20.04 上安装、保护、使用 MariaDB 和 PyMySQL。

    本系列的相关文章:

    在 Ubuntu 安装 MariaDB

    本文的主要参考内容来自:https://cloud.tencent.com/developer/article/1631666,以下步骤目前亲测可用。MariaDB 是一个开源的关系型数据库管理系统,向后兼容,可替代 MySQL。MariaDB 是由 MySQL 的一些原开发者和很多社区成员共同开发的。

    前提条件

    拥有 Ubuntu 服务器的管理权限,或者以 root 身份 或者以拥有 sudo 权限的用户身份登录系统。

    在 Ubuntu 上安装 MariaDB

    sudo apt update
    sudo apt install mariadb-server

    一旦安装完成,MariaDB 服务将会自动启动。 想要验证数据库服务器是否正在运行,输入:

    sudo systemctl status mariadb

    输出将会显示服务已经启用,并且正在运行。

    保护 MariaDB

    MariaDB 服务器有一个脚本叫做mysql_secure_installation用来提高数据库服务器安全。

    不带参数运行脚本:

    sudo mysql_secure_installation

    脚本将会提示输入 root 密码:

    Enter current password for root (enter for none):

    因为没有设置 root 密码,仅仅输入回车”Enter”。在下一个提示中,会被问到是否 MySQL root 用户设置密码:

    Set root password? [Y/n] n

    输入n。在 Ubuntu 上, MariaDB 用户默认使用auth_socket进行鉴权。这个插件会检查启动客户端的本地系统用户是否和指定的 MariaDB 用户名相匹配。如果输入Y,详见本文最后的错误排除。

    下一步,将会被要求移除匿名用户,限制 root 用户访问本地机器,移除测试数据库,并且重新加载权限表。这里都填Y

    Remove anonymous users? [Y/n] Y
    Disallow root login remotely? [Y/n] Y
    Remove test database and access to it? [Y/n] Y
    Reload privilege tables now? [Y/n] Y

    以 root 身份登录

    想要在终端命令行和 MariaDB 服务器进行交互,使用mysql客户端工具或者MariaDB。这个auth_socket插件将会通过 Unix socket 文件验证用户来连接localhost。这就意味着不能通过提供密码来验证 root。想要以 root 用户名登录 MariaDB 服务器,输入:

    sudo mysql

    出现 MariaDB shell,就像下面一样:

    Welcome to the MariaDB monitor.  Commands end with ; or \g.
    Your MariaDB connection id is 61
    Server version: 10.3.22-MariaDB-1ubuntu1 Ubuntu 20.04
    
    Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
    
    Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
    
    MariaDB [(none)]> Bye

    如果想使用第三方程序,例如 phpMyAdmin ,以 root 身份登录MariaDB 服务器,有两个选择。

    第一个是将鉴权方法从auth_socket修改为mysql_native_password(详见本文最后的错误排除):

    ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'very_strong_password';
    FLUSH PRIVILEGES;

    (推荐)第二个是创建一个管理员用户,可以访问所有的数据库:

    GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' IDENTIFIED BY 'very_strong_password';

    可以将这个管理员用户命名为任何想要的名字,但是请确保使用强密码。

    安装 PyMySQL

    安装 PyMySQL 以在 Python 中使用 MariaDB:

    # 安装PyMySQL以在Python中使用MariaDB
    sudo python3 -m pip install PyMySQL
    pip install PyMySQL

    数据库和数据表(创建、修改、删除)

    mysql -u用户名 -p密码 # 数据库登录,例如 mysql -uroot -proot
    show databases; # 展开数据库列表,注意:所有的分号;不可省略
    use eth_5min; # 使用该数据库
    show tables; # 查看该数据库下的所有数据表
    create database btc_5min # 创建数据库
    create table BTC-USDT-SWAP (ts text,o text,h text,l text,c text,vol text,volCcy text); # 创建数据表
    select * from your_table limit 10000; # 查找数据

    错误排除

    下方的内容参考来源为:https://blog.csdn.net/jlu16/article/details/82809937

    插入数据出错时:当通过root用户登录到 MySQL 数据库时,得到 ERROR 1698 (28000): Access deniedfor user ‘root’@‘localhost’ 的错误提示。

    原因是因为在最近的Ubuntu安装(当然也可能是其他安装)中,MySQL默认使用了 UNIX auth_socket plugin 插件。简单来说这意味着当 db_users 使用数据库时,将会通过系统用户认证表进行认证。可以通过下面的命令看看 root 用户是否设置成了这样:

    $ sudo mysql -u root # I had to use "sudo" since is new installation
    
    mysql> USE mysql;
    mysql> SELECT User, Host, plugin FROM mysql.user;
    
    +------------------+-----------------------+
    | User             | plugin                |
    +------------------+-----------------------+
    | root             | auth_socket           |
    | mysql.sys        | mysql_native_password |
    | debian-sys-maint | mysql_native_password |
    +------------------+-----------------------+

    其中 root 用户在使用auth_socket插件。而我们设置 root 用户使用mysql_native_password插件。

    $ sudo mysql -u root # I had to use "sudo" since is new installation
    
    mysql> USE mysql;
    mysql> UPDATE user SET plugin='mysql_native_password' WHERE User='root';
    mysql> FLUSH PRIVILEGES;
    mysql> exit;
    
    $ service mysql restart
  • 在 Abaqus 中用 Python 分析四点弯曲试验

    四点弯曲试验是测量材料弯曲性能的一种试验方法。将条状试样平放于弯曲试验夹具中,形成简支梁形式,支撑试样的两个下支撑点间的距离视试样长度可调,试样上方有两个对称的加载点。

    如何在 Abaqus 中执行 Python 文件?

    如下文章中的代码合成一份 py 文件,并将其命名为 Modele2D_CP.py ,放入C:/temp/TP_provir/ 文件夹下。在 Abaqus 最下方的命令行中输入下列的命令即可:

    execfile('C:/temp/TP_provir/Modele2D_CP.py', __main__.__dict__)

    如下文章中的代码为一个在 Abaqus 中的 Python 文件的基本组成结构。当使用 GUI 操作 Abaqus 之后,会在Abaqus的安装过程中生成的C:/temp文件夹(File –> Set Work Directory )中的.rec文件自动生成相关的 Python 代码,下方的 Python 代码均能在其中找到(ctrl+F)。

    在 Abaqus 中使用 Python 执行相关的模拟能够更快捷地修改重复的参数修改操作,甚至是循环执行某些步骤。

    导入库文件

    from part import *
    from material import *
    from section import *
    from assembly import *
    from step import *
    from interaction import *
    from load import *
    from mesh import *
    from optimization import *
    from job import *
    from sketch import *
    from visualization import *
    from connectorBehavior import *
    Mdb()   # reinitialise la base de donnees / le modele

    参数设置

    比如材料和试件的外观参数:

    ndiv=3
    L1, B1, h1 = 100., 40., 10.  #材料的长宽高
    ecinf, ecsup = 80., 60. #下支撑和上支撑的距离
    esize1 = h1/ndiv #网格的大小
    dens1, young1, poisson1 = 7.85e-09, 210000.0, 0.3 #定义材料:密度、杨氏模量、泊松比
    nmodes, uimp1 = 10, -10.
    jobname1= 'tp_provir_coque' + str(ndiv) #给生成的job取个名字

    绘制试件的外观

    下列为 GUI 的操作步骤(下同):

    • Create Part

    • 2D Planer –> Deformable –> Shell
    • Create lines 划线或者使用其他工具画草图 –> add constraint 添加约束 –> add dimension 画尺寸

    下列的代码将会画出一个二维的平面模型:

    s = mdb.models['Model-1'].ConstrainedSketch(name='__profile__', sheetSize=L1)
    
    s.setPrimaryObject(option=STANDALONE)
    
    s.Line(point1=(0.0, 0.0), point2=(ecinf/2., 0.0))
    s.Line(point1=(ecinf/2., 0.0), point2=(L1/2., 0.0))
    s.Line(point1=(L1/2., 0.0), point2=(L1/2., h1))
    s.Line(point1=(L1/2., h1), point2=(ecsup/2., h1))
    s.Line(point1=(ecsup/2., h1), point2=(0.0, h1))
    s.Line(point1=(0.0, h1), point2=(0.0, 0.0))
    
    p = mdb.models['Model-1'].Part(name='Sample', dimensionality=TWO_D_PLANAR, type=DEFORMABLE_BODY)
    p = mdb.models['Model-1'].parts['Sample'];
    p.BaseShell(sketch=s); 
    s.unsetPrimaryObject()
    session.viewports['Viewport: 1'].setValues(displayedObject=p)
    del mdb.models['Model-1'].sketches['__profile__']

    创建材料

    • 在 Module 选择 Property 模块
    • Create Material 创建材料
    • General > Density : masse volumique 设置密度
    • Mechanical > Elasticity > Elastic 设置杨氏模量、泊松比
    mdb.models['Model-1'].Material(name='steel')
    mdb.models['Model-1'].materials['steel'].Density(table=((dens1, ), ))
    mdb.models['Model-1'].materials['steel'].Elastic(table=((young1, poisson1), ))

    Create section

    • 在 Module 选择 Property 模块
    • Create Section 创建截面
    • Solid –> Homogeneous –> 设置材料和厚度(比如厚度为 B1 = 40mm)
    mdb.models['Model-1'].HomogeneousSolidSection(material='steel', name='sec2D', thickness=B1)

    Assign section

    • Assign Section
    p = mdb.models['Model-1'].parts['Sample'];
    p.SectionAssignment(offset=0.0, sectionName='sec2D', thicknessAssignment=FROM_SECTION, 
      offsetField='', offsetType=MIDDLE_SURFACE, region=(p.faces.getByBoundingBox(),) )

    Create assembly

    • Module Assembly –> Create assembly
    p = mdb.models['Model-1'].parts['Sample'];
    mdb.models['Model-1'].rootAssembly.DatumCsysByDefault(CARTESIAN)
    mdb.models['Model-1'].rootAssembly.Instance(dependent=ON, name='Sample1',part=p)

    Create step

    • Module Step –> Create step
    • General –> Static, general
    • Linear perturbation –> Frequency –> Lanczos –> Number of eigenvalues requested: Value 10
    mdb.models['Model-1'].FrequencyStep(name='modal1', numEigen=nmodes, previous='Initial')
    mdb.models['Model-1'].StaticStep(name='loading1', previous='modal1')

    Create loads

    创建负载:

    • 打开 Module Load
    • create load 创建负载
    • 由于测试模型为对称结构,所以模型只要做一半就可以了,在这里需要加上Y轴方向上的边界条件。 Create boundary condition 创建边界条件 –> initial –> Symmetry / Antisymmetry / Encastre
    • create boundary condition 创建边界条件 –> initial –> Displacement / Rotation (下方支撑)–> 选择上一节中 initial 的那个 step –> 在 U2 方向(y轴)增加支撑。
    • create boundary condition 创建边界条件 –> initial –> Displacement / Rotation –> 选择上一节中 static 的那个 step –> 在 U2 方向(y轴)有向下的挤压。
    a=mdb.models['Model-1'].rootAssembly.instances['Sample1']
    
    mdb.models['Model-1'].DisplacementBC(amplitude=UNSET, createStepName='Initial', 
        distributionType=UNIFORM, fieldName='', localCsys=None, name='cinf', 
        region=(a.vertices.getByBoundingBox(xMin=0.99*ecinf/2., xMax=1.01*ecinf/2.),), u1=UNSET, u2=SET, ur3=UNSET)
    
    mdb.models['Model-1'].XsymmBC(createStepName='Initial', localCsys=None, name='symX',
        region=(a.edges.getByBoundingBox(xMax=0.01*ecinf/2.),))
    
    mdb.models['Model-1'].DisplacementBC(amplitude=UNSET, createStepName='loading1',
        distributionType=UNIFORM, fieldName='', fixed=OFF, localCsys=None, name='uimp1',
        region=(a.vertices.getByBoundingBox(xMin=0.99*ecsup/2., xMax=1.01*ecsup/2.),),
        u1=UNSET, u2=uimp1, ur3=UNSET)

    Create mesh

    创建网格:

    • 打开 Module mesh
    • Seed Part –> 输入网格大小 –> 应用
    • Assign Element Type –> Standard –> Plane Stress
    • Mesh Part
    p=mdb.models['Model-1'].parts['Sample']
    
    p.seedPart(deviationFactor=0.1, minSizeFactor=0.1, size=esize1)
    
    p.setElementType(regions=(p.faces.getByBoundingBox(),), elemTypes=(
     ElemType(elemCode=CPS4, elemLibrary=STANDARD), 
     ElemType(elemCode=CPS3,elemLibrary=STANDARD, secondOrderAccuracy=OFF, distortionControl=DEFAULT)) )
    
    p.generateMesh()

    Create job

    创建工作:

    • 打开 Module job
    • Create job –> 在左边栏下方的 job上右击 –> Submit 提交
    mdb.models['Model-1'].rootAssembly.regenerate()
    
    mdb.Job(atTime=None, contactPrint=OFF, description='', echoPrint=OFF, 
        explicitPrecision=SINGLE, getMemoryFromAnalysis=True, historyPrint=OFF, 
        memory=90, memoryUnits=PERCENTAGE, model='Model-1', modelPrint=OFF, name=
        'Job-1', nodalOutputPrecision=FULL, queue=None, resultsFormat=ODB, scratch=
        '', type=ANALYSIS, userSubroutine='', waitHours=0, waitMinutes=0)
    
    mdb.jobs['Job-1'].submit(consistencyChecking=OFF)

    Visualisation

    查看结果:

    • 打开 Module Visualisation
    • Plot deformed shape
    • Plot Contours on deformed shape

    其中:

    • U – displacement 位移。U1、U2、U3 和 UR1、UR2、UR3 分别代表 xyz 移动和转动的自由度。(123 相当于 xyz)
    • S – stress 应力。S33 代表的是壳单元法线方向应力,S11 S22 代表壳单元面内的应力。
    • RF – 支反力

    Abaqus 里的单位

    Abaqus 中没有固定的单位制,所以需要为各个量选用相应匹配的单位,最后计算出的结果的单位与所采用的单位制相对应。Abaqus 中常用的单位制如下表:

  • 使用 Python 和 TA-Lib 进行自动化交易

    获取 API KEY 和可用的 SDK 包

    本文的案例使用了 Okex V5 API,参考前请阅读文章开头的警告。Okex 官网的 API 文档(目前只有 V5):https://www.okex.com/docs-v5/zh/

    由于官网没有为 V5 API 提供 SDK 包,所以本文用到了以下的第三方 SDK 包: https://github.com/jane-cloud/Open-API-SDK-V5

    import okex.account_api as Account
    import okex.Public_api as Public
    import okex.Trade_api as Trade

    获取账户信息

    一些初始化的信息,注意一定要保存好 APIKey 和 SecretKey 和 passphrase ,不要在公开场合(公开的文章、代码仓库等)出现。

    # 账户信息
    api_key = snickers.my_trading_info['api_key']
    secret_key = snickers.my_trading_info['secret_key']
    passphrase = snickers.my_trading_info['passphrase']
    flag = '0'  # 实盘 real trading ,v5 专有
    # 一些参数的初始化
    result_total = {} # 放置各种数据的读取结果,字典
    mail_content = {}  # 放置邮件内容,字典

    一些常用的信息,如“持仓数量”、“未实现收益”、“持仓方向”、“可用保证金”等。

    accountAPI = Account.AccountAPI(api_key, secret_key, passphrase, False, flag)  # 获取账户信息
    result_total['position-info'] = accountAPI.get_positions('SWAP', instrument) # 获取账户中的仓位信息
    holding_info = result_total['position-info']['data'][0]['pos'] # 持仓数量
    upl_info = result_total['position-info']['data'][0]['upl'] # 未实现收益
    side_info = result_total['position-info']['data'][0]['posSide'] # 持仓方向
    uplRatio_info = result_total['position-info']['data'][0]['uplRatio'] # 可用保证金

    获取基本行情数据

    这里获取单个标的的行情数据,下方代码中获取的是1分钟的 k 线信息,并根据实际需要提取相应的数据。

        marketAPI = Market.MarketAPI(api_key, secret_key, passphrase, False, flag) # 获取市场数据
        result_total['kline-'+instrument] = marketAPI.get_candlesticks(instrument, bar='1m') # 获取指定时间的k线
        
        kline_close_result = [] # 收盘价
        kline_high_result = [] # 最高价
        kline_low_result = [] # 最低价
        kline_hlc3 = [] # 上述三者的平均值
        
        list = result_total['kline-'+instrument]['data'] # 指定时间的k线100根,顺序“最新-最旧”
        i = 1
        while i < 15: # 限制,每次最多获取100根k线,所以要获取10次,才能获取1000根以上
            ## 最旧的那个时间戳
            dataunix = list[-1][0]
            print(dataunix)
            get_data_2nd_times = marketAPI.get_candlesticks(instrument,after=dataunix,bar='1m')
            #print(get_data_2nd_times)
            list = list + get_data_2nd_times['data']
            i = i+1
        #print(list)
        for i in range(len(list)): 
            kline_close_result.append(float(list[i][4]))
            kline_high_result.append(float(list[i][2]))
            kline_low_result.append(float(list[i][3]))
            kline_hlc3.append((float(list[i][2])+float(list[i][3])+float(list[i][4]))/3)
        
        calcul_atr(kline_close_result,kline_high_result,kline_low_result,instrument)

    使用 TA-Lib 计算指标

    在 Ubuntu 中安装 TA-Lib 请参考这篇文章: UBUNTU 服务器安装 TALIB 并定时执行 PYTHON 量化小脚本

    在 Windows 中安装 TA-Lib ,请访问 https://github.com/cgohlke/talib-build/releases/tag/v0.4.32 ,并根据自己的 Python 版本选择下载。

    例如,在上述网站下载了 TA_Lib‑0.4.21‑cp38‑cp38‑win_amd64.whl 这个文件,将此文件放入到 C:\Windows\System32 路径下。

    进入 CMD 命令行窗口(或 Anaconda Prompt 亦可),进入 C:\Windows\System32 ,输入安装命令 pip install TA_Lib‑0.4.21‑cp38‑cp38‑win_amd64.whl

    在脚本中导入该库:

    import talib

    MACD

    df = {}
    # diff, dea, macd
    df['DIFF'],df['DEA'],df['MACD'] = talib.MACD(np.array(kline_close_result),
    fastperiod=12, slowperiod=26, signalperiod=9)
        
    diff = df['DIFF'][~np.isnan(df['DIFF'])] # 去除nan
    dea = df['DEA'][~np.isnan(df['DEA'])] # 去除nan
    macd = df['MACD'][~np.isnan(df['MACD'])] # 去除nan

    EMA

    df = {}
    ## 计算EMA
    df['MA_long'] = talib.EMA(np.array(kline_close_result),timeperiod=100)
    df['MA_short'] = talib.EMA(np.array(kline_close_result),timeperiod=50)
    ema_value_diff.append(df['MA_short'][-2] - df['MA_long'][-2])
    ema_value_diff.append(df['MA_short'][-1] - df['MA_long'][-1])
    print(ema_value_diff)

    开仓和平仓

    开仓和平仓用一个函数即可,需要填写以下信息即可执行开仓和平仓:

    • 开多:买入开多(buyornot 填写 buy;direction_type 填写 long )
    • 开空:卖出开空(buyornot 填写 sell;direction_type 填写 short )
    • 平多:卖出平多(buyornot 填写 sell;direction_type 填写 long )
    • 平空:买入平空(buyornot 填写 buy;direction_type 填写 short )
    #%% 开多 开空
    def trading_swap(buyornot,direction_type,instrument):
        tradeAPI = Trade.TradeAPI(api_key, secret_key, passphrase, False, flag)
        accountAPI = Account.AccountAPI(api_key, secret_key, passphrase, False, flag)
        result = accountAPI.set_leverage(instId=instrument, lever='20', mgnMode='isolated',posSide='long') # 设定杠杆
        result = accountAPI.set_leverage(instId=instrument, lever='20', mgnMode='isolated',posSide='short')
        result2 = accountAPI.get_maximum_trade_size(instrument, 'isolated') # 逐仓最大可开仓张数
        max_sz = str(int(int(result2['data'][0]['maxBuy'])/10)) # 只开最大数量的一半,但张数要为整数
        # 开多:买入开多(side 填写 buy; posSide 填写 long )
        # 开空:卖出开空(side 填写 sell; posSide 填写 short )
        
        try:  # 尝试做以下事情
            result = tradeAPI.place_order(instId=instrument, tdMode='isolated', side=buyornot, posSide=direction_type,ordType='market', sz=max_sz)
            print(result)
        except:  # 如果因为各种原因报错
            print('可能没有相应的单子!或保证金不够!')
            mail_content["order_info"] = instrument+"!!!开仓失败!开仓数量大于可开数量,保证金不够!"
            mail_trade.send_mail_img(mail_content) # 发邮件
        else:  # 如果没有报错
            mail_content["order_info"] = instrument+"开仓成功"
            # 邮件内容正文
            mail_content['holding_info'] = '开仓成功'
            # 邮件主题
            mail_content['holding_info_subject'] = '开仓成功'
            mail_trade.send_mail_img(mail_content) # 发邮件

    市价全平

    ## 市价全平
    def trading_swap_close_all(direction_type,instrument):   
        try:  # 尝试做以下事情
            tradeAPI = Trade.TradeAPI(api_key, secret_key, passphrase, False, flag)
            result = tradeAPI.close_positions(instrument, 'isolated', direction_type , '')
            print(result)
        except:  # 如果因为各种原因报错
            print('可能没有相应的单子!或保证金不够!')
        else:  # 如果没有报错
            mail_content["order_info"] = "平仓成功"
            mail_trade.send_mail_img(mail_content) # 发邮件

    达到条件开仓平仓

        if mail_content['holding_info'] != "当前无持仓" and side_info == 'long' and  pos[-1] == -1:
            print("平多和开空")
            trading_swap_close_all("long",instrument) # 市价全平  
            time.sleep(15)
            trading_swap("sell","short",instrument)
        elif mail_content['holding_info'] != "当前无持仓" and side_info == 'short' and  pos[-1] == 1:
            trading_swap_close_all("short",instrument) # 市价全平  
            time.sleep(15)
            print("平空和开多")
            trading_swap("buy","long",instrument)

    在 Ubuntu 服务器中自动执行

    请参考下下列文章:

  • NAC-Python-GUI 和 NAC-React-App 的使用指南

    注意事项

    本页面为 使用 YOLOv4 检测目标并远程查看数据 的基本使用指南(并非开发指南,开发指南详见 https://xd.sh.cn/pje-nac/),可以根据本教程的步骤检测有鱼🐟类目标的视频或者摄像头所拍摄的实时录像。

    本项目至此暂停更新🤣,相关文章的列表如下所示:

    快速开始

    下载 nac-python-gui 的源码,目前有以下两个地址可以下载:

    下载权重文件,权重文件有两种物体的训练结果,分别为鱼🐟和仓鼠🐹(下方以鱼类为目标):

    下载和安装 Anaconda :

    Windows 的左下角直接搜索并打开 Anaconda Prompt,安装 openCV-python 和 PySimpleGUI,就是下方的命令复制粘贴回车即可。若系统内没有安装 imutils,也需要安装:

    pip install opencv-python
    pip install pysimplegui
    pip install imutils

    Windows 的左下角直接搜索并打开 Anaconda Navigator,选择并打开 Spyder,打开刚才克隆的 nac-python-gui 文件夹下的 yolo_video_with_webcam.py。正常情况下,此时其已经可以正常运行。

    如果有自备的视频(有鱼🐟类目标)可以将视频放在 nac-python-gui/videos 路径下,并且更改相应的文件名称。

    注意:视频分辨率不宜过大,过大可能导致 GUI 界面显示不全。

    若不想使用自备的视频可以直接使用摄像头进行检测,勾选上方 GUI 界面中的 Utiliser la webcam 可以进入摄像头模式。下图中展示了当使用摄像头实时识别的画面:

    注意:当关闭 GUI 界面时,摄像头可能不会被关闭。此时关闭整个 Spyder 软件即可完全退出并关闭摄像头。

    配合 nac-react-app 使用

    如果要配合 nac-react-app 使用,需要在 Anaconda Prompt 安装:

    pip install pyrebase4

    打开刚才克隆的 nac-python-gui 文件夹下的 yolo_video_with_webcam_focus.py,界面如下所示,即可登录到 nac-react-app 的账号。

    注意:该账号需要从 xd.sh.cn/nac/espace 注册,其会以加密的形式保存在 Google Firebase 上。管理员能够得知您的邮箱地址,但是无法获知您的密码。该账号和谷歌账号无关。

    注意:xd.sh.cn/nac 的路由仍存在一定问题,慢慢改,但可以直接访问具体链接。

    若成功登录该账号,则 nac-python-gui 所检测的画面(截图)会每隔一定的时间上传至 Firebase,并可以通过 xd.sh.cn/nac/espace 远程查看。

    进一步 DIY

    若需要进一步 DIY,可以直接修改 nac-python-gui 的相关代码,亦可通过 https://xd.sh.cn/pje-nac/ 参见详细的开发指南。若需要修改 nac-react-app 的代码,可以从下列的地址获得该网站的源码,该网站使用 React 开发:

    下载 nodejs 安装包:

    使用 Visual Studio Code 打开刚才克隆的 nac-react-app 文件夹。输入命令 npm install 下载必要的依赖包,下载完毕之后输入 npm start 即可预览。其将会自动打开浏览器并跳转至 http://localhost:3000/ 页面。

  • Ubuntu 服务器安装 Talib 并定时执行 Python 量化小脚本

    下文中均使用腾讯云轻量应用服务器进行测试,WIndows 本地定时任务设置可以查看这篇

    安装 Ananconda

    首先去 Anaconda 的官网下载 Anaconda 3,此处仅需获得其下载链接即可。先去官网找到 Linux 版本的下载链接,目前的最新版本为Anaconda3-2020.11-Linux-x86_64.sh,执行下面两行命令(将下载地址替换为最新版本)。下载完成之后,使用 Xshell 登录服务器,然后打开 Xftp,即可在 /home/你的用户名 的路径下找到该文件,大小约为500兆。

    wget https://repo.anaconda.com/archive/Anaconda3-2020.11-Linux-x86_64.sh
    bash Anaconda3-2020.11-Linux-x86_64.sh

    安装过程中基本上都点 yes 或者回车,最后显示 Thank you for installing Anaconda3! 即为安装成功。安装 Anaconda 可以轻松地应付很多 Python 带来的奇奇怪怪的问题。接下来将 Anaconda 添加到系统环境(虽然我不太确定这步是否有必要,可以先在控制台内输入 conda 看有没有反应,若无反应则需要执行下列命令)。

    echo 'export PATH="~/anaconda3/bin:$PATH"' >> ~/.bashrc
    source ~/.bashrc # 更新 bashrc 以立即生效

    安装 Ta-lib

    先去 Github https://github.com/mrjbq7/ta-lib 下载 talib 的 Python 版本,若无法访问,可以通过 coding.net 拉取。将下载好的安装包 ta-lib-0.4.0-src.tar.gz 解压之后编译并安装(按照上方 Github 链接的安装方法)。最后记得需要 pip install TA-Lib (也有可能只需要执行 pip install TA-Lib 即可,请自行尝试)。

    tar -xzf ta-lib-0.4.0-src.tar.gz
    cd ta-lib/
    ./configure --prefix=/usr
    make
    sudo make install
    cd ..
    pip install TA-Lib

    若 ./configure –prefix=/usr 执行出错,则执行:

    sudo apt-get install gcc libc6-dev

    自动运行 Python 脚本

    • 定时任务由三部分组成:时间参数、Python 位置和要执行的脚本路径。Python 的路径可以使用 which python 查找,默认路径为 /home/用户名/anaconda3/bin/python
    which python
    • 要运行 Python 脚本,需要先将这些文件打包上传至服务器,这里使用 xftp 可以非常方便地完成这些任务,上传的路径和上方 Anaconda3 和 talib 的路径相同。然后执行 python python脚本路径 即可执行,示例如下:
    python /home/ubuntu/auto-alert/get_data.py
    • 使用 crontab -e 打开 crontab ,写入定时任务。
    • 例如,要每分钟执行一次名为 get_data.py 的 Python 脚本,只要将下列语句写入 crontab 文件中。具体 Crontab 的定时写法可以自行搜索。
    */1 * * * * /home/ubuntu/anaconda3/bin/python /home/ubuntu/auto-alert/get_data.py
    • 保存并退出:Ctrl+O 保存 Ctrl+X 退出,若显示 installing new crontab 则表示设置成功。
    • 重启 cron 服务:sudo service cron restart
    • 查看定时任务状态:service cron status,需要等待上述定时执行的任务执行过之后,才能在这里看到。
    • 若 Python 脚本文件有所改动,那么仅需通过 Xftp 将源文件覆盖即可,若文件名不变,则无需改变 Crontab 文件。
  • 使用 YOLOv4 检测目标并远程查看数据

    这个项目的主要目标是检测新型宠物(法文:Nouveaux animaux de compagnie。这里其实可以替换成各种物体)的状态并远程查看相关数据。虽然只有5个学分且专业不相关,但也挺好,总比体验极差的其他课(有限元)好玩一点。

    可以通过下方链接访问线上版本的 NAC Web App,按照页面中的说明进行操作(只有法语版本):

    项目目标

    项目使用目标检测算法(YOLOv4)训练经过标注之后的 NAC 数据集,使用 Google Colab 进行训练。使用 Python 和 OpenCV 结合 YOLOv4 检测 NAC 的活动状态,使用了 Google Firebase 进行数据的储存,用户可以通过 Web App(使用 React.js 制作)获取 NAC 活动状态的数据和画面。

    使用指南

    开发指南

    视频教程

    文章教程

    相关链接

    可以通过下方链接访问线上版本的 NAC Web App,按照页面中的说明进行操作(只有法语版本):

    开发者可以通过下列网址寻找到本项目的源代码:

  • Firebase 的数据传输和用户验证:以 Python 和 JavaScript 为例

    开始

    需要准备的一些工具:

    Python 和 Firebase 的数据传输

    要使 Python 和 Firebase 之间传输数据,需要安装额外的包,但是这些都是非官方维护的包,所以基本上都已经无法使用,但是这里提供一个经过改进的SDK包仍然可以使用:https://github.com/nhorvath/Pyrebase4。注意:官网提供的SDK包是给 Admin 管理员批量管理数据和账号使用的,也就是说其不适用于客户端。

    pip install pyrebase4

    登录 Google Firebase 创建一个新应用,创建后在下列路径中找到 Firebase 的配置文件:设置 –> 项目设置 –> 您的应用 –> Firebase SDK snippet –> 配置,复制类似下方的代码:

    // For Firebase JS SDK v7.20.0 and later, measurementId is optional
    const firebaseConfig = {
      apiKey: "QWERT",
      authDomain: "QWERT.firebaseapp.com",
      databaseURL: "https://QWERT.firebaseio.com",
      projectId: "QWERT",
      storageBucket: "QWERT.appspot.com",
      messagingSenderId: "00000000",
      appId: "QWERT",
      measurementId: "QWERT"
    };

    下方代码仅列出了重要的部分,其余部分可以转至 Github 查看源码。首先,需要引入相关的包:

    import pyrebase

    用户验证 Auth

    初始化 Firebase,调用用户验证模块:

    firebase = pyrebase.initialize_app(firebaseConfig) # 初始化数据
    pickle.dump(firebase,open('firebase_info.txt','wb')) # 将初始化后的数据保存,以便其他函数引用
    auth = firebase.auth()

    这里仅使用用户的邮箱和设置的密码进行验证操作,如果验证成功则会显示成功的弹窗。将验证过后的 Token 保存在本地,以便其他函数调用。由于 Firebase 不支持 @ 等特殊符号,如果需要保存这类特殊符号,则需要进行更改(按个人喜好)。

    user = auth.sign_in_with_email_and_password(UserEmail, UserPassword)
    sg.popup('登录成功!欢迎', UserEmail)
    pickle.dump(user,open('user_info.txt','wb'))
    userUniqueId = UserEmail.replace("@","__").replace(".","_")
    pickle.dump(userUniqueId,open('user_id.txt','wb'))  

    上传数据 Realtime database

    导入用户登录数据和凭证(注意:目前该凭证有效期为一个小时,但是可以进行续期):

    # Lire des données 读取数据
    firebase = pickle.load(open('firebase_info.txt','rb'))
    user = pickle.load(open('user_info.txt','rb'))

    获取对数据库(Realtime database)、云储存(Storage)服务的引用:

    # Obtenir une référence au service de base de données
    db = firebase.database()
    storage = firebase.storage()

    将要上传的数据类型为 json 形式,json文件的设计形式可以查看这篇文章

    dataUser = {
        "setup":{"time_update":300},
        "zone_name":["manger","libre","manger","libre",
                     "manger","libre","manger","libre"],
    }

    将 dataUser 上传至 user/userUniqueId 这个节点之下,userUniqueId 为用户特定的 Id 编号(自行设定),而后面的 user['idToken'] 为用户的身份验证(如果没有,将无法上传成功)。上传完成之后,如下图所示:

    db.child("users").child(userUniqueId).set(dataUser, user['idToken'])

    上传图片等文件 Storage

    上传图片等文件至 Firebase Storage ,操作类似于前者。下方案例中,本地文件储存在 image_raw/ 文件夹之下,且是以时间戳命名。使用 put 即可将文件上传至 userUniqueId/路径之下,userUniqueId 为用户特定的 Id 编号(自行设定),需要注意的是 child 处无需写文件后缀,不然无法上传成功。

    storage.child(userUniqueId + "/" + str(timeStamp)).put('image_raw/'+ str(timeStamp) + '.jpg', user['idToken'])

    参考资料

    JavaScript 和 Firebase 的数据传输

    在 JavaScript 项目中添加 Firebase

    添加 Firebase SDK 并初始化 Firebase,在应用的文件夹下安装 Firebase SDK:

    npm install --save firebase

    在应用中初始化 Firebase。在本 App 中,Firebase 的初始化文件在 srcComponentsfirebase.js 文件中,然后 App.js 文件调用一次即可。

    `srcComponentsfirebase.js` 
    import firebase from "firebase/app";
    require('firebase/analytics')
    const EnvironmentFirebase = () => {
      var firebaseConfig = {
        apiKey: "",
        authDomain: "fir-rtc-aff50.firebaseapp.com",
        projectId: "fir-rtc-aff50",
        storageBucket: "fir-rtc-aff50.appspot.com",
        messagingSenderId: "",
        appId: "",
        measurementId: "",
      };
      // Initialize Firebase
      firebase.initializeApp(firebaseConfig);
      firebase.analytics();
    };
    export default EnvironmentFirebase;

    用户注册和身份验证 Auth

    引入 firebase 和 auth 包:

    import firebase from "firebase/app";
    require("firebase/auth");

    创建一个组件,在本例中,该组件为 AuthInput.js,下方有两个函数分别为用户注册和用户登录。下方为一个注册按钮组件的例子,这个按钮将调用 signUpWithEmailPassword 函数,并将用户输入的用户名和密码传至该函数:

    <Button
        variant="contained"
        className={classes.buttonArea}
        onClick={() => {
            var email = document.getElementById("standard-email").value;
            var password = document.getElementById("standard-password")
            .value;
            signUpWithEmailPassword(email, password);
        }}
        >
        CRÉER UN COMPTE
    </Button>

    上方调用了 signUpWithEmailPassword 函数,在该函数中使用了 createUserWithEmailAndPassword 用户创建用户。此外,signInWithEmailAndPassword的操作也类似,具体的调用过程如下所示或者可以查询官方文档。若登录发生错误(用户名、密码不正确或者是用户已注册等情况),Firebase 将会返回不同的错误信息,这里为了简单起见,统一显示为“遇到了一些错误!”。

    // 注册用户
      // 文档在此:https://firebase.google.com/docs/auth/web/start?authuser=0
      function signUpWithEmailPassword(email, password) {
        firebase
          .auth()
          .createUserWithEmailAndPassword(email, password)
          .then((user) => {
            // Signed in
            console.log(user);
            alert("大概注册成功了!");
            storeSuccessedData(true);
          })
          .catch((error) => {
            var errorCode = error.code;
            var errorMessage = error.message;
            console.log(errorCode);
            alert("遇到了一些错误!");
          });
      }
    
      // 登录
      function signInWithEmailPassword(email, password) {
        firebase
          .auth()
          .signInWithEmailAndPassword(email, password)
          .then((user) => {
            // Signed in
            console.log(user);
            alert("大概登录成功了!");
            storeSuccessedData(true);
          })
          .catch((error) => {
            var errorCode = error.code;
            var errorMessage = error.message;
            console.log(errorCode);
            alert("遇到了一些错误!");
          });
      }

    读取数据和文件

    引入 firebase 和 auth 、database 、 storage 包:

    import firebase from "firebase/app";
    require("firebase/auth");
    require("firebase/database");
    require("firebase/storage");

    读取用户的数据,然后进行进一步处理。

    function readUserData(userEmail) {
      var adaRef = firebase
        .database()
        .ref("/cibles/" + userNameGenerate(userEmail))
        .once("value")
        .then( // Do something);
    }

    读取用户 Storage 中指定路径下的文件,userNameGenerate(userEmail) 为用户的个性路径,.list({ maxResults: 5 }) 表示读取最多5个文件(注意此处文件将会以升序的方式列出,也就是说列出的文件为最初的那些文件,而不是最新的文件。所以,这里我使用了listAll,然后使用 reverse() 将数组反转)。其余操作可以查看官方文档进行进一步查看。

    function readUserImg(userEmail) {
      // Create a storage reference from our storage service
      var storageRef = firebase
        .storage()
        .ref("/" + userNameGenerate(userEmail) + "/")
        .list({ maxResults: 5 })
        .then();
      return storageRef;
    }

    参考资料

  • 更简单一点!PySimpleGUI 快速创建 YOLO 目标检测的界面

    本文中的所用到的代码和文件可以从下列地址获取:


    使用 GUI (图形用户界面)的优点是用户应该能够立即使用应用程序,并能快速找到和使用他们想要的功能。在日常生活、工作中使用到的应用程序(浏览器、office、微信等等)就是 GUI 的体现,它使我们的工作变得效率更高、门槛更低。

    之前在桌面端只试过使用 Matlab 制作 GUI,具体可以查看这篇文章:Matlab GUI app designer 多人消费付款记账催债系统 。使用 Matlab 的 APP designer 或者 Guide (已过时)可以非常方便的制作出界面,因为只要拖拽即可。但是使用Matlab制作界面对于应用的使用者来说非常不便:它打包后的体积非常大,而且需要安装额外的 Matlab 运行库才能够使用。

    Python 作为目前非常“流行”的编程语言,实际上确实非常好用。对于我来说,它能够把日常琐碎重复的工作自动化完成,比如说文件重命名、批量替换等。制作图形界面能够让使用者更加方便的使用这些功能,而不是面对漆黑一片的代码行。

    在网上看到这么一句话:PySimpleGUI 是适用于人类使用的 Python GUI。它将 tkinterpyQtWxPython 等 Python 知名的 GUI 库转换为可移植的、用户更友好的界面开发库。当然缺点也是显而易见的,PySimpleGUI 只能制作出极为基础的界面,可能无法实现复杂且好看的界面设计。

    创建的步骤

    界面部分

    firebase_login

    下文以最简单的 firebase_login 界面为例:

    • 建议安装 Anaconda 管理库文件、使用 Python。
    • 使用 Anaconda Prompt 安装 PySimpleGUI,如果安装过程中提示需要安装其他的安装包,安装即可 :
    pip install PySimpleGUI
    • 引入文件:
    import PySimpleGUI as sg
    • 创建一个 PySimpleGUI 界面。该界面中有两个需要提交的变量:一个是用户邮箱、还有一个是用户密码,点击 OK 按钮之后即可执行验证 try。该界面中有以下几个元素,一个标题和图片、两个输入框、两个按钮:
      • sg.Text 创建文字,可以设置字体、大小、颜色和位置等。
      • sg.Image 引入图像,若其需要和上述的文字排列在同一行,则需要写在同一数组中。
      • sg.OKsg.Cancel 这是两个固定了文字的按钮,当然也可以自定义创建按钮,当点击事件(event)为 Cancel 或者用户点击关闭时,将退出应用;反之,将执行登录的步骤:
    sg.ChangeLookAndFeel('Reddit')
    layout = 	[
        [sg.Text('Connectez-vous à votre compte', size=(30,1), font=('Helvetica',12),text_color='#1c86ee' ,justification='left'),\
         sg.Image(r'images\nac-logo.png',key = "_WEATHER_IMG_",size=(100, 50))],
        [sg.Text('Email'), sg.In(size=(40,1), key='_USER_EMAIL_')],
        [sg.Text('Password'), sg.In(size=(40,1), key='_USER_PASSWORD_')],
        [sg.OK(), sg.Cancel()]
    ]
    win = sg.Window('Test vidéo pour YOLOv4 - NAC',
                    default_element_size=(21,1),
                    text_justification='left',
                    auto_size_text=False).Layout(layout)
    event, values = win.Read()
    if event is None or event =='Cancel':
        sys.exit()
    
    UserEmail = values['_USER_EMAIL_']
    UserPassword = values['_USER_PASSWORD_']
    
    auth = firebase.auth()
    
    try:
        # Connexion 登录
        user = auth.sign_in_with_email_and_password(UserEmail, UserPassword)
        sg.popup('登录成功!欢迎', UserEmail)
        pickle.dump(user,open('user_info.txt','wb'))
        userUniqueId = UserEmail.replace("@","__").replace(".","_")
        pickle.dump(userUniqueId,open('user_id.txt','wb'))        
    except:
        sg.popup('发生了一些错误,可能是用户名/密码错误!')
    win.Close()
    

    yolo_video_with_webcam

    下文为主功能界面的部分主要代码,其功能性的代码详见另一篇文章。

    • sg.In 输入框
    • sg.Slider 滑块,可以定义数值范围
    • sg.Button 和上文中类似,但是新定义了一个按钮;点击之后会执行
    • sg.FileBrowse 浏览文件按钮
    • sg.FolderBrowse 浏览文件夹按钮
    i_vid = r'videos\003_x264.mp4'
    o_vid = r'output\car_chase_01_out.mp4'
    y_path = r'yolo-coco'
    sg.ChangeLookAndFeel('Reddit')
    layout = 	[
    		[sg.Text('Test vidéo pour YOLOv4 - NAC', size=(28,1), font=('Helvetica',18),text_color='#1c86ee' ,justification='left'),\ # 换行
                 sg.Image(r'images\nac-logo.png',size=(100, 50))],
    		[sg.Text('Chemin de la vidéo'), sg.In(i_vid,size=(40,1), key='input'), sg.FileBrowse()],
    		[sg.Text('Chemin de la Yolo'), sg.In(y_path,size=(40,1), key='yolo'), sg.FolderBrowse()],
    		[sg.Text('Confiance'), sg.Slider(range=(0,1),orientation='h', resolution=.1, default_value=.5, size=(15,15), key='confidence')],
    		[sg.Text('Seuil'), sg.Slider(range=(0,1), orientation='h', resolution=.1, default_value=.3, size=(15,15), key='threshold')],
    		[sg.Text(' '*8), sg.Checkbox('Utiliser la webcam', key='_WEBCAM_')],
    		[sg.Button('Connecxion avec votre compte'),sg.OK(), sg.Cancel()]
    			]
    
    win = sg.Window('Test vidéo pour YOLOv4 - NAC',
    				default_element_size=(21,1),
    				text_justification='left',
    				auto_size_text=False).Layout(layout)
    event, values = win.Read()
    if event is None or event =='Cancel':
    	exit()
    if event == 'Connecxion avec votre compte':
     	firebase_login.firebaseLogin()
    use_webcam = values['_WEBCAM_']
    args = values
    
    win.Close()
    

    后一个界面分为左边栏和右边栏:

    • position_elem = win.FindElement('_POSITION_') :寻找界面中 key 为_POSITION_的元素。
    • position_elem.Update(targetPosition):将上面寻找到的元素更新为一个新的值。
    • event, values = win.Read(timeout=0):获取GUI中所有数据和变量。
    • gui_confidence = values['confidence']:获取GUI中的变量。
    • firebase_login.firebaseUploadData(targetPositionObject,timeRightNow):调用firebase_login文件中的firebaseUploadData的函数。
    	if not win_started: # if win_started is not None
    		win_started = True
    		sg.SetOptions(text_justification='Center') 
            # 左边栏
    		left_col =  [#界面元素#]
            # 右边栏
    		right_col = [#界面元素#]
    		layout = [
    			[sg.Column(left_col, element_justification='c'), sg.VSeperator(),
    			sg.Column(right_col, element_justification='c')]
    		]
            
    		win = sg.Window('YOLO Output',
    						default_element_size=(14, 1),
    						text_justification='left',
    						auto_size_text=False).Layout(layout).Finalize()
    		position_elem = win.FindElement('_POSITION_')
            #其他需要更新的元素#
    	else:
    		position_elem.Update(targetPosition)
    		#其他需要更新的元素#
    
    	event, values = win.Read(timeout=0)
     
    	if event is None or event == 'Exit':
    		break
    	gui_confidence = values['confidence']
    
    	loopTimes=loopTimes+1
    	if loopTimes % loopInterval == 0:
    		timeRightNow = round(time.time());
    		cv2.imwrite('image_raw/'+ str(timeRightNow) + '.jpg',frame) #存储为图像
    		firebase_login.firebaseUploadData(targetPositionObject,timeRightNow)
    

    检测部分

    检测使用了 OpenCV-Python结合 YOLOv4-darknet 训练得到的 weight 文件,检测部分完整的代码文件可以查看下列地址:0sheldonhuang0/nac-python-gui (github.com)coco.names内包含了需要检测的目标名称,给每个标签配上不同的颜色,以便区分。

    labelsPath = os.path.sep.join([args["yolo"], "coco.names"])
    LABELS = open(labelsPath).read().strip().split("n") #打开标签
    
    # 每个对象配备了不一样的颜色,以便在图片中标记时便于区分。
    np.random.seed(1)
    COLORS = np.random.randint(0, 255, size=(len(LABELS), 3),
        dtype="uint8")

    加载 YOLO weight 和 Config 文件:

    • 使用 cv2.dnn.readNetFromDarknet加载网络。
    • YOLO 含有很多的图层,getUnconnectedOutLayersNames() 用于提取输出图层的名称。
    # 加载 YOLO weight 和 Config 文件
    weightsPath = os.path.sep.join([args["yolo"], "custom-yolov4-detector_final.weights"])
    configPath = os.path.sep.join([args["yolo"], "custom-yolov4-detector.cfg"])
    
    # 加载 YOLO 文件
    print("[INFO] loading YOLO from disk...")
    net = cv2.dnn.readNetFromDarknet(configPath, weightsPath)
    ln = net.getLayerNames()
    ln = [ln[i[0] - 1] for i in net.getUnconnectedOutLayers()]
    
    # 初始化视频,输出视频的帧率和画面尺寸
    vs = cv2.VideoCapture(args["input"])
    writer = None
    (W, H) = (None, None)

    读取视频或者摄像头中下一帧的数据:

    # 读取视频或者摄像头中下一帧的数据
    if use_webcam:
        grabbed, frame = cap.read()
    else:
        grabbed, frame = vs.read()
        # 分辨率-宽度
        zone_width = int(vs.get(cv2.CAP_PROP_FRAME_WIDTH))/4
        # 分辨率-高度
        zone_height = int(vs.get(cv2.CAP_PROP_FRAME_HEIGHT))/2

    检测每一帧图片,frame 为视频截帧得到的图片:

        # 如果每帧图片尺寸为空,那抓取它
        if W is None or H is None:
            (H, W) = frame.shape[:2]
        # 从输入图像构造一个 blob,然后执行 YOLO 对象检测器的前向传递
        # 得出边界和概率
        blob = cv2.dnn.blobFromImage(frame, 1 / 255.0, (416, 416),
            swapRB=True, crop=False)
        net.setInput(blob)
        start = time.time()
        layerOutputs = net.forward(ln)
        end = time.time()

    初始化边界框、置信度、目标种类的数组,循环提取每个输出层,每一层中可能有多个框。提取当前目标的目标对象的种类和置信度,由于可以自行设置置信度的最低值,舍弃检测结果小于最低值的部分。最后得出方框的坐标和其他必要的信息,最后将显示在图片上。

       # 初始化边界框、置信度、目标种类的数组
        boxes = []
        confidences = []
        classIDs = []
    
        # 循环提取每个输出层
        for output in layerOutputs:
            # 循环提取每个框
            for detection in output:
                # 提取当前目标的类 ID 和置信度
                scores = detection[5:]
                classID = np.argmax(scores)
                confidence = scores[classID]
    
                # 通过确保检测概率大于最小概率来过滤较不精确的预测
                if confidence > gui_confidence:
                    # 将边界框坐标相对于图像的大小进行缩放,YOLO 返回的是边界框的中心(x, y)坐标,
                    # 后面是边界框的宽度和高度
                    box = detection[0:4] * np.array([W, H, W, H])
                    (centerX, centerY, width, height) = box.astype("int")
    
                    # 转换出边框左上角坐标
                    x = int(centerX - (width / 2))
                    y = int(centerY - (height / 2))
    
                    # 更新边界框坐标、置信度和种类 id 的列表
                    boxes.append([x, y, int(width), int(height)])
                    confidences.append(float(confidence))
                    classIDs.append(classID)
    
        # gui_confidence:置信度的阈值
        # gui_threshold:非最大抑制的阈值(调整容错率)
        idxs = cv2.dnn.NMSBoxes(boxes, confidences, gui_confidence, gui_threshold)
        targetPosition = []
        targetDetailNumber = []
        zone_info = []

    在下方代码后有一大段是为了便于将检测的数据转为可以上传至 Firebase 的 Json 数据,这里便不再赘述。至此,程序可以检测出所输入的视频或者摄像头录像中每一帧图片中所包含的目标物体和它们的位置,并且使用方框、名称和置信度标注出来。

    下一步将跳转至上一节中界面部分中的yolo_video_with_webcam界面,并且可以每隔一段时间将图像和目标数据上传至 Firebase 和本地。

    # 确定每个对象至少有一个框存在
    if len(idxs) > 0:
        # 循环画出保存的边框
        for i in idxs.flatten():
            # 提取坐标和宽度
            (x, y) = (boxes[i][0], boxes[i][1])
            (w, h) = (boxes[i][2], boxes[i][3])
    
            targetDetailNumber.append(classIDs[i])
            targetPosition.append([x+w/2,y+h/2]) # 每一帧的目标数量和位置
    
            # [[793.0, 517.0], [796.5, 423.0], [841.5, 367.0], [889.5, 499.5], 
            # [1001.5, 584.0], [254.5, 480.5], [204.0, 420.5], [693.5, 343.5], 
            # [73.0, 504.0], [123.5, 368.5], [752.0, 280.0], [1017.0, 508.5], 
            # [1232.0, 683.0], [14.5, 473.5], [398.0, 86.0], [1225.0, 374.5], 
            # [1097.0, 139.5], [1098.0, 600.5], [61.5, 172.5], [723.0, 140.0], 
            # [863.0, 174.0]]
            print(targetPosition)
            # [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0]
            print(targetDetailNumber)
    
            # 画出边框和标签
            color = [int(c) for c in COLORS[classIDs[i]]]
            cv2.rectangle(frame, (x, y), (x + w, y + h), color, 2)
            text = "{}: {:.4f}".format(LABELS[classIDs[i]],
                                       confidences[i])
            cv2.putText(frame, text, (x, y - 5),
            cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)

    使用说明

    可以直接从 Github 上面下载本程序的源码,推荐使用 Anaconda 和 Spyder 进行编辑,并且需要主要安装以下一些包。若仍然缺少包,则仅需下载对应的包即可。

    • OpenCV-Python
    • Pyrebase4
    • PySimpleGUI
    • Numpy
    • imutils

    在安装完库之后,运行即可看到目标检测的实况。界面如上文的三张图所示,但可能和最终的界面有所不同。用户在正常情况下将执行 exe 文件启动桌面端软件。

    • 首个页面中上方两个输入框可以进行测试:如果用户有目标视频文件和对应的 weight 文件,那么可以从此处导入(注意导入文件和文件夹的路径是相对路径)。用户可以选择置信度和阈值,用户在点击 OK 之后即可进行预测。
    • 若用户的设备有摄像头,则可以勾选下方的使用摄像头选项,点击 OK 之后即可识别和检测摄像头所捕捉到的画面。
    • 用户在识别页面将只能调整检测的置信度和阈值,其他调整选项将放在在设置页面。上传的间隔时间,在目前版本无法调整,但在考虑范围之内。

    若用户有本系统的账号和密码,则可以通过左下角的按钮进行登录。如果登录成功,桌面端将会隔一段时间将用户所检测到的目标数据和图像上传至 Firebase。若用户没有登录, 则无法上传成功且在目前版本可能出错。

  • 目标检测用 YOLO:制作自己的数据集(观赏鱼)并使用 Darknet YOLOv4 在线训练

    注意:本文只是介绍了整个训练过程的大概流程和使用工具,不涉及详细的操作。训练的具体教程和内容可以浏览本文最底部链接里的 Jupyter Notebook 查看。

    做了件什么事情?

    目标检测是计算机视觉应用的一个方向,随着深度学习技术的发展,基于深度学习的目标检测开始变为主流,在无人驾驶、人工智能、人脸识别等领域大量使用。目标定位能够标出物体所在位置,目标分类能够判断在图像中是否含有该物体,而目标检测则是将两者相结合。

    因为有个课题的方向需要运用到目标检测这方面的技术,所以尝试了一下目前主流的目标检测技术。下方是一段b站养鱼up主的一段视频( 视频来源:零基础养鱼第二步——鱼要养得活 @ 我家有个动物园SUN)中的截图,图中的观赏鱼🐟都用框框标示了出来,在这些方框的上方都标有 poisson (法文:鱼)字样,旁边有介于0到1之间的数值代表了置信度。

    YOLOv4 算是目前比较强悍的一种目标检测算法,其全称是 You Only Look Once: Unified, Real-Time Object Detection,它在速度和精度都很有优势,Real-Time Object Detection 意味着它可以实时进行目标检测(检测摄像头或者是视频文件等)。

    下图中显示了不同目标检测算法准确度和速度之间的比较。可以看到 YOLOv4 在 MS COCO 数据集上获得了较高的 AP 值(average precision 平均精度),即在同FPS值(帧率)下有较高的AP值。

    MS COCO 是一种数据集,包含了80种物体分类、33万张图片、150万个目标对象。相比而言,本文使用到的数据集仅只有1种物体(观赏鱼)、122张图片(大多数来自b站萌宠区直播视频截图)、大约1000个不到目标对象(观赏鱼)。本文所使用的数据集(图片和标注数据)和训练结果(.weight文件,约250Mb)可以在本文最底部链接中下载

    上文图片来源:https://github.com/AlexeyAB/Darknet

    本篇文章主要内容如下图所示:首先使用 Labelimg 制作自己的数据集,然后白嫖 Google Colab (以下简称 Colab) 或 百度 Ai Studio (以下简称 Ai Studio)的服务器和显卡。在使用 Darknet 框架下,制作自己的数据集并训练自己的 YOLOv4 模型,得到 .weights 权重文件。

    使用了哪些工具?

    首先是使用到的软件,软件工具包括了以下这些(注意下列列表中的软件,仅针对使用 Ai Studio 或者是 Colab。若使用自己的电脑进行训练,则需要更复杂的步骤来配置环境。):

    在自己的电脑上处理数据

    1. Anaconda(用 Python 来处理一些重复的无意义操作,比如文件重命名、视频截帧。)

    2. Labelimg (用来进行物体目标标注。可以从 github 上直接获取,但是获取之后需要自行安装 Python 环境。)

    详见后文:制作自己的数据集。

    在 Ai Studio 或者 Colab 上训练

    1. Darknet YOLOv4 (用来进行训练,服务器上配置。可以从 github 上直接获取,但是获取之后不能直接用,需要自行编译。)

    若使用自己本地环境进行配置,则需要安装 Nvidia Cuda 和 CuDNN 以加快训练速度,还要有一块大显存的、高性能显卡。而如果使用 Ai Studio 和 Colab 进行训练则无需担心这些环境配置问题,也无需担心显卡性能问题,两者均配置了拥有 16GB 显存的英伟达特斯拉显卡

    我分别尝试了使用自己本子上的 Windows 和 Ubuntu 以及在线的机器学习环境 Ai Studio 和 Colab 进行编译和训练数据,其中 Windows 和 Ai Studio 在编译 Dartnet 这一关就卡住了(一直编译错误,可能和 Cuda 和 CuDNN 没装对有关)。

    而在 Ubuntu 上始终无法使用 CuDNN 进行训练,但是可以用单个 GPU 进行训练。使用我的破笔记本(Nivdia MX150(2018年轻薄本主流配置)),随便试了几张图(注意:只有几张图),结果不是“显卡内存不足”就是训练用时需要10个小时以上。

    后来分别使用了 Ai Studio 和 Colab 的分别进行数据训练。两者实际上没有优劣之分,只不过在使用 Ai Studio 的时候,在编译 Darknet 的时候始终遇到错误;而在使用 Colab 训练时间过长之后,会发生丢失文件的情况。Colab 提供的服务器、包括磁盘都是临时的,而不是永久的。所以在服务器上的任何改动,在关闭或者将服务器闲置时,上面所有的资料将会清空。在使用完这些服务器训练之后,要及时地将训练过后的权重文件以及配置文件下载下来。

    先试试别人的数据集

    在前文提到过数据集 MS COCO,在 Darknet 的 Github 的仓库下面(https://github.com/AlexeyAB/Darknet),作者提供了使用 MS COCO 数据集训练出来的权重文件可以作为测试使用。本文所使用的数据集(观赏鱼的图片和标注数据)和训练结果(.weight文件,约250Mb)可以在本文最底部链接中下载

    此外,这里还推荐一个数据集的分享、制作网站 Roboflow。虽然这上面的数据集也不是特别多,但是它可以用于小批量的测试,且每种数据集和标注数据都能下载成不同的格式以对应不同的目标检测算法。下方的截图所示为“是否戴口罩”的数据集,数据集内包含了戴口罩和不戴口罩的人,包含了149张原始图片和标注数据。点击 YOLO Darknet TXT 按钮即可下载适用于 YOLO Darknet 的数据集格式,既可以下载为 zip 压缩包,也可以直接使用它提供的链接在训练之前直接调用。

    https://app.roboflow.com/datasets

    制作自己的数据集

    第一步是获得一定数量含有目标的图片。经实测,当检测目标数不是很多的情况下,100到200张图片即可获得不错的检测效果。在上方提到的观赏鱼数据集中,一共仅包含了122张图片。这些观赏鱼图片中有一部分是来自搜索引擎直接搜索,另外一部分来自b站直播的视频截帧:下方链接只是其中的两个,另外的截图原视频来源由于主播未上播、当时也没有记录所以记不清了。

    这里可以使用 Python 脚本,截取视频中的截图,代码如下所示:(推荐使用 Anaconda 玩 Python,这样会更加容易管理环境。下方的代码需要在 Anaconda Prompt 中安装 opencv-python:pip install opencv-python

    ## 来源:python编程:使用opencv按一定间隔截取视频帧
    ## https://blog.csdn.net/xinxing__8185/article/details/48440133
    import cv2
    
    video_name = '006';
    vc = cv2.VideoCapture(video_name + '.mp4') #读入视频文件
    c=1
    
    timeF = 200  #视频帧计数间隔频率
    
    while rval:   #循环读取视频帧
        rval, frame = vc.read()
        print(rval,frame)
        if(c%timeF == 0): #每隔timeF帧进行存储操作
            cv2.imwrite('image_raw/'+'img'+ video_name + '_' + str(c) + '.jpg',frame) #存储为图像
        c = c + 1
        cv2.waitKey(1)
        print('输出1张图片')
    vc.release()

    接下来一步需要用到目标标注软件:Labelimghttps://github.com/tzutalin/labelImg)。这是一个用 python 写成的目标数据标注软件,用户可以用它来标注自己图片中的目标。使用方法也很方便,我使用了最简单的 Windows + Anaconda 的方案:首先傻瓜安装 Anaconda (Python 3 及以上),然后打开 Anaconda Prompt 安装 pyqt5 和 lxml:

    conda install pyqt=5
    conda install -c anaconda lxml

    将 Labelimg 克隆至本地或者下载 zip 压缩包到本地,用 Anaconda Prompt 打开 Labelimg 文件夹(路径根据自己的实际情况更改):

    cd C:UserssheldDownloadslabelImg-master
    pyrcc5 -o libs/resources.py resources.qrc
    python labelImg.py

    接着就会打开 Labelimg 的 GUI 界面,因为是图形操作界面,没有太多可说的,操作十分便捷:按键盘上的 w 键即可快速画框框。

    要注意的是:

    1. 如果需要使用 YOLO 作为数据输出格式,那么需要在软件的左侧进行设置。
    2. 每画完一张图需要保存,当然也可以使用它的“自动保存”模式。
    3. 如果所标注的对象只有一种,那么可以使用“单目标标注”模式,更加快捷。
    4. 每张图片将单独保存为一份 txt 文件,内容为所画方框的坐标,如下图左下角。(0 对应了检测目标列表的序号,在这里代表了 poisson。若有多种物体,则该序号则会不同。)

    使用 Ai Studio 或 Colab 进行训练

    在使用 Colab 的时候,整个操作均在一个 Jupyter Notebook(学 Python 必备神器)中执行,所以找到了一个实例 Jupyter Notebook(darknet 仓库下方有个官方的 Notebook),这个 Jupyter Notebook(.ipynb 文件)亦可在本文最下方的链接中下载。(注意: Colab 会因为一些众所周知的原因无法在国内登录。)

    在使用 Ai Studio 训练时一直遇到:当 Makefile 文件中 GPU = 1 时, Darknet 编译遇到错误,所以暂时放弃了使用 Ai Studio。然而,若不使用 GPU 进行训练,则环境可以仅使用 CPU 训练,但速度会严重降低。

    Jupiter Notebook 文件在 Ai Studio 中亦可使用,但 Ai Studio 没有对飞浆以外的算法进行适配,GPU环境可能不兼容。不过只要在使用 Ai Studio 时 Darknet 可编译成功,那么就可以按照官方步骤顺利进行后续的训练。

    这里不细说如何修改配置文件等细小的操作内容,因为整个操作过程均可以使用 Jupyter Notebook 完成,所有细节都能在这个.ipynb 文件中找到。这里仅说一下大概的流程:

    1. 查看 Cuda 版本号和显卡型号,下载、安装并配置对应的 CuDNN。

    2. 看看显卡的型号:Colab 是一块 16Gb 显存的英伟达 Tesla T4,Ai Studio 是一块 16Gb 显存的英伟达 Tesla V100。(百度用的显卡性能更强。)

    3. 根据显卡型号和 Cuda 版本安装 CuDNN,但是这步基本上略过,因为环境基本上都预先布置好了。

    4. 安装 Darknet

      • https://github.com/roboflow-ai/Darknet.git 克隆 Darknet 并解压。
      • 修改 Darknet 文件夹中的 Makefile 文件,把 GPU 和 CuDNN 都用起来!
      • 使用 make 命令编译 Darknet。
      • 下载 YOLOv4 ConvNet weights 文件并放入 Darknet 文件夹: yolov4.conv.137` 。
    5. 上传自己的数据集,并且解压在 Darknet 文件夹中。

    6. 新建并配置 obj.data 、train.txt、valid.txt 等文件。( Jupyter Notebook 中一段 Python 代码搞定 )

    7. 新建并配置 YOLOv4 的自定义文件 custom-yolov4-detector.cfg。( Jupyter Notebook 中一段 Python 代码搞定 )

    8. 使用 ./Darknet detector train data/obj.data cfg/custom-yolov4-detector.cfg yolov4.conv.137 -dont_show -map 就能训练啦!训练用时约10小时,每隔一段时间就会在 backup 文件夹下生成一个权重文件。

    9. 训练完成之后,最后一个权重文件为 custom-yolov4-detector_final.weights

    验证自己的训练结果

    1. 最简单的验证方法可以直接在训练完的 Darknet 文件夹内完成( Jupiter Notebook 中 Python 代码搞定)。
    2. 借用一个使用 PySimpleGUI 写的 Python 程序,叫做 [PySimpleGUI-YOLO]() https://github.com/PySimpleGUI/PySimpleGUI-YOLO,克隆或者下载下来之后解压缩。使用 Anaconda 安装 opencv-python 和 pysimplegui 包之后,即可运行。运行的效果就如本文开头所示。

    结语

    目标检测的旅程远还没有结束,本文只是介绍了整个训练过程的大概流程和使用工具,不涉及详细的操作和原理。训练的具体教程和内容可以浏览本文底部链接中的 Jupyter Notebook 查看。

    附件

    附件中包含了:

    • 观赏鱼 yolov4 训练后权重文件
      • custom-yolov4-detector.cfg
      • custom-yolov4-detector_best.weights
      • custom-yolov4-detector_final.weights
    • python 数据处理小脚本
    • 观赏鱼 yolov4 数据集.zip (1种物体(观赏鱼)、122张图片(大多数来自b站萌宠区直播视频截图)、大约1000个不到目标对象(观赏鱼)。)
    • yolov4.conv.137
    • YOLOv4-Darknet-Poisson-Jupter-NoteBook.ipynb

    链接地址:

  • 最优化:Python3 模拟退火算法

    背景

    这个 TP 的背景是这样的:有一个商务旅行团需要去 n 个城市,怎样安排他们的行程可以使行程变得更短?行程的起点和终点都在巴黎。为了解决这个问题,可以使用两种算法:第一种就是最简单的求解 n 个城市 n!种行程的各个距离,然后选取最短的行程;第二种使用la méthode du recuit simulé 模拟退火算法

    关于模拟退火算法

    模拟退火算法的思想借鉴于固体的退火原理,当固体的温度很高的时候,内能比较大,内部粒子处于快速无序运动。当温度慢慢降低的过程中,固体的内能减小,慢慢趋于有序。最终,当固体处于常温时,内能达到最小。此时,粒子最为稳定。

    模拟退火算法从某一较高的温度出发,这个温度称为初始温度,伴随着温度参数的不断下降,算法中的解趋于稳定。但是,可能这样的稳定解是一个局部最优解。此时,模拟退火算法中会以一定的概率跳出这样的局部最优解,以寻找目标函数的全局最优解。

    导入所需的模块

    import numpy as np
    import time
    import itertools
    import matplotlib.pyplot as plt

    单个路程的总距离计算

    # 两个输入
    # city_list : 需要去的城市列表
    # city_dict : 城市的坐标字典
    # 一个输出
    # 路程的总距离
        distance = 0 # 初始化距离
        distance+=np.sqrt(city_dict[city_list[0]]['x']**2+city_dict[city_list[0]]['y']**2)  # 就是求两个坐标点之间的距离,先求第一个(巴黎的坐标为(0,0))
        # 循环求并累加
        for i in range(len(city_list)-1):
            x1=city_dict[city_list[i]]['x']
            y1=city_dict[city_list[i]]['y']
            x2=city_dict[city_list[i+1]]['x']
            y2=city_dict[city_list[i+1]]['y']
            distance=distance+np.sqrt((x1-x2)**2+(y1-y2)**2)
        # 求最后一个(巴黎的坐标为(0,0))
        distance+=np.sqrt(city_dict[city_list[len(city_list)-1]]['x']**2+city_dict[city_list[len(city_list)-1]]['y']**2)
        return distance  # 返回总距离

    随机打乱原有路程

    # 一个输入:list_in 当前旅行团的行程列表
    # 一个输出:尝试路程列表
    # np.random.permutation:输入一个数或者数组,生成一个随机序列,对多维数组来说是多维随机打乱
    # np.asarray:从已有的数组创建数组
        return np.random.permutation(np.asarray(list_in).tolist())

    计算退火新的温度(降温)

    # 输入:h>0 计算参数,浮点数。h越小,算法越有风险;越大算法收敛时间越长。
    # 输入:k 算法参数,整数。
    # 输入:ind 算法的迭代次数,整数。
    # 输入:Temp 当前算法的温度
    # 输出:新的参数k
    # 输出:新的温度Temp
        while ind &lt;= np.exp((k-1)*h) or ind>np.exp(k*h):
             k+=1
             Temp=1.0/k
    
        return k,Temp

    绘图函数

        chemin=['Paris']+chemin+['Paris']
        plt.plot([dico[ville]['x'] for ville in chemin],[dico[ville]['y'] for ville in chemin],'bo-') # 其他城市用蓝色表示
        plt.plot(dico['Paris']['x'],dico['Paris']['y'],'ro') # 巴黎用红色表示
    
        plt.xlim([-500.0,500.0]) # 坐标范围
        plt.ylim([-800.0,300.0])
        plt.title("Trajet")
        ax=plt.gca()
        ax.set_aspect('equal')
    
    def plot_temp(Temp_list):
        plt.figure()
        plt.plot(Temp_list)
        plt.xlabel('$n$')
        plt.ylabel('$T$')
        plt.title(u'Profil de température')
        plt.grid()

    基本参数

    # 城市列表,字典
    dico=dict()
    dico['Lille']      ={'x':52.0, 'y':197.0}
    dico['Orléans']    ={'x':-33.0, 'y':-105.0}
    dico['Lyon']       ={'x':185.0, 'y':-343.0}
    dico['Paris']      ={'x':0.0, 'y':0.0}
    dico['Marseille']  ={'x':225.0, 'y':-617.0}
    dico['Strasbourg'] ={'x':403.0, 'y':-31.0}
    dico['Rennes']     ={'x':-300.0, 'y':-88.0}
    dico['Metz']       ={'x':285.0, 'y':30.0}
    dico['Bordeaux']   ={'x':-213.0, 'y':-448.0}
    dico['Perpignan']  ={'x':40.0, 'y':-688.0}
    dico['Cherbourg']  ={'x':-289.0, 'y':86.0}
    
    parcours=np.random.permutation(['Marseille',
                                    'Lyon',
                                    'Rennes',
                                    'Lille',
                                    'Perpignan',
                                    'Cherbourg']).tolist()
    # 城市越多,传统的算法将花费更多时间。</pre>

    用传统解决办法

    # 计算所有的可能路线,如果是5个城市就是5!种情况;如果是10个城市就是10!种情况。
    t1=time.time()   #计时开始
    
    permutations=list()
    for i in itertools.permutations(parcours):  #排列顺序可变,元素不重复:每一项用长度为r的元组表示
        permutations.append(i)      #所有的可能情况,一种种情况往上叠。
    print('Nombre de trajets étudiés : ', len(permutations))
    
    cost=list()
    for i in range(0,len(permutations)-1):
        parcours_chaque=list(permutations[i])
        parcours_chaque=['Paris']+parcours_chaque+['Paris']
        print(parcours_chaque)
        print(cost_function(parcours_chaque,dico)) #传到上方的cost_function算法里
        cost.append(cost_function(parcours_chaque,dico)) #所有的可能情况,一种种情况往上叠。
    t2=time.time() # 计时结束
    
    # 打印结果
    cost=np.asarray(cost)
    print('Trajet le plus court :')
    for ind, item in zip(range(1,len(parcours)+1),permutations[cost.argmin()]):
        print(ind,' ',item)
    print('Distance totale : ', cost.min())
    print('Temps de calcul : ',t2-t1)

    用模拟退火法

    candidate=parcours
    # 先设置参数
    itermax=10000
    hpar=3.0
    kpar=1
    Temp=1.0/kpar
    Temp_list=[Temp]
    
    t1=time.time()   #计时开始
    for ind in range(itermax):
        #计算新的行程尝试(就是根据现有行程随机生成一个新的)
        new_candidate=compute_new_candidate(candidate)
        #计算新的行程和旧的行程的路程差距
        delta=cost_function(new_candidate,dico)-cost_function(candidate,dico)
        #如果新的候选路线更长,则仍可以一定的概率接受它。
        if delta &lt;= 0:
            candidate = new_candidate
        else:
            u=np.random.uniform(0,1) # 产生一个0到1之间的随机数
            if np.exp(-delta/Temp) >= u :
                candidate = new_candidate
        # 降温
        kpar, Temp = compute_Temp(hpar,kpar,ind+2,Temp)
        Temp_list.append(Temp)
    t2=time.time()
    
    # 打印结果
    print('Trajet le plus court :')
    for ind, item in zip(range(1,len(parcours)+1),candidate):
        print(ind,' ',item)
    print('Distance totale : ', cost_function(candidate,dico))
    print('Temps de calcul : ',t2-t1)
    
    # 绘制图表
    plot_chemin(parcours,dico)
    plot_chemin(candidate,dico)
    
    plot_temp(Temp_list)
    plt.show()

    参考资料

    https://blog.csdn.net/google19890102/article/details/45395257