主頁 >  其他 > ML-Agents(十)Crawler

ML-Agents(十)Crawler

2020-09-14 10:30:13 其他

目錄

  • 一、前言
  • 二、環境與訓練引數
  • 三、場景基本結構
  • 四、代碼分析
    • BodyPart
    • JointDriveController
    • GroundContact
    • CrawlerAgent
      • Agent初始化
      • Agent環境觀測值收集
      • Agent動作反饋
      • Agent重置
      • 其他
  • 五、訓練
  • 六、總結

一、前言

今天是六一,先祝大家六一快樂!距上次發文章已經過了快一個月,作業有點忙,所以有點拖更,見諒~

我們這次來研究一下Crawler(爬蟲)示例,官方其實還有個示例——Reacher,但是這個示例比較簡單,就是模擬一個帶兩個關節的手臂去跟隨目標物體,其核心就是讓我們學會如何利用Configurable Joint來進行訓練,相比之下,Crawler就復雜得多,因此我們跳過Reacher這個示例,感興趣的童靴可以自己去研究一下,

先來看看Crawler示例的效果:

crawler1

可以看到這次小藍變成了一只四腳爬蟲,每只腳上有兩個關節,其任務就是通過四肢協調運動找到綠色方塊,大概可以預想到,這個示例在開始訓練的時候先要解決的問題就是小藍怎么通過四肢協調站起來不摔倒,然后進行移動,最后才是找到綠色方塊,

此外,該示例有兩個場景:CrawlerDynamicTarget和CrawlerStaticTarget,分別是動態目標物和靜態目標物,上圖展示的就是動態生成目標物,綠色的方塊會產生到隨機的位置,而后者是綠色方塊就在小藍的前方,因此我們直接研究較難的CrawlerDynamicTarget示例,CrawlerStaticTarget也就迎刃而解了,

二、環境與訓練引數

  • 設定:有四只胳膊和四只前臂的生物,

  • 目標:Agent必須移動它的身體朝目標方向移動而不摔倒,

    • CrawlerStaticTarget:目標方向一直在前方,
    • CrawlerDynamicTarget:目標會隨機改變位置,
  • Agents:環境中包含8個相同行為引數的agent,

  • Agent獎勵設定(獨立的):

    • 若小藍的速度方向朝向目標方向,則+0.03*(速度與目標方向的點積),
    • 若小藍面朝目標方向,則+0.01*(小藍向前方向與目標方向的點積),
    • 隨著時間的增加,每一幀-0.001(PS.該獎勵是可勾選可不勾選的,官方示例默認未勾選),

    Note:這里來說一下,為什么獎勵這樣設定,首先要明白點積的概念:從幾何意義來講,若有兩個向量ab,則a·b = |a||b|cos(a, b),由此可推出:

    ①當a·b>0,兩向量方向基本相同,夾角在0°到90°之間;

    ②當a·b=0,兩向量正交,相互垂直;

    ③當a·b<0,兩向量方向基本相反,夾角在90°到180°之間,

    因此,當小藍和目標速度或朝向大于90°時,其實是在獎勵負數,由此來迫使小藍面朝目標物并且向目標物前進,這里的設定其實還比較巧妙,可以注意一下,

  • 行為引數:

    • 矢量觀測空間:117個變數,分別對應于每個肢節的位置、角度、速度、角速度,再加小藍body的加速度和角加速度,
    • 矢量動作空間:(Continuous)20個變數,對應關節的轉動,
    • 視覺觀察:無,
  • 可變引數:無,

  • 基準平均獎勵:

    CrawlerStaticTarget:2000,

    CrawlerDynamicTarget:400,

三、場景基本結構

這個示例的場景很簡單:

image-20200508212445189

其中,CrawlerSettings里有一個AdjustTrainingTimescale腳本,該腳本就是通過數字鍵來改變Time.timeScale屬性的,說是可以在訓練的時候用,本次訓練的時候我會試一下改變Time.timeScale能否加快訓練速度,

然后來詳細講一下訓練單元:

image-20200508213237779

訓練單元里的Walls、Ground以及Target都如同直譯,沒啥特別說的,主要看一下Crawler:

image-20200508214745481

Crawler身上有5個腳本,BehaviorParameters是行為引數腳本,只要有繼承Agent的腳本,則會自動附加該腳本;CrawlerAgent則是agent訓練腳本;DecisionRequester會定期自動為agent請求決策,如果沒有該腳本,則需要手動呼叫Agent.RequestDecision()方法,不過之前的例子其實Agent上都有這個腳本,我以前忘記講了;JointDriveController控制各個關節,具體還有什么作用一會兒代碼決議的時候再來看;ModelOverrider這個腳本在0.15.0里是沒有的,這個腳本可以先不用管它,這個腳本的作用大概是在訓練前,在Console里輸入指定的命令,允許在訓練期間覆寫代理的.nn模型檔案,

除此之外,Crawler的Body還有一個GroundContact腳本,該腳本是用來檢測Crawler是否摔倒,即頭部觸地,此時可以對agent懲罰1,并重新開始新的Episode,這兩個都是可選的:

image-20200508222450705

Crawler有四只前臂和四只腿,再加一個Body:

image-20200508221933225

它們是以Configuration Joint(關節組件)兩兩相連的,以一部分為例:

image-20200508222712491

