分類
發燒車訊

從零開始搭建前後端分離的NetCore2.2(EF Core CodeFirst+Autofac)+Vue的項目框架之十二Swagger(參數)使用二

  引言

  在 中提到了 Swagger 的基本使用,僅限於沒有參數,沒有驗證的那種api文檔生成,那麼這篇就連接上篇繼續,在一般具有安全性、權限等驗證的接口上,

  都會在header/url中加上請求者的秘鑰、簽名等,當然也有可能添加到body等其它地方, Swashbuckle.AspNetCore 都支持這些寫法。

  如何使用 — 下面將介紹兩種使用方式

兩種方式參數設置到何處都是在  In屬性上,屬性對於值如下:    參考

  • query: 參数字段值對應放在url中
  • header: 參數值對應放在header param中
  • body: 參數對應放到請求體中
  • path: 參數應該對應放到請求路徑  // 具體貌似沒用
  • formData: 參數對應放到請求表單中

  第一種:將一個或多個參數保護API的“securityDefinitions”添加到生成的Swagger中。

這種是直接在文檔的右上方添加一個 Authorize 按鈕,設置了值后,每一個請求都會在設置的位置上加上相應的值,在 上一篇隨筆中的 ConfigureServices 方法中,

對應位置 services.AddSwaggerGen(options =>{}) 中的  XmlComments  下 添加代碼如下:

                options.AddSecurityDefinition("token", new ApiKeyScheme
                {
                    Description = "token format : {token}",//參數描述
                    Name = "token",//名字
                    In = "header",//對應位置
                    Type = "apiKey"//類型描述
                });
                options.AddSecurityDefinition("sid", new ApiKeyScheme
                {
                    Description = "sid format : {sid}",//參數描述
                    Name = "sid",//名字
                    In = "header",//對應位置
                    Type = "apiKey"//類型描述
                });
                //添加Jwt驗證設置 設置為全局的,不然在代碼中取不到
                options.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>> {
                    { "token", Enumerable.Empty<string>() },
                    { "sid", Enumerable.Empty<string>() },
                });

  添加完成后,運行起來看下效果,效果圖: 

 設置上對應值,調用測試方法,可以在header中取到剛設置到的值,

 這裡能看到,可以取到設置的參數了。這樣一來,在需要驗證的接口上,我們就可以通過接口文檔來測試了。基本不用再藉助postman等接口測試工具了。

但是,但是,這裡有一個問題,就是只要設置了參數值,每一次訪問都會在請求中帶上參數。

下面將介紹第二種方式,只給需要驗證用戶的接口上添加驗證參數。

  第二種:使用“filters”擴展Swagger生成器,來實現只在需要添加參數的方法上添加參數。複雜的可以根據自己的需求來添加對應參數

實現方式就是先新建一個類,名: SwaggerParameter ,實現 IOperationFilter 接口。SwaggerParameter 類代碼如下: 

    /// <summary>
    /// 自定義添加參數
    /// </summary>
    public class SwaggerParameter : IOperationFilter
    {
        /// <summary>
        /// 實現 Apply 方法
        /// </summary>
        /// <param name="operation"></param>
        /// <param name="context"></param>
        public void Apply(Operation operation, OperationFilterContext context)
        {
            if (operation.Parameters == null) operation.Parameters = new List<IParameter>();
            var attrs = context.ApiDescription.ActionDescriptor.AttributeRouteInfo;
            var t = typeof(BaseUserController);
            //先判斷是否是繼承用戶驗證類
            if (context.ApiDescription.ActionDescriptor is ControllerActionDescriptor descriptor && context.MethodInfo.DeclaringType?.IsSubclassOf(t) == true)
            {
                //再驗證是否允許匿名訪問
                var actionAttributes = descriptor.MethodInfo.GetCustomAttributes(inherit: true);
                bool isAnonymous = actionAttributes.Any(a => a is AllowAnonymousAttribute);
                // 需要驗證的方法添加
                if (!isAnonymous)
                {
                    operation.Parameters.Add(new NonBodyParameter()
                    {
                        Name = "sid",
                        In = "header", //query header body path formData
                        Type = "string",
                        Required = true,//是否必選
                        Description = "登錄返回的sid"
                    });
                    operation.Parameters.Add(new NonBodyParameter()
                    {
                        Name = "token",
                        In = "header", //query header body path formData
                        Type = "string",
                        Required = true,//是否必選
                        Description = "登錄返回的token"
                    });
                }
            }
        }
    }

 運行起來后,進入到  文檔頁面,可以看到右上角的 Authorize 按鈕已經不見了,在不需要驗證的方法上,也找不到相應需要設置參數的輸入框。就只有在需要驗證的接口上才有。

參考Swagger文檔圖如下: 

參考代碼圖如下:

 

效果圖: 

  這樣一來設置也就完成了。從上面就能看出,就只有需要用戶驗證的接口才會有相應參數。 

 

我的設置方式是先定義了用戶驗證控制器類,讓需要用戶驗證的控制器繼承該控制器,然後在該控制器中不需要用戶驗證的接口上加上 AllowAnonymous 屬性 

設置fitter時就可以根據上面提到的兩個點來進行判斷是否需要加上參數,如果不是這樣實現的,可以根據自己的需求變更fitter類,來控制文檔的生成。 

 

以上若有什麼不對或可以改進的地方,望各位指出或提出意見,一起探討學習~ 

有需要源碼的可通過此 鏈接拉取 覺得還可以的給個 start 和點個 下方的推薦哦~~謝謝!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

分類
發燒車訊

實現 Redis 協議解析器