這里小提一下可配置關節 (Configurable Joint),該組件具有非常強的自定義性,具體可以看下圖:

image-20200508223030033

可以配置的引數非常多,但其實很多都是基礎的引數,各個引數是如何限制該關節的,建議大家自己下去后再去深入研究,我們這里大概可以看一下Crawler的四肢是如何運動的:
首先看前臂:

image-20200508223854107

前臂的繞X、Y角旋轉沒被鎖定(Locked),其余的都被鎖定了,

再看一下后臂:

image-20200508224118852

后臂的只有繞X軸是沒被鎖定的,其余的都被鎖定了,

這樣組合的結果就是(靈魂畫手,能看懂即可,,,):

image-20200508224446490

前臂可以繞自身中心軸轉,可以繞body上下轉,而后臂只能繞前臂上下,該關節配置構成了Crawler運動的基礎,

每個后臂還有一個腳的子物體(就是那個小圓球),我看官方本來是想在腳接觸地面時令小球換個材質,不接觸地面時又是另一種材質,但是示例中最終并沒有使用該方法,我們可以來試試這個功能:

建立兩個材質球Red和Green,然后分別將這兩個材質球拖到Crawler預制體的Agent腳本上,并勾選Use Foot Grounded Visualization選項:

image-20200508230453030

運行的話我們就會有以下效果:

crawler2

腳基礎地面就變成綠色,不接觸地面就是紅色,

四、代碼分析

本示例的代碼首先需要理解Agent身上的JointDriveController腳本,該腳本用于設定Crawler的肢體關節協調(其他三個示例Walker、Warm也一樣用到該腳本),同時該腳本中還包括BodyPart腳本,用于存盤agent中每個肢體部分的相關資訊,

我們先來看一下BodyPart結構,是如何對Crawler的身體做存盤的,

BodyPart

	/// <summary>
    /// 用于存盤agent每個身體部位的行動和學習相關資訊
    /// </summary>
    [System.Serializable]
    public class BodyPart
    {
        [Header("Body Part Info")] [Space(10)] public ConfigurableJoint joint;//身體的可配置關節組件
        public Rigidbody rb;//剛體
        [HideInInspector] public Vector3 startingPos;//起始位置
        [HideInInspector] public Quaternion startingRot;//起始角度

        [Header("Ground & Target Contact")]
        [Space(10)]
        public GroundContact groundContact;//檢測地面接觸
        public TargetContact targetContact;//檢測目標接觸
        
        [FormerlySerializedAs("thisJDController")]
        [HideInInspector] public JointDriveController thisJdController;//關節組件Controller

        [Header("Current Joint Settings")]
        [Space(10)]
        public Vector3 currentEularJointRotation;//關節當前歐拉角

        [HideInInspector] public float currentStrength;//當前作用力
        public float currentXNormalizedRot;
        public float currentYNormalizedRot;
        public float currentZNormalizedRot;

        [Header("Other Debug Info")]
        [Space(10)]
        public Vector3 currentJointForce;//當前關節作用力

        public float currentJointForceSqrMag;//當前關節作用力大小
        public Vector3 currentJointTorque;//當前關節轉矩
        public float currentJointTorqueSqrMag;//當前關節轉矩大小
        public AnimationCurve jointForceCurve = new AnimationCurve();//關節作用力曲線
        public AnimationCurve jointTorqueCurve = new AnimationCurve();//關節力矩曲線

        /// <summary>
        /// Reset body part to initial configuration.
        /// 身體關節初始化
        /// </summary>
        public void Reset(BodyPart bp)
        {
            bp.rb.transform.position = bp.startingPos;//位置
            bp.rb.transform.rotation = bp.startingRot;//角度
            bp.rb.velocity = Vector3.zero;//速度
            bp.rb.angularVelocity = Vector3.zero;//角速度
            if (bp.groundContact)
            {//地面接觸標志置位
                bp.groundContact.touchingGround = false;
            }

            if (bp.targetContact)
            {//目標接觸標志置位
                bp.targetContact.touchingTarget = false;
            }
        }

        /// <summary>
        /// 根據給定的x,y,z角度和力的大小計算扭矩
        /// </summary>
        public void SetJointTargetRotation(float x, float y, float z)
        {
            x = (x + 1f) * 0.5f;
            y = (y + 1f) * 0.5f;
            z = (z + 1f) * 0.5f;

            //Mathf.Lerp(from : float, to : float, t : float) 插值,t=0~1,回傳(to-from)*t
            var xRot = Mathf.Lerp(joint.lowAngularXLimit.limit, joint.highAngularXLimit.limit, x);
            var yRot = Mathf.Lerp(-joint.angularYLimit.limit, joint.angularYLimit.limit, y);
            var zRot = Mathf.Lerp(-joint.angularZLimit.limit, joint.angularZLimit.limit, z);

            //Mathf.InverseLerp(from : float, to : float, value : float)反插值,回傳value在from和to之間的比例值
            currentXNormalizedRot = Mathf.InverseLerp(joint.lowAngularXLimit.limit, joint.highAngularXLimit.limit, xRot);
            currentYNormalizedRot = Mathf.InverseLerp(-joint.angularYLimit.limit, joint.angularYLimit.limit, yRot);
            currentZNormalizedRot = Mathf.InverseLerp(-joint.angularZLimit.limit, joint.angularZLimit.limit, zRot);

            joint.targetRotation = Quaternion.Euler(xRot, yRot, zRot);//使關節轉向目標角度
            currentEularJointRotation = new Vector3(xRot, yRot, zRot);//當前關節歐拉角
        }
        /// <summary>
        /// 設定關節作用力大小
        /// </summary>
        /// <param name="strength"></param>
        public void SetJointStrength(float strength)
        {
            var rawVal = (strength + 1f) * 0.5f * thisJdController.maxJointForceLimit;
            var jd = new JointDrive
            {
                positionSpring = thisJdController.maxJointSpring,//關節最大彈力
                positionDamper = thisJdController.jointDampen,//關節彈性大小
                maximumForce = rawVal//施加的最大力
            };
            joint.slerpDrive = jd;
            currentStrength = jd.maximumForce;//當前施加的力
        }
    }