本文是 《用 Golang 實現一個 Redis》系列文章第二篇,本文將分別介紹 以及 的實現,若您對協議有所了解可以直接閱讀協議解析器部分。

Redis 通信協議

Redis 自 2.0 版本起使用了統一的協議 RESP (REdis Serialization Protocol),該協議易於實現,計算機可以高效的進行解析且易於被人類讀懂。

RESP 是一個二進制安全的文本協議,工作於 TCP 協議上。客戶端和服務器發送的命令或數據一律以 \r\n (CRLF)結尾。

RESP 定義了5種格式:

  • 簡單字符串(Simple String): 服務器用來返回簡單的結果,比如”OK”。非二進制安全,且不允許換行。
  • 錯誤信息(Error): 服務器用來返回簡單的結果,比如”ERR Invalid Synatx”。非二進制安全,且不允許換行。
  • 整數(Integer): llenscard等命令的返回值, 64位有符號整數
  • 字符串(Bulk String): 二進制安全字符串, get 等命令的返回值
  • 數組(Array, 舊版文檔中稱 Multi Bulk Strings): Bulk String 數組,客戶端發送指令以及lrange等命令響應的格式

RESP 通過第一個字符來表示格式:

  • 簡單字符串:以”+” 開始, 如:”+OK\r\n”
  • 錯誤:以”-” 開始,如:”-ERR Invalid Synatx\r\n”
  • 整數:以”:”開始,如:”:1\r\n”
  • 字符串:以 $ 開始
  • 數組:以 * 開始

Bulk String有兩行,第一行為 $+正文長度,第二行為實際內容。如:

$3\r\nSET\r\n

Bulk String 是二進制安全的可以包含任意字節,就是說可以在 Bulk String 內部包含 “\r\n” 字符(行尾的CRLF被隱藏):

$4
a\r\nb

$-1 表示 nil, 比如使用 get 命令查詢一個不存在的key時,響應即為$-1

Array 格式第一行為 “*”+數組長度,其後是相應數量的 Bulk String。如, ["foo", "bar"]的報文:

*2
$3
foo
$3
bar

客戶端也使用 Array 格式向服務端發送指令。命令本身將作為第一個參數,如 SET key value指令的RESP報文:

*3
$3
SET
$3
key
$5
value

將換行符打印出來:

*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n

協議解析器

我們在 一文中已經介紹過TCP服務器的實現,協議解析器將實現其 Handler 接口充當應用層服務器。

協議解析器將接收 Socket 傳來的數據,並將其數據還原為 [][]byte 格式,如 "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\value\r\n" 將被還原為 ['SET', 'key', 'value']

本文完整代碼:

來自客戶端的請求均為數組格式,它在第一行中標記報文的總行數並使用CRLF作為分行符。

bufio 標準庫可以將從 reader 讀到的數據緩存到 buffer 中,直至遇到分隔符或讀取完畢后返回,所以我們使用 reader.ReadBytes('\n') 來保證每次讀取到完整的一行。

需要注意的是RESP是二進制安全的協議,它允許在正文中使用CRLF字符。舉例來說 Redis 可以正確接收並執行SET "a\r\nb" 1指令, 這條指令的正確報文是這樣的:

*3  
$3
SET
$4
a\r\nb 
$7
myvalue

ReadBytes 讀取到第五行 “a\r\nb\r\n”時會將其誤認為兩行:

*3  
$3
SET
$4
a  // 錯誤的分行
b // 錯誤的分行
$7
myvalue

因此當讀取到第四行$4后, 不應該繼續使用 ReadBytes('\n') 讀取下一行, 應使用 io.ReadFull(reader, msg) 方法來讀取指定長度的內容。

msg = make([]byte, 4 + 2) // 正文長度4 + 換行符長度2
_, err = io.ReadFull(reader, msg)

定義 Client 結構體作為客戶端抽象:

type Client struct {
    /* 與客戶端的 Tcp 連接 */
    conn   net.Conn

    /* 
     * 帶有 timeout 功能的 WaitGroup, 用於優雅關閉
     * 當響應被完整發送前保持 waiting 狀態, 阻止鏈接被關閉
     */
    waitingReply wait.Wait

    /* 標記客戶端是否正在發送指令 */ 
    sending atomic.AtomicBool
    
    /* 客戶端正在發送的參數數量, 即 Array 第一行指定的數組長度 */
    expectedArgsCount uint32
    
    /* 已經接收的參數數量, 即 len(args)*/ 
    receivedCount uint32
    
    /*
     * 已經接收到的命令參數,每個參數由一個 []byte 表示
     */
    args [][]byte
}

定義解析器:

type Handler struct {

    /* 
     * 記錄活躍的客戶端鏈接 
     * 類型為 *Client -> placeholder 
     */
    activeConn sync.Map 

    /* 數據庫引擎,執行指令並返回結果 */
    db db.DB

    /* 關閉狀態標誌位,關閉過程中時拒絕新建連接和新請求 */
    closing atomic.AtomicBool 
}

接下來可以編寫主要部分了:

func (h *Handler)Handle(ctx context.Context, conn net.Conn) {
    if h.closing.Get() {
        // 關閉過程中不接受新連接
        _ = conn.Close()
    }

    /* 初始化客戶端狀態 */
    client := &Client {
        conn:   conn,
    }
    h.activeConn.Store(client, 1)

    reader := bufio.NewReader(conn)
    var fixedLen int64 = 0 // 將要讀取的 BulkString 的正文長度
    var err error
    var msg []byte
    for {
        /* 讀取下一行數據 */ 
        if fixedLen == 0 { // 正常模式下使用 CRLF 區分數據行
            msg, err = reader.ReadBytes('\n')
            // 判斷是否以 \r\n 結尾
            if len(msg) == 0 || msg[len(msg) - 2] != '\r' {
                errReply := &reply.ProtocolErrReply{Msg:"invalid multibulk length"}
                _, _ =  client.conn.Write(errReply.ToBytes())
            }
        } else { // 當讀取到 BulkString 第二行時,根據給出的長度進行讀取
            msg = make([]byte, fixedLen + 2)
            _, err = io.ReadFull(reader, msg)
            // 判斷是否以 \r\n 結尾
            if len(msg) == 0 || 
              msg[len(msg) - 2] != '\r' ||  
              msg[len(msg) - 1] != '\n'{
                errReply := &reply.ProtocolErrReply{Msg:"invalid multibulk length"}
                _, _ =  client.conn.Write(errReply.ToBytes())
            }
            // Bulk String 讀取完畢,重新使用正常模式
            fixedLen = 0 
        }
        // 處理 IO 異常
        if err != nil {
            if err == io.EOF || err == io.ErrUnexpectedEOF {
                logger.Info("connection close")
            } else {
                logger.Warn(err)
            }
            _ = client.Close()
            h.activeConn.Delete(client)
            return // io error, disconnect with client
        }

        /* 解析收到的數據 */
        if !client.sending.Get() { 
            // sending == false 表明收到了一條新指令
            if msg[0] == '*' {
                // 讀取第一行獲取參數個數
                expectedLine, err := strconv.ParseUint(string(msg[1:len(msg)-2]), 10, 32)
                if err != nil {
                    _, _ = client.conn.Write(UnknownErrReplyBytes)
                    continue
                }
                // 初始化客戶端狀態
                client.waitingReply.Add(1) // 有指令未處理完成,阻止服務器關閉
                client.sending.Set(true) // 正在接收指令中
                // 初始化計數器和緩衝區 
                client.expectedArgsCount = uint32(expectedLine) 
                client.receivedCount = 0
                client.args = make([][]byte, expectedLine)
            } else {
                // TODO: text protocol
            }
        } else {
            // 收到了指令的剩餘部分(非首行)
            line := msg[0:len(msg)-2] // 移除換行符
            if line[0] == '$' { 
                // BulkString 的首行,讀取String長度
                fixedLen, err = strconv.ParseInt(string(line[1:]), 10, 64)
                if err != nil {
                    errReply := &reply.ProtocolErrReply{Msg:err.Error()}
                    _, _ = client.conn.Write(errReply.ToBytes())
                }
                if fixedLen <= 0 {
                    errReply := &reply.ProtocolErrReply{Msg:"invalid multibulk length"}
                    _, _ = client.conn.Write(errReply.ToBytes())
                }
            } else { 
                // 收到參數
                client.args[client.receivedCount] = line
                client.receivedCount++
            }


            // 一條命令發送完畢
            if client.receivedCount == client.expectedArgsCount {
                client.sending.Set(false)

                // 執行命令並響應
                result := h.db.Exec(client.args)
                if result != nil {
                    _, _ = conn.Write(result.ToBytes())
                } else {
                    _, _ = conn.Write(UnknownErrReplyBytes)
                }

                // 重置客戶端狀態,等待下一條指令
                client.expectedArgsCount = 0
                client.receivedCount = 0
                client.args = nil
                client.waitingReply.Done()
            }
        }
    }
}

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

分類
發燒車訊

pod刪除主要流程源碼解析

本文以v1.12版本進行分析

當一個pod刪除時,client端向apiserver發送請求,apiserver將pod的deletionTimestamp打上時間。kubelet watch到該事件,開始處理。

syncLoop

kubelet對pod的處理主要都是在syncLoop中處理的。