以上代碼說難不難,說簡單也不簡單,,,主要是涉及到Joint組件的使用,這里面牽扯到一些力學知識,我就不望文生義了,有興趣的同學可以深入研究一下,

JointDriveController

 	/// <summary>
    /// Joint控制器
    /// </summary>
    public class JointDriveController : MonoBehaviour
    {
        [Header("Joint Drive Settings")]
        [Space(10)]
        public float maxJointSpring;//關節最大彈力大小
        public float jointDampen;//關節抵抗彈力的強度
        public float maxJointForceLimit;//最大作用力
        //float m_FacingDot;//該變數沒用到

        //身體部位字典
        [HideInInspector] public Dictionary<Transform, BodyPart> bodyPartsDict = new Dictionary<Transform, BodyPart>();

        /// <summary>
        /// 創建BodyPart物件并將其添加到字典中
        /// </summary>
        public void SetupBodyPart(Transform t)
        {
            var bp = new BodyPart
            {
                rb = t.GetComponent<Rigidbody>(),
                joint = t.GetComponent<ConfigurableJoint>(),
                startingPos = t.position,
                startingRot = t.rotation
            };
            bp.rb.maxAngularVelocity = 100;//最大角速度為100

            //添加地面碰撞檢測腳本
            bp.groundContact = t.GetComponent<GroundContact>();
            if (!bp.groundContact)
            {
                bp.groundContact = t.gameObject.AddComponent<GroundContact>();
                bp.groundContact.agent = gameObject.GetComponent<Agent>();
            }
            else
            {
                bp.groundContact.agent = gameObject.GetComponent<Agent>();
            }

            //添加目標碰撞檢測腳本
            bp.targetContact = t.GetComponent<TargetContact>();
            if (!bp.targetContact)
            {
                bp.targetContact = t.gameObject.AddComponent<TargetContact>();
            }

            bp.thisJdController = this;
            bodyPartsDict.Add(t, bp);
        }
        /// <summary>
        /// 更新身體每一部分當前的作用力及轉矩
        /// </summary>
        public void GetCurrentJointForces()
        {
            foreach (var bodyPart in bodyPartsDict.Values)
            {//輪詢身體每部分
                if (bodyPart.joint)
                {
                    bodyPart.currentJointForce = bodyPart.joint.currentForce;//當前關節作用力
                    bodyPart.currentJointForceSqrMag = bodyPart.joint.currentForce.magnitude;//當前關節作用力大小
                    bodyPart.currentJointTorque = bodyPart.joint.currentTorque;//當前關節作用轉矩
                    bodyPart.currentJointTorqueSqrMag = bodyPart.joint.currentTorque.magnitude;//當前關節作用轉矩大小
                    if (Application.isEditor)
                    {//IDE下,創建關節作用力和關節力矩的曲線
                        if (bodyPart.jointForceCurve.length > 1000)
                        {
                            bodyPart.jointForceCurve = new AnimationCurve();
                        }

                        if (bodyPart.jointTorqueCurve.length > 1000)
                        {
                            bodyPart.jointTorqueCurve = new AnimationCurve();
                        }

                        bodyPart.jointForceCurve.AddKey(Time.time, bodyPart.currentJointForceSqrMag);
                        bodyPart.jointTorqueCurve.AddKey(Time.time, bodyPart.currentJointTorqueSqrMag);
                    }
                }
            }
        }
    }

這個腳本主要是將多個BodyPart進行管理的作用,同時可以實時更新身體每一部分作用力及轉矩,用以Agent收集

BodyPart的相關資訊,

以上兩個腳本我注釋的比較粗略,主要是對Joint組件的不熟悉造成的,該組件使用的細節我就不深入講解了,我們主要能弄清楚ml-agents是如何對這種多關節復雜的agent進行訓練的就可以了,