func (kl *Kubelet) syncLoop(updates <-chan kubetypes.PodUpdate, handler SyncHandler) {
for {
...
        if !kl.syncLoopIteration(updates, handler, syncTicker.C, housekeepingTicker.C, plegCh) {
            break
        }
...

與pod刪除主要在syncLoopIteration中需要關注的是以下這兩個。

func (kl *Kubelet) syncLoopIteration(configCh <-chan kubetypes.PodUpdate, handler SyncHandler,
    syncCh <-chan time.Time, housekeepingCh <-chan time.Time, plegCh <-chan *pleg.PodLifecycleEvent) bool {
    select {
    case u, open := <-configCh:
...
        switch u.Op {
...
        case kubetypes.UPDATE:
            handler.HandlePodUpdates(u.Pods)
...
    case <-housekeepingCh:
        if !kl.sourcesReady.AllReady() {
        } else {
            if err := handler.HandlePodCleanups(); err != nil {
                glog.Errorf("Failed cleaning pods: %v", err)
            }
        }
    }

第一個是由於發送給apiserver的DELETE請求觸發的,增加了deletionTimestamp的事件。這裏對應於kubetypes.UPDATE。也就是會走到HandlePodUpdates函數。

另外一個與delete相關的是每2s執行一次的來自於housekeepingCh的定時事件,用於清理pod,執行的是handler.HandlePodCleanups函數。這兩個作用不同,下面分別進行介紹。

HandlePodUpdates

先看HandlePodUpdates這個流程。只要打上了deletionTimestamp,就必然走到這個流程里去。

func (kl *Kubelet) HandlePodUpdates(pods []*v1.Pod) {
    for _, pod := range pods {
...
        kl.dispatchWork(pod, kubetypes.SyncPodUpdate, mirrorPod, start)
    }
}

在HandlePodUpdates中,進而將pod的信息傳遞到dispatchWork中處理。

func (kl *Kubelet) dispatchWork(pod *v1.Pod, syncType kubetypes.SyncPodType, mirrorPod *v1.Pod, start time.Time) {
    if kl.podIsTerminated(pod) {
        if pod.DeletionTimestamp != nil {
            kl.statusManager.TerminatePod(pod)
        }
        return
    }
    // Run the sync in an async worker.
    kl.podWorkers.UpdatePod(&UpdatePodOptions{
        Pod:        pod,
        MirrorPod:  mirrorPod,
        UpdateType: syncType,
        OnCompleteFunc: func(err error) {
...

這裏首先通過判斷了kl.podIsTerminated(pod)判斷pod是不是已經處於了Terminated狀態。如果是的話,則不進行下面的kl.podWorkers.UpdatePod。

func (kl *Kubelet) podIsTerminated(pod *v1.Pod) bool {
    status, ok := kl.statusManager.GetPodStatus(pod.UID)
    if !ok {
        status = pod.Status
    }
    return status.Phase == v1.PodFailed || status.Phase == v1.PodSucceeded || (pod.DeletionTimestamp != nil && notRunning(status.ContainerStatuses))
}

這個地方特別值得注意的是,並不是由了DeletionTimestamp就會認為是Terminated狀態,而是有DeletionTimestamp且所有的容器不在運行了。也就是說如果是一個正在正常運行的pod,是也會走到kl.podWorkers.UpdatePod中的。UpdatePod通過一系列函數調用,最終會通過異步的方式執行syncPod函數中進入到syncPod函數中。

func (kl *Kubelet) syncPod(o syncPodOptions) error {
...
    if !runnable.Admit || pod.DeletionTimestamp != nil || apiPodStatus.Phase == v1.PodFailed {
        var syncErr error
        if err := kl.killPod(pod, nil, podStatus, nil); err != nil {
...

在syncPod中,調用killPod從而對pod進行停止操作。

killPod

killPod是停止pod的主體。在很多地方都會使用。這裏主要介紹下起主要的工作流程。停止pod的過程主要發生在killPodWithSyncResult函數中。

func (m *kubeGenericRuntimeManager) killPodWithSyncResult(pod *v1.Pod, runningPod kubecontainer.Pod, gracePeriodOverride *int64) (result kubecontainer.PodSyncResult) {
    killContainerResults := m.killContainersWithSyncResult(pod, runningPod, gracePeriodOverride)
...
    for _, podSandbox := range runningPod.Sandboxes {
            if err := m.runtimeService.StopPodSandbox(podSandbox.ID.ID); err != nil {
...

killPodWithSyncResult的主要工作分為兩個部分。killContainersWithSyncResult負責將pod中的container停止掉,在停止后再執行StopPodSandbox。

func (m *kubeGenericRuntimeManager) killContainer(pod *v1.Pod, containerID kubecontainer.ContainerID, containerName string, reason string, gracePeriodOverride *int64) error {
    if err := m.internalLifecycle.PreStopContainer(containerID.ID); err != nil {
        return err
    }
...
    err := m.runtimeService.StopContainer(containerID.ID, gracePeriod)

killContainersWithSyncResult的主要工作是在killContainer中完成的,這裏可以看到,其中的主要兩個步驟是在容器中進行prestop的操作。待其成功后,進行container的stop工作。至此所有的應用容器都已經停止了。下一步是停止pause容器。而StopPodSandbox就是執行這一過程的。將sandbox,也就是pause容器停止掉。StopPodSandbox是在dockershim中執行的。

func (ds *dockerService) StopPodSandbox(ctx context.Context, r *runtimeapi.StopPodSandboxRequest) (*runtimeapi.StopPodSandboxResponse, error) {
...
if !hostNetwork && (ready || !ok) {
...
        err := ds.network.TearDownPod(namespace, name, cID, annotations)
...
    }
    if err := ds.client.StopContainer(podSandboxID, defaultSandboxGracePeriod); err != nil {

StopPodSandbox中主要的部分是先進行網絡卸載,再停止相應的容器。在完成StopPodSandbox后,至此pod的所有容器都已經停止完成。至於volume的卸載,是在volumeManager中進行的。本文不做單獨介紹了。停止后的容器在pod徹底清理后,會被gc回收。這裏也不展開講了。

HandlePodCleanups

上面這個流程並不是刪除流程的全部。一個典型的情況就是,如果container都不是running,那麼在UpdatePod的時候都return了,那麼又由誰來處理呢?這裏我們回到最開始,就是那個每2s執行一次的HandlePodCleanups的流程。也就是說比如container處於crash,container正好不是running等情況,其實是在這個流程里進行處理的。當然HandlePodCleanups的作用不僅僅是清理not running的pod,再比如數據已經在apiserver中強制清理掉了,或者由於其他原因這個節點上還有一些沒有完成清理的pod,都是在這個流程中進行處理。

func (kl *Kubelet) HandlePodCleanups() error {
... 
    for _, pod := range runningPods {
        if _, found := desiredPods[pod.ID]; !found {
            kl.podKillingCh <- &kubecontainer.PodPair{APIPod: nil, RunningPod: pod}
        }
    }

runningPods是從cache中獲取節點現有的pod,而desiredPods則是節點上應該存在未被停止的pod。如果存在runningPods中有而desiredPods中沒有的pod,那麼它應該被停止,所以發送到podKillingCh中。

func (kl *Kubelet) podKiller() {
...
    for podPair := range kl.podKillingCh {
...

        if !exists {
            go func(apiPod *v1.Pod, runningPod *kubecontainer.Pod) {
                glog.V(2).Infof("Killing unwanted pod %q", runningPod.Name)
                err := kl.killPod(apiPod, runningPod, nil, nil)
...
            }(apiPod, runningPod)
        }
    }
}

在podKiller流程中,會去接收來自podKillingCh的消息,從而執行killPod,上文已經做了該函數的介紹了。

statusManager

在最後,statusManager中的syncPod流程,將會進行檢測,通過canBeDeleted確認是否所有的容器關閉了,volume卸載了,cgroup清理了等等。如果這些全部完成了,則從apiserver中將pod信息徹底刪除。

func (m *manager) syncPod(uid types.UID, status versionedPodStatus) {
...
    if m.canBeDeleted(pod, status.status) {
        deleteOptions := metav1.NewDeleteOptions(0)
        deleteOptions.Preconditions = metav1.NewUIDPreconditions(string(pod.UID))
        err = m.kubeClient.CoreV1().Pods(pod.Namespace).Delete(pod.Name, deleteOptions)
...

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

分類
發燒車訊

【目標檢測實戰】目標檢測實戰之一–手把手教你LMDB格式數據集製作!

文章目錄

1 目標檢測簡介
2 lmdb數據製作
    2.1 VOC數據製作
    2.2 lmdb文件生成

lmdb格式的數據是在使用caffe進行目標檢測或分類時,使用的一種數據格式。這裏我主要以目標檢測為例講解lmdb格式數據的製作。

1 目標檢測簡介

【1】目標檢測主要有兩個任務:

  1. 判斷圖像中對象的類別
  2. 類別的位置

【2】目標檢測需要的數據:

  1. 訓練所需的圖像數據,可以是jpg、png等圖片格式
  2. 圖像數據對應的類別信息和類別框的位置信息。

2 lmdb數據製作

caffe一般使用lmdb格式的數據,在製作數據之前,我們需要對數據進行標註,可以使用labelImg對圖像進行標註(),這裏就不多贅述數據標註的問題。總之,我們得到了圖像的標註Annotations數據。lmdb數據製作,首先需要將annotations數據和圖像數據製作為VOC格式,然後將其生成LMDB文件即可。下邊是詳細的步驟:

2.1 VOC數據製作

這裏我以caffe環境的Mobilenet+YOLOv3模型的代碼為例(),進行lmdb數據製作,並且也假設你已經對其配置編譯成功(如沒成功,可以參考博文進行配置),所以我們的根目錄為:caffe-Mobilenet-YOLO-master,下邊為詳細步驟:

【1】VOC格式目錄建立:

VOC格式目錄主要包含為:

其中,Annotations里存儲的是xml標註信息,JPEGImages存儲的是圖片,ImageSets則是訓練和測試的txt列表等信息,下邊我們就要安裝如上的目錄進行建立我們自己的數據目錄。

創建Annotations、JPEGImages、ImageSets/Main等文件,命令如下(也可直接界面操作哈):

注:建議新手按照我的名稱,對於後續文件修改容易!!!

cd ~/   # 進入home目錄
cd Documents/  # 進入Documents目錄
cd caffe-Mobilenet-YOLO-master/  # 進入我們的根目錄
cd data         # 進入data目錄內
mkdir VOCdevkit   # 創建存儲我們自己的數據的文件夾
cd VOCdevkit
mkdir MyDataSet  # 創建存儲voc的目錄
cd MyDataSet   
# 創建VOC格式目錄
mkdir Annotations
mkdir JPEGImages
mkdir ImageSets
cd ImageSets
mkdir Main

好啦,我們的文件夾就建立好了,如下圖所示:

【2】將所有xml文件考入至Annotations文件夾內
【3】將所有圖片考入至JPEGImages文件夾內
【4】劃分訓練接、驗證集合測試集,如下為Python代碼,需要修改的地方註釋已標明:

import os  
import random 
# 標註文件的路徑,需要你自己修改
xmlfilepath=r'/home/Documents/caffe-Mobilenet-YOLO-master/data/VOCdevkit/MyDataSet/Annotations/'      
# 這裡是存儲數據的本目錄,需要改為你自己的目錄              
saveBasePath=r"/home/Documents/caffe-Mobilenet-YOLO-master/data/VOCdevkit/"                        
trainval_percent=0.8            # 表示訓練集和驗證集所佔比例,你需要自己修改,也可選擇不修改
train_percent=0.8               # 表示訓練集所佔訓練集驗證集的比例,你需要自己修改,也可選擇不修改       
total_xml = os.listdir(xmlfilepath)
num=len(total_xml)    
list=range(num)    
tv=int(num*trainval_percent)    
tr=int(tv*train_percent)    
trainval= random.sample(list,tv)    
train=random.sample(trainval,tr)    
  
print("train and val size",tv)  
print("traub suze",tr)  
ftrainval = open(os.path.join(saveBasePath,'MyDataSet/ImageSets/Main/trainval.txt'), 'w')    
ftest = open(os.path.join(saveBasePath,'MyDataSet/ImageSets/Main/test.txt'), 'w')    
ftrain = open(os.path.join(saveBasePath,'MyDataSet/ImageSets/Main/train.txt'), 'w')    
fval = open(os.path.join(saveBasePath,'MyDataSet/ImageSets/Main/val.txt'), 'w')    
  
for i  in list:    
    name=total_xml[i][:-4]+'\n'    
    if i in trainval:    
        ftrainval.write(name)    
        if i in train:    
            ftrain.write(name)    
        else:    
            fval.write(name)    
    else:    
        ftest.write(name)    
    
ftrainval.close()    
ftrain.close()    
fval.close()    
ftest .close() 

上述代碼修改之後,在根目錄caffe-Mobilenet-YOLO-master執行上述代碼即可,
在data/VOCdevkit/MyDataSet/ImageSets下生成trainval.txt、test.txt、train.txt、val.txt等所需的txt文件,如下圖所示:

這些TXT文件會包含圖片的名字,不帶路徑,如下圖所示:

2.2 lmdb文件生成

【1】執行如下命令,將生成lmdb所需的腳本複製至data/VOCdevkit/MyDataSet文件夾內:

cp data/VOC0712/create_* data/MyDataSet/                # 把create_list.sh和create_data.sh複製到MyDataSet目錄                  
cp data/VOC0712/labelmap_voc.prototxt data/MyDataSet/   # 把labelmap_voc.prototxt複製到MyDataSet目錄 

【2】修改create_list.sh文件:

1 第3行修改目錄路徑,截止到VOCdevkit即可

2 第13行修改為for name in MyDataSet(VOCdevkit下自己建立的文件夾名字)

3 第15-18行註釋掉

4 第41行get_image_size修改為自己的路徑(注意,這裡是build caffe_mobilenet_yolo之後才會形成的):

#!/bin/bash
# 如果嚴格安裝我上述的步驟,就可以不用修改路徑位置。
# 需要修改的位置也使用註釋進行了標註和解釋

# 這裏需要更改,你數據的根目錄位置,需要修改的地方!!!!
root_dir="/home/Documents/Caffe_Mobilenet_YOLO/data/VOCdevkit/"   
sub_dir=ImageSets/Main
bash_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
for dataset in trainval test
do
  dst_file=$bash_dir/$dataset.txt
  if [ -f $dst_file ]
  then
    rm -f $dst_file
  fi
  for name in MyDataSet  # 如果你建立的不是MyDataSet,這裏需要修改為你自己的名字
  do
    # 這裏需要修改,註釋掉即可
    #if [[ $dataset == "test" && $name == "VOC2012" ]]
    #then
    #  continue
    #fi
    echo "Create list for $name $dataset..."
    dataset_file=$root_dir/$name/$sub_dir/$dataset.txt

    img_file=$bash_dir/$dataset"_img.txt"
    cp $dataset_file $img_file
    sed -i "s/^/$name\/JPEGImages\//g" $img_file
    sed -i "s/$/.jpg/g" $img_file

    label_file=$bash_dir/$dataset"_label.txt"
    cp $dataset_file $label_file
    sed -i "s/^/$name\/Annotations\//g" $label_file
    sed -i "s/$/.xml/g" $label_file

    paste -d' ' $img_file $label_file >> $dst_file

    rm -f $label_file
    rm -f $img_file
  done

  # Generate image name and size infomation.
  if [ $dataset == "test" ]
  then
    home/Documents/Caffe_Mobilenet_YOLO/caffe-MobileNet-YOLO-master/build/tools/get_image_size $root_dir $dst_file $bash_dir/$dataset"_name_size.txt"

【3】creat_data.sh修改:

1 第2行修改為自己的路徑:root_dir=”/home/Documents/caffe-MobileNet-YOLO-master/”

2 第7行修改為:data_root_dir=”/home/Documents/caffe-MobileNet-YOLO-master/data/VOVdevkit/

3 第8行修改為:dataset_name=”MyDataSet”

4 第9行修改為:mapfile=”\(root_dir/data/VOCdevkit/\)dataset_name/labelmap_voc.prototxt”

5 第26行修改為\(root_dir/data/VOCdevkit/\)dataset_name/$subset.txt

cur_dir=$(cd $( dirname ${BASH_SOURCE[0]} ) && pwd )
# 修改為自己的路徑
root_dir="/home/Documents/Caffe_Mobilenet_YOLO/caffe-MobileNet-YOLO-master/"

cd $root_dir

redo=1
# 這裏需要修改為自己的路徑
data_root_dir="/home/Documents/Caffe_Mobilenet_YOLO/caffe-MobileNet-YOLO-master/data/VOCdevkit/"
dataset_name="MyDataSet"  # 修改為自己的名字
mapfile="$root_dir/data/VOCdevkit/$dataset_name/labelmap_voc.prototxt"  # 修改為自己的路徑
anno_type="detection"
db="lmdb"
min_dim=0
max_dim=0
width=0
height=0

extra_cmd="--encode-type=jpg --encoded"
if [ $redo ]
then
  extra_cmd="$extra_cmd --redo"
fi
for subset in test trainval
# subset.txt路徑需要修改
do
  python $root_dir/scripts/create_annoset.py --anno-type=$anno_type \
  --label-map-file=$mapfile --min-dim=$min_dim --max-dim=$max_dim --resize-width=$width \
  --resize-height=$height --check-label $extra_cmd $data_root_dir $root_dir/data/VOCdevkit/$dataset_name/$subset.txt \
  $data_root_dir/$dataset_name/$db/$dataset_name"_"$subset"_"$db examples/$dataset_name

【3】修改labelmap_voc.prototxt文件:

除了第一個背景標籤部分不要修改,其他改成自己的標籤就行,多的刪掉,少了添加進入就行

【4】最後在caffe-MobileNet-YOLO-master/examples文件夾內新建一個MyDataSet文件夾(空的)

【5】運行create_list.sh腳本: ./data/VOCdevkit/MyDataSet/create_list.sh,運行完后,會在自己建的VOCdevkit/MyDataSet/目錄內生成trainval.txt, test.txt, test_name_size.txt。

【6】運行create_data.sh腳本: ./data/VOCdevkit/MyDataSet/create_data.sh

運行此命令時,提示:bash:./data/VOCdevkit/MyDataSet/create_list.sh:Permission denied,沒有權限,需要執行如下命令賦予執行命令:

chmod u+x data/VOCdevkit/MyDataSet/create_data.sh

出現了錯誤:ValueError: need more than 2 values to unpack,

需要將create_annoset.py中第88行的seg去掉,因為我們的Annotations只有兩個值,img_file和anno。

運行完后,會在會在自己建的VOCdevkit/MyDataSet目錄內生成lmdb文件夾:

lmdb對應訓練集和測試集的lmdb格式的文件夾:

***
好啦,今天的教程就到這裏,如有疑問,可關注公眾號【計算機視覺聯盟】私信我或留言交流!!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

分類
發燒車訊

氫燃料沒未來?Honda改拚電動車,明年搶攻歐陸

本田汽車(Honda)計畫於2018年一次推出兩款全電動車,且宣告未來將以電動車作為發展主軸。

根據《BusinessInsider》報導表示,本田六月就已表示將打造中國專屬電動車,8月29日發佈的聲明稿則指明另一款電動車是為歐洲客戶所設計。

本田說,新城市電動概念車(Urban EV Concept)將在九月法蘭克福車展上亮相,這也是本田在歐洲第一款電動車,肩負打開歐洲電動車市場的任務。

目前本田僅有一款純電動車在美國上市,不過本田表示,2030年旗下三分之二的車型都要電動化,達成目標的方法將在法蘭克福車展上對外說明。

本田與日本同業豐田(Toyota)此前主要開發氫燃料車與混和動力車,但由上述可知本田策略已經轉向發展電動車,可能引領其它日本車廠跟進。

(本文內容由授權使用。圖片出處:)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

分類
發燒車訊

豐田:車輛全面由電池驅動前、須經歷2-3次技術突破

 

CNBC 9月5日報導,豐田汽車公司會長內山田竹志(Takeshi Uchiyamada)在接受專訪時表示,基於當前電池技術的侷限、他懷疑消費者會立即投向電動車的懷抱。內山田表示,豐田不是排斥電動車,但為了要提供足夠的續航力、電動車目前需要安裝許多電池並得花相當長的時間去充電,而且電池壽命也是一大問題。

內山田認為車輛全面由電池驅動之前、還須經歷2到3次的技術性突破才行。不過,他也坦承,隨著中國、美國等地鼓勵電動車發展的法令生效,汽車製造商若不推出電動車的話可能就會被淘汰出局,因此豐田已著手研發較好的電池技術。

吉利汽車控股旗下Volvo日前宣布,2019年起旗下所有新車將會是純電動或油電混合驅動。

根據DNV GL首度發布的「能源轉型展望」報告,受電動車滲透率持續上揚的影響,石油供應將在2020到2028年期間轉趨持平、隨後大幅下降,2034年將遭天然氣超越。這份報告預估電動車、內燃引擎車將在2022年達到「成本平價」,預估到2033年全球半數輕型新車銷售量都將是電動車。

特斯拉(Tesla)平價電動車「Model 3」不含獎勵計畫的售價為35,000美元起、電池續航力為345公里。

Thomson Reuters報導,嘉能可(Glencore)董事長Tony Hayward 於5月受訪時表示,電動車的快速進步意味著石油需求可能會在2040年以前觸頂,深海鑽油、加拿大油砂等高成本原油生產商恐將先被淘汰出局,擁有生產成本優勢的石油輸出國組織(OPEC)相對較不受衝擊。Hayward曾任英國石油公司(BP Plc)執行長。

嘉能可執行長Ivan Glasenberg指出,如果電動車在2035年拿下90到95%的市占率,全球年度銅需求量可望較目前的2,300萬噸呈現倍增。德國總理梅克爾(Angela Merkel)5月22日指出,鋰電池技術已經進步到可以讓電動車擁有1千公里的續航力、遠高於目前的200-300公里,德國必須大舉投資以確保產業繼續保有優勢。

戴姆勒(Daimler AG)董事長Deiter Zetsche 5月22日表示,預估到2022年旗下將有超過10款的純電動轎車系列。戴姆勒旗下全資子公司ACCUMOTIVE當日在德國卡門茨(Kamenz)為第二座電池工廠舉行奠基儀式、邀請梅克爾出席。這座工廠耗資5億歐元、預計在2018年年中正式營運。

(本文內容由授權使用。圖片出處:public domian CC0)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

分類
發燒車訊

DuckDuckGo切換到蘋果地圖

  匿名搜索引擎 DuckDuckGo 宣布地圖和地址相關的搜索將使用蘋果地圖。Google 被認為是一大隱私威脅,顯然以隱私作為主要賣點的 DuckDuckGo 不能也應該使用 Google 地圖。它可以選擇的全球地圖只剩下蘋果地圖、OpenStreetMap 和必應地圖等不多的幾個選擇。Google 地圖在功能和細節上仍然是最出色的,蘋果地圖的美國地圖足夠精細,但在其它國家它的地圖準確性就並不那麼令人滿意了。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

分類
發燒車訊

再擴大充電網路,特斯拉宣布將開始在市區設置充電站

 

除了擴建超級充電站、推出家用充電器安裝選擇,特斯拉(Tesla)近日又宣布將在美國一些大型城市內設置市區充電站,來提供給駕駛人更方便的充電選擇。

根據特斯拉透露,市區內的小型充電站將會先在芝加哥、波士頓推出,市區充電站僅能夠提供約72kW 的電力,大約是以往超級充電站120kW 功率的一半,這意味著充電時間會稍微變長,大約會需要45-50 分鐘來為汽車充電。

但在市區充電站中,電動車將不會受到超級充電站內那種「充電分流」的影響,即使另一輛車使用相鄰的充電器,雙方的充電功率也不會因為分流而降低,讓駕駛在高密度人口的城市內也能夠享受快速、實惠的充電。

 

Major increases in the Supercharger and Tesla urban charger network happening over the next several months

— Elon Musk (@elonmusk)

 

除此之外,為了讓駕駛人在汽車充電時不會無聊,特斯拉計畫將這些充電站設置在超級市場、購物中心和市中心的景點附近,讓駕駛能夠利用時間去採買或逛街。市區充電站的收費也將和超級充電站一致,價格遠比汽油的花費便宜的多。

特斯拉希望透過這些市區充電站的設置,能在家庭充電器或目的地級充電站之外,提供給駕駛一個新的選擇,並慢慢達成特斯拉遠大計劃的一部分——建立一個完整的充電網路,讓特斯拉駕駛能夠開車到達世界各地。

「我們會持續擴大充電網路,讓特斯拉駕駛無論身在何處,都能隨時找到可靠的方法取得電力。」

(合作媒體:。圖片出處:)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

分類
發燒車訊

Yelp的“嚇人”更新說明:訓練神經網絡刪除全部數據

  騰訊《一線》 紀振宇 1 月 16 日發自硅谷

  本地服務信息點評網站 Yelp 最近發生了一次可能釀成嚴重後果的運營事故:全部數據遭到刪除,幸而最終得以恢復。

  15 日,Yelp 在其移動應用的最新更新說明中稱:“我們向所有本周使用 app 過程中出現問題的用戶致歉。我們訓練了一個神經網絡用於去除應用中的所有程序缺陷,但它把所有數據都刪除了,我們只好恢復了所有數據。“

  在更新說明中公布這一事件的同時,Yelp 還不忘幽默一把,稱,“公平地說,至少短暫時間內(指所有數據被刪除后和數據恢復之前),我們是 100% 沒有 Bug 的。“

  根據 Yelp 近期應用更新說明显示, Yelp 應用最近的更新重點為去除各種漏洞、程序缺陷和改善應用功能,或許是由於程序故障排查已經成為一個常規工作,因而 Yelp 計劃訓練一套神經網絡來進行程序故障的自動排查,但卻意外發生了刪除所有數據的事故。

  Yelp 成立於 2004 年,由兩名原 Paypal 的員工創辦,被認為是本地信息服務點評的鼻祖,於 2012 年上市,目前市值約為 30 億美元。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

分類
發燒車訊

雷諾日產2022目標:推零排放電動車、降低車用鋰電池成本30%

特斯拉(Tesla Inc.)讓電動車產業起了大震盪,發展愈來愈快,雷諾日產聯盟(The Renault-Nissan Alliance)也不落人後,聲稱2020年將發表12款零排放電動車,同時還要推出機器人計程車(robo-taxi),提供無人駕駛的叫車服務。

CNBC報導,雷諾日產聯盟董事長兼執行長、三菱汽車董事長戈恩(Carlos Ghosn)15日在接受專訪時表示,集團擬定的六年新計畫「Alliance 2022」,除了要發表零排放電動車、機器人計程車之外,也會推出40款自駕程度不等的汽車款式。

戈恩說,機器人計程車主要是商業用途,許多像是Uber等業者,對此都有極高需求。另外,戈恩也打算將雷諾、日產與三菱汽車的合作綜效倍增,預估2022年聯盟的年營收,將從原本的50億歐元拉高至100億歐元;到了2022年,三大車廠的合併汽車銷售量,有望從2016年的1,000萬台、1,050萬台進一步增加至1,400萬台。
目前聯盟的主要目標,是降低車用電池的成本,希望能在2022年底前,將成本降低30%,單次充電時間壓縮到15分鐘、每次充電可行駛的里程數增加至230公里。戈恩說,集團已經評估過鋰的產能,估計未來5-6年內,應該都不會有瓶頸出現。

先前已有消息指稱,中國打算禁用、禁產傳統汽油車。新華社9月9日報導,中國工信部副部長辛國斌表示,一些國家已經制定了停止生產銷售傳統能源汽車的時間表。他說,目前工信部也啟動了相關研究、將會同相關部門制定中國的時間表。

Fortune 11日報導,目前北京當局並未對何時要進行轉換設定時間表。其他國家則打算在2030年左右逐步將汽油車淘汰:印度的目標設定在2030年、但並不是嚴格執行的時間點,至於英國、法國與挪威的預定目標則分別是2040年、2040年與2025年。

澳洲礦商Orocobre Limited最近才聲稱鋰礦需求強、報價攀高,將擴產碳酸鋰。barron`s.com、The Australian報導,Orocobre執行長Richard Seville 8月底說,全球鋰礦市場的基本面依舊穩健,不但需求強勁、供給吃緊,報價也相當具有吸引力。為了滿足特定電池夥伴的需求,Orocobre會分階段擴充Olaroz鋰礦廠的產能。

依據Orocobre計畫,位於阿根廷的Olaroz鋰礦廠,產能料將倍增。另外,該公司還會跟豐田通商(Toyota Tsusho Corporation)一同打造一座廠房,預計將年產10,000公噸的氫氧化鋰(lithium hydroxide)。

(本文內容由授權使用)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益