GroundContact

	/// <summary>
    /// 該腳本包含了agent可能與地面接觸的關節運動的邏輯,通過該腳本,可以設定某些身體部位如果接觸地面后做出懲罰
    /// </summary>
    [DisallowMultipleComponent] //不可重復掛載特性
    public class GroundContact : MonoBehaviour
    {
        [HideInInspector] public Agent agent;//對應的agent

        //當接觸地面時,是否令agent置位
        [Header("Ground Check")] public bool agentDoneOnGroundContact;
        //是否在接觸地面時懲罰agent
        public bool penalizeGroundContact;
        //接觸地面懲罰的數值
        public float groundContactPenalty;
        //接觸地面標志
        public bool touchingGround;
        //地面物體的tag
        const string k_Ground = "ground";

        /// <summary>
        /// 檢測碰撞是否為地面
        /// </summary>
        void OnCollisionEnter(Collision col)
        {
            if (col.transform.CompareTag(k_Ground))
            {//碰撞到地面
                touchingGround = true;
                if (penalizeGroundContact)
                {//懲罰agent
                    agent.SetReward(groundContactPenalty);
                }
                if (agentDoneOnGroundContact)
                {//使得agent重新開始
                    agent.EndEpisode();
                }
            }
        }
        /// <summary>
        /// 檢查地面碰撞是否結束,并使其標志復位
        /// </summary>
        void OnCollisionExit(Collision other)
        {
            if (other.transform.CompareTag(k_Ground))
            {
                touchingGround = false;
            }
        }
    }

該腳本在小藍的每個BodyPart上都有掛載,我們可以來詳細看一下:

image-20200525212333536

image-20200525212400364

image-20200525212417000

由上圖可知,當agent的leg以及Body接觸地面后,會使agent懲罰1,并使得agent重新開始新的一輪訓練;而foreLeg接觸地面則不做任何懲罰,

同時可以留意一下腳本一開始的[DisallowMultipleComponent]特性,該特性可使得腳本組件只在同一物體上存在一個,

下面我們來看一下Crawler的Agent腳本,

CrawlerAgent

Agent初始化

/// <summary>
/// Crawler的Agent腳本
/// </summary>
[RequireComponent(typeof(JointDriveController))]//要求JointDriveController腳本同時存在
public class CrawlerAgent : Agent
{
    [Header("Target To Walk Towards")]
    [Space(10)]
    public Transform target;//目標方塊
    public Transform ground;//地面
    public bool detectTargets;//檢測目標標志
    public bool targetIsStatic;//目標物是否是靜態的
    public bool respawnTargetWhenTouched;//當為true時,到達目標后,目標會重新隨機到其他地方
    public float targetSpawnRadius;//目標隨機位置半徑

    //各部分BodyPart
    [Header("Body Parts")] [Space(10)] public Transform body;
    public Transform leg0Upper;
    public Transform leg0Lower;
    public Transform leg1Upper;
    public Transform leg1Lower;
    public Transform leg2Upper;
    public Transform leg2Lower;
    public Transform leg3Upper;
    public Transform leg3Lower;

    [Header("Joint Settings")] [Space(10)] JointDriveController m_JdController;//Joint控制器
    Vector3 m_DirToTarget;//小藍到目標物的方向
    float m_MovingTowardsDot;//小藍速度方向與目標方向的點積
    float m_FacingDot;//小藍正方向與目標方向的點積

    [Header("Reward Functions To Use")]
    [Space(10)]
    public bool rewardMovingTowardsTarget;//速度方向與目標方向獎勵是否開啟
    public bool rewardFacingTarget;//小藍正方向與目標方向點積是否開啟
    public bool rewardUseTimePenalty;//是否隨時間流逝而懲罰

    [Header("Foot Grounded Visualization")]
    [Space(10)]
    public bool useFootGroundedVisualization;//是否使腳接觸地面改變材質
    
    public MeshRenderer foot0;
    public MeshRenderer foot1;
    public MeshRenderer foot2;
    public MeshRenderer foot3;
    public Material groundedMaterial;//接觸地面腳的材質
    public Material unGroundedMaterial;//未接觸地面腳的材質

    Quaternion m_LookRotation;//小藍到目標方向四元數
    Matrix4x4 m_TargetDirMatrix;//目標方向旋轉矩陣

    /// <summary>
    /// Agent初始化
    /// </summary>
    public override void Initialize()
    {
        m_JdController = GetComponent<JointDriveController>();//獲得Joint控制器
        m_DirToTarget = target.position - body.position;//小藍到目標方向向量

        //Setup each body part
        //設定身體每一部分
        m_JdController.SetupBodyPart(body);
        m_JdController.SetupBodyPart(leg0Upper);
        m_JdController.SetupBodyPart(leg0Lower);
        m_JdController.SetupBodyPart(leg1Upper);
        m_JdController.SetupBodyPart(leg1Lower);
        m_JdController.SetupBodyPart(leg2Upper);
        m_JdController.SetupBodyPart(leg2Lower);
        m_JdController.SetupBodyPart(leg3Upper);
        m_JdController.SetupBodyPart(leg3Lower);
    }
}

這段代碼主要注意一下上面將要使用的變數,

Agent環境觀測值收集

	/// <summary>
    /// 觀測值收集
    /// </summary>
    /// <param name="sensor"></param>
    public override void CollectObservations(VectorSensor sensor)
    {
        m_JdController.GetCurrentJointForces();//更新身體每一部分當前的作用力及轉矩

        //更新小藍到目標的方向
        m_DirToTarget = target.position - body.position;//向量agent到target
        m_LookRotation = Quaternion.LookRotation(m_DirToTarget);//獲取小藍正向到目標向量的四元數
        m_TargetDirMatrix = Matrix4x4.TRS(Vector3.zero, m_LookRotation, Vector3.one);//將上述四元數轉換為旋轉矩陣

        //Body到地面的高度(下方測量值)
        RaycastHit hit;
        if (Physics.Raycast(body.position, Vector3.down, out hit, 10.0f))
        {
            sensor.AddObservation(hit.distance);
        }
        else
            sensor.AddObservation(10.0f);

        //前方、上方測量值收集
        //獲取body的正向到目標方向轉換的相對向量
        var bodyForwardRelativeToLookRotationToTarget = m_TargetDirMatrix.inverse.MultiplyVector(body.forward);
        sensor.AddObservation(bodyForwardRelativeToLookRotationToTarget);
        //獲取body的上方到目標方向轉換的相對向量
        var bodyUpRelativeToLookRotationToTarget = m_TargetDirMatrix.inverse.MultiplyVector(body.up);
        sensor.AddObservation(bodyUpRelativeToLookRotationToTarget);

        foreach (var bodyPart in m_JdController.bodyPartsDict.Values)
        {//收集身體每一部分的測量值
            CollectObservationBodyPart(bodyPart, sensor);
        }
    }
    /// <summary>
    /// 將每個身體部位的相關資訊添加到觀察中
    /// </summary>
    public void CollectObservationBodyPart(BodyPart bp, VectorSensor sensor)
    {
        var rb = bp.rb;
        //是否接觸地面
        sensor.AddObservation(bp.groundContact.touchingGround ? 1 : 0); 
        //bp速度方向相對于目標方向的相對矢量,即速度與目標方向關系
        var velocityRelativeToLookRotationToTarget = m_TargetDirMatrix.inverse.MultiplyVector(rb.velocity);
        sensor.AddObservation(velocityRelativeToLookRotationToTarget);
        //bp角加速度方向相對于目標方向的相對矢量,即角速度與目標方向關系
        var angularVelocityRelativeToLookRotationToTarget = m_TargetDirMatrix.inverse.MultiplyVector(rb.angularVelocity);
        sensor.AddObservation(angularVelocityRelativeToLookRotationToTarget);

        if (bp.rb.transform != body)
        {//除了body之外的部分,獲取每一部分(肢體)的相對位置,x、y、z當前角度以及當前作用力
            var localPosRelToBody = body.InverseTransformPoint(rb.position);
            sensor.AddObservation(localPosRelToBody);
            sensor.AddObservation(bp.currentXNormalizedRot); // Current x rot
            sensor.AddObservation(bp.currentYNormalizedRot); // Current y rot
            sensor.AddObservation(bp.currentZNormalizedRot); // Current z rot
            sensor.AddObservation(bp.currentStrength / m_JdController.maxJointForceLimit);
        }
    }

這部分代碼我認為是Crawler示例中比較重要的部分,因為從中可以學習到如何對于多關節的復雜問題進行資料收集,這里面又涉及到一些角度轉換的問題,例如四元數、轉換矩陣操作等,

總體來,這部分資料主要如下:小藍forward到目標的相對旋轉關系,小藍up到目標的相對關系,每一部分速度方向、角速度相對于目標方向關系,每部分肢節的作用力及旋轉角度等,

這里有興趣的童靴可以仔細研究一下,我這里主要來搞一些四元數的相關用法,

  • 四元數(Quaternion)

    網上關于四元數的文章應該很多了,我按我得理解寫一下,有錯誤請指正,

    首先說到四元數,主要用到的地方就是三維世界中物體的旋轉,對于三維世界中描述物體的旋轉,我們一般有三種方法表示:旋轉矩陣、歐拉角、四元數,

    以一個點p為例,以上述三種方法旋轉得到p',則有:

    • 旋轉矩陣

      旋轉矩陣乘以點p的齊次坐標,得到旋轉后的的點p':

      image-20200531195201386

      另,繞x,y,z軸旋轉θ的矩陣為:

      image-20200531200229579

    • 歐拉角

      歐拉角描述旋轉,是我們通常用的方式,例如下圖,可以將其旋轉分解為三步(藍色為起始坐標系,紅色為旋轉后的坐標系):

      image-20200531200756335

      先繞z軸旋轉α,再繞x軸旋轉β,最后繞z軸旋轉γ,當然這里的旋轉順序并不是規定死的,在Unity中,旋轉的順序是ZXY順序,

      那么對應于以上歐拉角的旋轉矩陣為:

      image-20200531201207752

      不過歐拉角有個解決不了的問題,即“萬向節死鎖”問題,同時使用歐拉角也不能進行平滑插值,

    • 四元數

      四元數其實是一種高階復數,它可以很方便的描述物體繞任意軸的旋轉,四元數q可以表示為:

      image-20200531201637816

      其中,i、j、k滿足:

      image-20200531201657359

      同時四元數又可以寫成一個向量和一個實數的組合形式:

      image-20200531201738536

      四元數可以看作是向量和實數的更加一般的形式,我們普通用的向量可以視為實部為0的四元數,而實數可以視為虛部為0的四元數,由此可以得到一些四元數符合實數或者向量的運算性質(感興趣的同學可以自己去查,例如四元數的乘法、共軛四元數、四元數的逆等),

      利用四元數來刻畫三維空間中的旋轉,令點p繞單位向量(x,y,z)表示的軸旋轉θ,則可申明一個四元數q:

      image-20200531202214278

      再令我們要旋轉的p點寫成四元數的形式p(P,0)(相當于虛部為p,實部為0),則旋轉后的p'可以用以下公式計算:

      image-20200531202545007

      當然這個公式的右邊可以看到,是三個四元數的乘法,最后得到的p'也是一個四元數,

  • Unity中四元數的API

    上面我們講了三種旋轉方式,都只是比較簡單的講解,有許多細節其實并沒有涉及到,其實可以分別利用三種方法對一個點去計算旋轉后的位置,這樣可以更加深刻的加深印象,

    在Unity中,我們大多數使用的是歐拉角來描述物體的旋轉,但其實四元數更加方便,功能更加強大,但是四元數的實部和虛部如果你不是很了解,則不要去修改它們,這里我們只是決議一下Quaternion的一些API用法,

    • Quaternion.AngleAxis(float angle, Vector3 axis)

      這個方法其實就是四元數的本來用法,即繞某軸axis旋轉angle角度,例如,使得一個Cube繞Unity中x軸旋轉45度,則有

      q = Quaternion.AngleAxis(45, Vector3.right);Cube.transform.rotation = q;

      上式中q為任意四元數,效果如下圖:

      初始位置,藍色線為物體自身z軸,綠色為y軸,紅色為x軸,

      image-20200531204445451

      變換后:

      image-20200531204737582

      當然,我們也可以利用該函式使得Cube繞任意軸旋轉angle角度,我們在場景中放置一個軸:

      image-20200531205038521

      image-20200531205509261

      然后讓Cube繞這這根軸旋轉45度,既有:

      q = Quaternion.AngleAxis(45, Axis.transform.up);Cube.transform.rotation = q;

      image-20200531205547070

    • Quaternion.LookRotation(Vector3 forward, Vector3 upwards = Vector3.up)

      這個函式其實就是讓物體的前方指向forward方向,物體的上方指向upwards方向(可不賦值),例如,我現在要讓Cube的前方指向下,上方指向前,則有:

      q = Quaternion.LookRotation(Vector3.down, Vector3.forward);Cube.transform.rotation = q;

      image-20200531210335029

      當然這個函式就可以衍生出一些還玩的用法,例如同步兩個物體的旋轉,我們引入一個Target球體:

      image-20200531210737980

      現在使得方塊的前方與球體的上方一致,方塊的上方與球體的后方一致,將代碼在Update中執行:

      q = Quaternion.LookRotation(Target.transform.up, -Target.transform.forward);Cube.transform.rotation = q;

      則有:

      crawler3

      可以看到上圖中方塊的前方(藍色軸)一直與球體的上方(綠色軸)一致,而方塊的上方(綠色軸)一直與球體的后方一致,

      除此之外,我們還可以利用該方法使得方塊一直面向球體:

      q = Quaternion.LookRotation(Target.transform.position);Cube.transform.rotation = q;

      crawler4

      當然這樣寫有個弊端,就是方塊的位置不能移動,如果移動的話,則該方法失效:

      crawler5

      可以看到將方塊上移一些,則不能看向目標球體了,因此我們可以對這段代碼改造一下:

      var vec = Target.transform.position - Cube.transform.position;q = Quaternion.LookRotation(vec);Cube.transform.rotation = q;

      crawler6

      這樣就可以一直使得方塊的前方指向目標球體了,

    • Quaternion.FromToRotation(Vector3 fromDirection, Vector3 toDirection)

      這個函式主要是將某個方向fromDirection指向另一個方向toDirection,例如將Cube的前方指向Cube的下方,則有:

      q = Quaternion.FromToRotation(Cube.transform.forward, -Cube.transform.up);

      當然,除此之外,我們會發現如果使用Quaternion.LookRotation可以令方塊前方一直看向目標球體,那如果想讓方塊的上方一直看向目標球體怎么辦呢?這里就需要使用Quaternion.FromToRotation來操作了,一開始你可能會寫出以下代碼:

      q = Quaternion.FromToRotation(Cube.transform.up, Target.transform.position);

      但是這段代碼是有問題的,會使得Cube產生抖動:

      crawler7

      所以這里其實是應該這么寫:

      q = Quaternion.FromToRotation(Vector3.up, Target.transform.position);

      這樣就能實作方塊的上方一直指向目標球體,與上面同理,再次改造一下,使得方塊移動位置也可以指向球體,則有:

      var vec= Target.transform.position - Cube.transform.position;q = Quaternion.FromToRotation(Vector3.up, vec);

      crawler8

      OK,到這里我們對于四元數這幾個函式就講解到這,算是拋磚引玉,如果理解有什么不正確的地方還請留言指正,附上測驗的代碼,注釋可以自己進行撤銷去測驗,基本都是上面講解中涉及到的代碼:

      public class QuaTest : MonoBehaviour
      {
          public GameObject Cube;
          public GameObject Axis;
          public GameObject Target;
      
          private Quaternion q;
      
          void Update()
          {
              //向右(x)
              Debug.DrawLine(Cube.transform.position, Cube.transform.localPosition + Cube.transform.right, Color.red);
              Debug.DrawLine(Target.transform.position, Target.transform.localPosition + Target.transform.right, Color.red);
              //向前(z)
              Debug.DrawLine(Cube.transform.position, Cube.transform.localPosition + Cube.transform.forward, Color.blue);
              Debug.DrawLine(Target.transform.position, Target.transform.localPosition + Target.transform.forward, Color.blue);
              //向上(y)
              Debug.DrawLine(Cube.transform.position, Cube.transform.localPosition + Cube.transform.up, Color.green);
              Debug.DrawLine(Target.transform.position, Target.transform.localPosition + Target.transform.up, Color.green);
      
              if (Input.GetKeyDown(KeyCode.Q))
              {
                  //Cube繞x軸旋轉45度
                  //q = Quaternion.AngleAxis(45, Vector3.right);
      
                  //Cube繞Axis自定義軸旋轉45度
                  //q = Quaternion.AngleAxis(45, Axis.transform.up);
      
                  //繞x軸旋轉90度
                  //q = Quaternion.Euler(90, 0, 0);//歐拉角實作
                  //q = Quaternion.LookRotation(Vector3.down, Vector3.forward);//令物體的前方指向下,上方指向前
                  //q = Quaternion.AngleAxis(90, Vector3.right);//令物體繞右軸(x軸)旋轉90度
                  //q = Quaternion.FromToRotation(Vector3.forward, Vector3.down);//令物體的前方指向物體的下方,不能使用自身坐標系
                  //q = Quaternion.FromToRotation(Cube.transform.forward, -Cube.transform.up);//令物體的前方指向物體的下方,不能使用自身坐標系
      
                  Cube.transform.rotation = q;
              }
              //令方塊前方與球體上方一致,方塊上方與球體后方一致,即令方塊的旋轉與球的旋轉同步
              //q = Quaternion.LookRotation(Target.transform.up, -Target.transform.forward);
      
              //令方塊一直面向目標球體,若Cube自身坐標變了,則失效
              //q = Quaternion.LookRotation(Target.transform.position);
              //令方塊一直面向目標球體,Cube自身坐標變也一直面向
              //var vec = Target.transform.position - Cube.transform.position;
              //q = Quaternion.LookRotation(vec);
      
              //令方塊上方一直看向目標球體
              //q = Quaternion.FromToRotation(Cube.transform.up, Target.transform.position);//如果使用自身的向上向量,會產生抖動
              //q = Quaternion.FromToRotation(Vector3.up, Target.transform.position);
              var vec= Target.transform.position - Cube.transform.position;
              q = Quaternion.FromToRotation(Vector3.up, vec);//注,這里需要使用世界坐標向上,而不能使用自身坐標系的向上向量
      
              Cube.transform.rotation = q;
          }
      }
      

Agent動作反饋

 	/// <summary>
    /// 動作反饋
    /// </summary>
    /// <param name="vectorAction"></param>
    public override void OnActionReceived(float[] vectorAction)
    {
        //獲取所有部分
        var bpDict = m_JdController.bodyPartsDict;

        var i = -1;
        //設定每一部分的角度
        bpDict[leg0Upper].SetJointTargetRotation(vectorAction[++i], vectorAction[++i], 0);
        bpDict[leg1Upper].SetJointTargetRotation(vectorAction[++i], vectorAction[++i], 0);
        bpDict[leg2Upper].SetJointTargetRotation(vectorAction[++i], vectorAction[++i], 0);
        bpDict[leg3Upper].SetJointTargetRotation(vectorAction[++i], vectorAction[++i], 0);
        bpDict[leg0Lower].SetJointTargetRotation(vectorAction[++i], 0, 0);
        bpDict[leg1Lower].SetJointTargetRotation(vectorAction[++i], 0, 0);
        bpDict[leg2Lower].SetJointTargetRotation(vectorAction[++i], 0, 0);
        bpDict[leg3Lower].SetJointTargetRotation(vectorAction[++i], 0, 0);

        //設定每一部分的作用力
        bpDict[leg0Upper].SetJointStrength(vectorAction[++i]);
        bpDict[leg1Upper].SetJointStrength(vectorAction[++i]);
        bpDict[leg2Upper].SetJointStrength(vectorAction[++i]);
        bpDict[leg3Upper].SetJointStrength(vectorAction[++i]);
        bpDict[leg0Lower].SetJointStrength(vectorAction[++i]);
        bpDict[leg1Lower].SetJointStrength(vectorAction[++i]);
        bpDict[leg2Lower].SetJointStrength(vectorAction[++i]);
        bpDict[leg3Lower].SetJointStrength(vectorAction[++i]);
    }

注意這里Action Space Type為Continuous,且Space Size為20

Agent重置

/// <summary>
    /// Agent重置,使得身體各個部分初始化等
    /// </summary>
    public override void OnEpisodeBegin()
    {
        if (m_DirToTarget != Vector3.zero)
        {//讓小藍正向面對目標物
            transform.rotation = Quaternion.LookRotation(m_DirToTarget);
        }
        transform.Rotate(Vector3.up, Random.Range(0.0f, 360.0f));//使得小藍隨機旋轉一個角度

        foreach (var bodyPart in m_JdController.bodyPartsDict.Values)
        {//身體各部分置位
            bodyPart.Reset(bodyPart);
        }
        if (!targetIsStatic)
        {//如果開啟動態目標物,則隨機重置目標物位置
            GetRandomTargetPos();
        }
    }
    /// <summary>
    /// 使得目標方塊位置隨機生成
    /// </summary>
    public void GetRandomTargetPos()
    {
        //Random.insideUnitSphere:回傳半徑為1的球體內的一個隨機點
        var newTargetPos = Random.insideUnitSphere * targetSpawnRadius;
        newTargetPos.y = 5;
        target.position = newTargetPos + ground.position;
    }

在之前的版本中,Agent重置函式為AgentReset(),現在版本更名為OnEpisodeBegin()

此外,以上代碼段中可以看一下隨機產生目標物的方法,其使用了UnityEngine.Random.insideUnitSphere屬性,該值會回傳一個半徑為1的球體內的一個隨機點,除此之外,該類中還提供onUnitSphere(在球上隨機位置),insideUnitCircle(在平面圓內隨機位置)兩個屬性,

其他

	void FixedUpdate()
    {
        if (detectTargets)
        {//開啟檢測碰撞目標獎勵
            foreach (var bodyPart in m_JdController.bodyPartsDict.Values)
            {//每幀遍歷身體的每個部分,是否碰撞到目標
                if (bodyPart.targetContact && bodyPart.targetContact.touchingTarget)
                {//碰撞到目標,則獎勵1,并根據自選項重置目標位置
                    TouchedTarget();
                }
            }
        }
        
        if (useFootGroundedVisualization)
        {//是否開啟碰撞地板腳變材質功能
            foot0.material = m_JdController.bodyPartsDict[leg0Lower].groundContact.touchingGround
                ? groundedMaterial
                : unGroundedMaterial;
            foot1.material = m_JdController.bodyPartsDict[leg1Lower].groundContact.touchingGround
                ? groundedMaterial
                : unGroundedMaterial;
            foot2.material = m_JdController.bodyPartsDict[leg2Lower].groundContact.touchingGround
                ? groundedMaterial
                : unGroundedMaterial;
            foot3.material = m_JdController.bodyPartsDict[leg3Lower].groundContact.touchingGround
                ? groundedMaterial
                : unGroundedMaterial;
        }

        if (rewardMovingTowardsTarget)
        {//是否開啟速度方向與目標方向獎勵懲罰機制
            RewardFunctionMovingTowards();
        }

        if (rewardFacingTarget)
        {//是否開啟小藍前方與目標方向獎勵懲罰機制
            RewardFunctionFacingTarget();
        }

        if (rewardUseTimePenalty)
        {//是否開啟隨時間流失懲罰機制
            RewardFunctionTimePenalty();
        }
    }

	/// <summary>
    /// 計算小藍速度方向與目標方向的點積,以此來獎勵或懲罰
    /// </summary>
    void RewardFunctionMovingTowards()
    {
        m_MovingTowardsDot = Vector3.Dot(m_JdController.bodyPartsDict[body].rb.velocity, m_DirToTarget.normalized);
        AddReward(0.03f * m_MovingTowardsDot);
    }

    /// <summary>
    /// 計算小藍正向與目標方向的點積,以此來懲罰獲獎勵
    /// </summary>
    void RewardFunctionFacingTarget()
    {
        m_FacingDot = Vector3.Dot(m_DirToTarget.normalized, body.forward);
        AddReward(0.01f * m_FacingDot);
    }

    /// <summary>
    /// 隨時間流失,懲罰小藍,促使其快速完成任務
    /// </summary>
    void RewardFunctionTimePenalty()
    {
        AddReward(-0.001f);
    }

此段代碼主要是對各種自選項進行設定判斷,主要用途就是設定在什么時候給予小藍懲罰或獎勵,

image-20200531215440750

至此,我們將Crawler的主要代碼都決議了一遍,可能有一些地方沒有決議的很清楚,也算是拋磚引玉,主要借鑒一下里面的用法即可,實作還是要根據具體需求具體分析,除此之外引入了一些四元數的內容,這次也算是對四元數知識的空缺進行了一定程度的彌補,

五、訓練

在命令列中輸入以下命令:

mlagents-learn config/trainer_config.yaml --run-id=crawler_normal --train

進行訓練,如下圖:

crawler9

有點像魔鬼的步伐,,,

還記得AdjustTrainingTimescale腳本么,是設定Time.Scale的腳本,此時我們如果按數字鍵1~9,會發現Crawler的動作確實可以減慢或加速,就不做動圖了,動圖幀數一定的,也看不出來加速或減速,所以自己可以去試一下,這個值應該是確實能影響訓練速度的,我列印了一下,普通訓練的話這個值是20,Time.Scale最大為100,但是也應該不能設定的太大,太大的話會造成Update卡頓,反而影響訓練速度,不過這個是我猜的,,,

訓練一段時間就發現小藍以飛快速度奔向目標:

crawler11

得飄得飄得意的飄~

順帶附上訓練后的TesorBoard:

image-20200601185720849

可以看到,最終訓練的Cumulative Reward大概在650左右,比官方的資料400還要好很多,

放到Unity中來看一下訓練效果:

crawler12

效果和官方訓練的模型一樣,沒什么問題,

六、總結

本次的案例主要是展示對于復雜的多關節物件如何訓練,并且提及到了一些四元數的知識,歡迎大家點贊留言共同探討,

寫文不易~因此做以下申明:

1.博客中標注原創的文章,著作權歸原作者 煦陽(本博博主) 所有;

2.未經原作者允許不得轉載本文內容,否則將視為侵權;

3.轉載或者參考本文內容請注明來源及原作者;

4.對于不遵守此宣告或者其他違法使用本文內容者,本人依法保留追究權等,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/33905.html

標籤:其他

上一篇:maya

下一篇:兩個自用的Dota2 自走棋輔助工具:陣容模擬器與UI Mod插件

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more