標籤: 南投搬家費用

  • 福特CES展新款插電式電動車 利用太陽能板充電

    福特CES展新款插電式電動車 利用太陽能板充電

    據悉,全美第2大車廠福特汽車公司(Ford Motor)準備展示1款插電式混合動力電動車,這款車透過車頂太陽能板充電。C-Max Solar Energi將在7日起於拉斯維加斯登場的2014國際消費電子展(International Consumer Electronics Show)亮相。

    C-Max純靠電力可行駛大約21英里(34公里),最遠行程620英里左右。C-Max的車頂配備SunPower公司300至350瓦的太陽能電池,預告未來可能生產不需插電的量產充電車。

    這輛車的概念包括設置類似頂篷的停車棚,運用菲涅爾透鏡(Fresnel lenses)將陽光聚焦在這輛車,提高太陽能電池的效能。這款電動車還配有與充電站接合的標準充電器。

    這款車由喬治亞理工學院(Georgia Institute of Technology)研發,可隨太陽的移動方向轉換位置。福特預估,該公司去年賣出超過8萬5000輛混合動力車及電動車。

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

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

  • angular 接入 IdentityServer4

    angular 接入 IdentityServer4

    angular 接入 IdentityServer4

    Intro

    最近把活動室預約的項目做了一個升級,預約活動室需要登錄才能預約,並用 IdentityServer4 做了一個統一的登錄註冊中心,這樣以後就可以把其他的需要用戶操作的應用統一到 IdentityServer 這裏,這樣就不需要在每個應用里都做一套用戶的機制,接入 IdentityServer 就可以了。

    目前活動室預約的服務器端和基於 angular 的客戶端已經完成了 IdentityServer 的接入,並增加了用戶的相關的一些功能,比如用戶可以查看自己的預約記錄並且可以取消自己未開始的預約,

    還有一個小程序版的客戶端暫時還未完成接入,所以小程序版目前暫時是不能夠預約的

    為什麼要寫這篇文章

    目前在網上看到很多都是基於 implicit 模式接入 IdentityServer,這樣實現起來很簡單,但是現在 OAuth 已經不推薦這樣做了,OAuth 推薦使用 code 模式來代替 implicit

    implicit 模式會有一些安全風險,implicit 模式會將 accessToken 直接返回到客戶端,而 code 模式只是會返回一個 code,accessToken 和 code 的分離的兩步,implicit 模式很有可能會將 token 泄露出去

    詳細可以參考 StackOverflow 上的這個問答

    https://stackoverflow.com/questions/13387698/why-is-there-an-authorization-code-flow-in-oauth2-when-implicit-flow-works

    除此之外,還有一個小原因,大多是直接基於 oidc-client 的 一個 npm 包來實現的,我是用了一個針對 angular 封裝的一個庫 angular-oauth2-oidc,如果你在用 angular ,建議你可以嘗試一下,針對 angular 做了一些封裝和優化,對 angular 更友好一些

    準備接入吧

    API 配置

    預約系統的 API 和網站管理系統是在一起的,針對需要登錄才能訪問的 API 單獨設置了的 policy 訪問

    services.AddAuthentication()
        .AddIdentityServerAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme, options =>
        {
            options.Authority = Configuration["Authorization:Authority"];
            options.RequireHttpsMetadata = false;
    
            options.NameClaimType = "name";
            options.RoleClaimType = "role";
        })
        ;
    
    services.AddAuthorization(options =>
    {
        options.AddPolicy("ReservationApi", builder => builder
            .AddAuthenticationSchemes(IdentityServerAuthenticationDefaults.AuthenticationScheme)
            .RequireAuthenticatedUser()
            .RequireScope("ReservationApi")
        );
    });
    

    需要授權才能訪問的接口設置 Authorize 並指定 Policy 為 ReservationApi

    [Authorize(Policy = "ReservationApi")]
    [HttpPost]
    public async Task<IActionResult> MakeReservation([FromBody] ReservationViewModel model)
    

    IdentityServer Client 配置

    首先我們需要在 IdentityServer 這邊添加一個客戶端,因為我們要使用 code 模式,所以授權類型需要配置 authorization-code 模式,不使用 implicit 模式

    允許的作用域(scope) 是客戶端允許訪問的 api 資源和用戶的信息資源,openid 必選,profile 是默認的用戶基本信息的集合,根據自己客戶端的需要進行配置,ReservationApi 是訪問 API 需要的 scope,其他的 scope 根據客戶端需要進行配置

    angular 客戶端配置

    安裝 angular-oauth2-oidc npm 包,我現在使用的是 9.2.0 版本

    添加 oidc 配置:

    export const authCodeFlowConfig: AuthConfig = {
      issuer: 'https://id.weihanli.xyz',
    
      // URL of the SPA to redirect the user to after login
      redirectUri: window.location.origin + '/account/callback',
    
      clientId: 'reservation-angular-client',
    
      dummyClientSecret: 'f6f1f917-0899-ef36-63c8-84728f411e7c',
    
      responseType: 'code',
    
      scope: 'openid profile ReservationApi offline_access',
    
      useSilentRefresh: false,
    
      showDebugInformation: true,
    
      sessionChecksEnabled: true,
    
      timeoutFactor: 0.01,
    
      // disablePKCI: true,
    
      clearHashAfterLogin: false
    };
    

    在 app.module 引入 oauth 配置

      imports: [
        BrowserModule,
        AppRoutingModule,
        AppMaterialModule,
        HttpClientModule,
        FormsModule,
        ReactiveFormsModule,
        BrowserAnimationsModule,
        OAuthModule.forRoot({
          resourceServer: {
            allowedUrls: ['https://reservation.weihanli.xyz/api'],
            sendAccessToken: true
          }
        })
      ]
    

    OAuthModule 里 resourceServer 中的 allowedUrls 是配置的資源的地址,訪問的資源符合這個地址時就會自動發送 accessToken,這樣就不需要自己實現一個 interceptor 來實現自動在請求頭中設置 accessToken 了

    在 AppComponment 的構造器中初始化 oauth 配置,並加載 ids 的發現文檔

    export class AppComponent {
      constructor(
            private oauth: OAuthService
        ) {
        this.oauth.configure(authConfig.authCodeFlowConfig);
        this.oauth.loadDiscoveryDocument();
        }
        // ...
    }
    

    添加一個 AuthGuard,路由守衛,需要登錄才能訪問的頁面自動跳轉到 /account/login 自動登錄

    AuthGuard:

    import { Injectable } from '@angular/core';
    import { CanActivate, Router } from '@angular/router';
    import { OAuthService } from 'angular-oauth2-oidc';
    
    @Injectable({
      providedIn: 'root'
    })
    export class AuthGuard implements CanActivate {
      constructor(private router: Router, private oauthService: OAuthService) {}
    
      canActivate() {
        if (this.oauthService.hasValidAccessToken()) {
          return true;
        } else {
          this.router.navigate(['/account/login']);
          return false;
        }
      }
    }
    
    

    路由配置:

    import { NgModule } from '@angular/core';
    import { Routes, RouterModule } from '@angular/router';
    import { ReservationListComponent } from './reservation/reservation-list/reservation-list.component';
    import { NoticeListComponent } from './notice/notice-list/notice-list.component';
    import { NoticeDetailComponent } from './notice/notice-detail/notice-detail.component';
    import { AboutComponent } from './about/about.component';
    import { NewReservationComponent } from './reservation/new-reservation/new-reservation.component';
    import { LoginComponent } from './account/login/login.component';
    import { AuthGuard } from './shared/auth.guard';
    import { AuthCallbackComponent } from './account/auth-callback/auth-callback.component';
    import { MyReservationComponent } from './account/my-reservation/my-reservation.component';
    
    const routes: Routes = [
      { path: '', component: ReservationListComponent },
      { path: 'reservations/new', component:NewReservationComponent, canActivate: [AuthGuard] },
      { path: 'reservations', component: ReservationListComponent },
      { path: 'notice', component: NoticeListComponent },
      { path: 'notice/:noticePath', component: NoticeDetailComponent },
      { path: 'about', component: AboutComponent },
      { path: 'account/login', component: LoginComponent },
      { path: 'account/callback', component: AuthCallbackComponent },
      { path: 'account/reservations', component: MyReservationComponent, canActivate: [AuthGuard] },
      { path: '**', redirectTo: '/'}
    ];
    
    @NgModule({
      imports: [RouterModule.forRoot(routes)],
      exports: [RouterModule]
    })
    export class AppRoutingModule { }
    

    AccountLogin 會將用戶引導到 ids 進行登錄,登錄之後會跳轉到配置的重定向 url,我配置的是 account/callback

    import { Component, OnInit } from '@angular/core';
    import { OAuthService } from 'angular-oauth2-oidc';
    
    @Component({
      selector: 'app-login',
      templateUrl: './login.component.html',
      styleUrls: ['./login.component.less']
    })
    export class LoginComponent implements OnInit {
    
      constructor(private oauthService: OAuthService) {
      }
    
      ngOnInit(): void {
        // 登錄
        this.oauthService.initLoginFlow();
      }
    
    }
    

    Auth-Callback

    import { Component, OnInit } from '@angular/core';
    import { OAuthService } from 'angular-oauth2-oidc';
    import { Router } from '@angular/router';
    
    @Component({
      selector: 'app-auth-callback',
      templateUrl: './auth-callback.component.html',
      styleUrls: ['./auth-callback.component.less']
    })
    export class AuthCallbackComponent implements OnInit {
    
      constructor(private oauthService: OAuthService, private router:Router) {
      }
    
      ngOnInit(): void {
        this.oauthService.loadDiscoveryDocumentAndTryLogin()
        .then(_=> {
          this.oauthService.loadUserProfile().then(x=>{
            this.router.navigate(['/reservations/new']);
          });
        });
      }
    
    }
    

    More

    當前實現還不太完善,重定向現在始終是跳轉到的新預約的頁面,應當在跳轉登錄之前記錄一下當前的地址保存在 storage 中,在 auth-callback 里登錄成功之後跳轉到 storage 中之前的地址

    Reference

    • https://sunnycoding.cn/2020/03/14/angular-spa-auth-with-ocelot-and-ids4-part3/#i-2
    • https://github.com/OpenReservation/angular-client
    • https://github.com/manfredsteyer/angular-oauth2-oidc/
    • https://github.com/OpenReservation/ReservationServer

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

  • 萬向李澤楷競買菲斯科 今日將出結果

    據《華爾街日報》報導,美國電動汽車品牌菲斯科(Fisker)最終將於美國當地時間2月12日在紐約進行拍賣,美國法院表示會在一個工作日後宣判結果,李澤楷控股的混合動力技術控股有限公司與萬向集團仍是最有可能的競買成功者。

    據路透社的消息,混合動力為加強此次競買工作以及公司的管理,特別聘請了曾主要負責福特歐洲業務的前高管Martin Leach。Leach表示,混合動力目前最大的困境是,萬向集團在一年前收購了A123系統公司,而A123是Fisker的主要電池供應商,不過混合動力可以得到另一家電池公司波士頓動力的支持。

    美國汽車經銷協會首席經濟學家史蒂文表示,競買的最終贏家將獲得的不僅是Fisker的汽車產品或設計,還包括其知識產權,其中涉及的36項專利(大約一半為待定),包括電氣傳動系統、太陽能等專利技術。

    據悉,萬向錢潮作為萬向集團控股的汽車零部件製造和銷售公司,其股價近日來也因受各種消息刺激連續上漲。

    萬向錢潮並於昨(12)日發佈公告表示,從2000年開展電動汽車研發以來,控股股東萬向集團一直致力於發展清潔能源產業,但與國際先進技術相比尚有一定差距。為此,萬向集團希望通過併購聯合方式提升技術能力等。此前,萬向集團掌門人魯冠球曾表示,對同特斯拉電動車結盟合作抱開放態度。

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

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

  • LeetCode 79,這道走迷宮問題為什麼不能用寬搜呢?

    LeetCode 79,這道走迷宮問題為什麼不能用寬搜呢?

    本文始發於個人公眾號:TechFlow,原創不易,求個關注

    今天是LeetCode專題第48篇文章,我們一起來看看LeetCode當中的第79題,搜索單詞(Word Search)。

    這一題官方給的難度是Medium,通過率是34.5%,點贊3488,反對170。單從這份數據上來看,這題的質量很高,並且難度比之前的題目稍稍大一些。我個人覺得通過率是比官方給的題目難得更有參考意義的指標,10%到20%可以認為是較難的題,30%左右是偏難的題。50%是偏易題,所以如果看到某題標着Hard,但是通過率有50%,要麼說明題目很水,要麼說明數據很水,總有一點很水。

    題意

    廢話不多說,我們來看題意:

    這題的題面挺有意思,給定一個二維的字符型數組,以及一個字符串,要求我們來判斷能否在二維數組當中找到一條路徑,使得這條路徑上的字符連成的字符串和給定的字符串相等?

    樣例

    board =
    [
      ['A','B','C','E'],
      ['S','F','C','S'],
      ['A','D','E','E']
    ]
    
    Given word = "ABCCED", return true.
    Given word = "SEE", return true.
    Given word = "ABCB", return false.
    

    比如第一個字符串ABCCED,我們可以在數組當中找到這樣一條路徑:

    題解

    不知道大家看到題面和這個樣例有什麼樣的感覺,如果你刷過許多題,經常思考的話,我想應該不難發現,這道題的本質其實和走迷宮問題是一樣的。

    我們拿到的這個二維的字符型數組就是一個迷宮, 我們是要在這個迷宮當中找一條“出路”。不過我們的目的不是找到終點,而是找到一條符合題意的路徑。在走迷宮問題當中,迷宮中不是每一個點都可以走的,同樣在當前問題當中,也不是每一個點都符合字符串的要求的。這兩個問題雖然題面看起來大相徑庭,但是核心的本質是一樣的。

    我們來回憶一下,走迷宮問題應該怎麼解決?

    這個答案應該已經非常確定了,當然是搜索算法。我們需要搜索解可能存在的空間去尋找存在的解,也就是說我們面臨的是一個解是否存在的問題,要麼找到解,要麼遍歷完所有的可能性發現解不存在。確定了是搜索算法之後,剩下的就簡單了,我們只有兩個選項,深度優先或者是廣度優先。

    理論上來說,一般判斷解的存在性問題,我們使用廣度優先搜索更多,因為一般來說它可以更快地找到解。但是本題當中有一個小問題是,廣度優先搜索需要在隊列當中存儲中間狀態,需要記錄地圖上行走過的信息,每有一個狀態就需要存儲一份地圖信息,這會帶來比較大的內存開銷,同樣存儲的過程也會帶來計算開銷,在這道題當中,這是不可以接受的。拷貝狀態帶來的空間消耗還是小事,關鍵是拷貝帶來的時間開銷,就足夠讓這題超時了。所以我們別無選擇,只能深度優先。

    明確了算法之後,只剩下了最後一個問題,在這個走迷宮問題當中,我們怎麼找到迷宮的入口呢?因為題目當中並沒有規定我們起始點的位置,這也不難解決,我們遍歷二維的字符數組,和字符串開頭相匹配的位置都可以作為迷宮的入口。

    最後,我們來看代碼,並沒有什麼技術含量,只是簡單的回溯法而已。

    class Solution:
        def exist(self, board: List[List[str]], word: str) -> bool:
            fx = [[0, 1], [0, -1], [1, 0], [-1, 0]]
            def dfs(x, y, l):
                if l == len(word):
                    return True
                for i in range(4):
                    nx = x + fx[i][0]
                    ny = y + fx[i][1]
                    # 出界或者是走過的時候,跳過
                    if nx < 0 or nx == n or ny < 0 or ny == m or visited[nx][ny]:
                        continue
                    if board[nx][ny] == word[l]:
                        visited[nx][ny] = 1
                        if dfs(nx, ny, l+1):
                            return True
                        visited[nx][ny] = 0
                return False
                    
            n = len(board)
            if n == 0:
                return False
            m = len(board[0])
            if m == 0:
                return False
            
            visited = [[0 for i in range(m)] for j in range(n)]
            
            for i in range(n):
                for j in range(m):
                    # 找到合法的起點
                    if board[i][j] == word[0]:
                        visited = [[0 for _ in range(m)] for _ in range(n)]
                        visited[i][j] = 1
                        if dfs(i, j, 1):
                            return True
                        
            return False
    

    總結

    如果能夠想通回溯法,並且對於回溯法的實現足夠熟悉,那麼這題的難度是不大的。實際上至今為止,我們一路刷來,已經做了好幾道回溯法的問題了,我想對你們來說,回溯法的問題應該已經小菜一碟了。

    相比於回溯法來說,我覺得更重要的是我們能夠通過分析想清楚,為什麼廣度優先搜索不行,底層核心的本質原因是什麼。這個思考的過程往往比最後的結論來得重要。

    如果喜歡本文,可以的話,請點個關注,給我一點鼓勵,也方便獲取更多文章。

    本文使用 mdnice 排版

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

  • 豐田、本田或於明年量產並銷售燃料電池車

    行駛時不會排放二氧化碳的燃料電池車(FCV)在日本一直受到企業與政府的推崇與支持。FCV目前以租賃販售為主,但自2015年起,FCV將開始針對一般消費者、企業進行販售,可望進一步加快普及。

    據日經新聞26日報導,本田汽車(Honda)將在2015年11月透過狹山工廠開始生產FCV,並將在2015年內於日美歐進行販售,年產量預估為1,000台、售價預估將壓在1,000萬日圓以下。

    本田所將生產的FCV為5人座車款,且充飽一次燃料所能行駛的距離可達約500km、為現行電動車(EV)的2倍水準。

    除了本田之外,豐田(Toyota)也將透過本社工廠生產FCV,年產量將同樣為1,000台、也同樣將在2015年內於日美歐開賣,且之後並計劃於2020年將年產量擴增至數萬台的規模。

    豐田預計在2015年開賣的FCV售價將壓在1,000萬日圓以下,且之後並計劃於2020年代將售價壓低至300-500萬日圓的水準。

    燃料電池車研發「三國鼎立」格局

    豐田汽車於2013年1月宣布將攜手德國車廠BMW研發燃料電池車。

    雷諾-日產聯盟(The Renault-Nissan Alliance)也於2013年1月宣布將攜手德國戴姆勒(Daimler)、美國福特汽車(Ford)研發燃料電池(FC)系統,以藉此大幅刪減投資成本,目標為在2017年開賣全球首款經濟實惠的量產款FCV。

    另外,本田也於2013年7月宣布,將與美國汽車大廠通用汽車(General Motors;GM)攜手研發燃料電池車(FCV),而本田預計在2015年開賣的FCV就可能使用GM的技術。

    日本政府補助建造燃料站

    據華爾街日報去年12月26日的報導,日本政府宣布,2014年4月起的會計年度,將撥款72億日圓,補助建造氫燃料站;同時也將挹注64億日圓研發如何降低燃料電池的製造成本。

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

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

  • 中國正考慮降低電動汽車進口關稅

    據中新網報道,中國政府正考慮進行電動汽車稅收改革。中國政府官員日前會見特斯拉(Tesla)首席執行官馬斯克(Musk Elon),均表示中國將支持電動汽車產業。

    科技部部長萬鋼在北京會見馬斯克時表示,中國政府正在考慮電動汽車在稅收方面的改革,比如在進口關稅方面會有別於傳統汽車的進口,但具體細則現在還在制定之中。

    工業和信息化部部長苗圩會見馬斯克時則表示,中國政府高度重視新能源汽車產業的發展,希望特斯拉公司發揮自身優勢,不斷創新,加強與中國企業的合作。

    他同時指出,中國政府正在制定政策,幫助像特斯拉一樣的企業進入中國,促進電動汽車產業在中國的發展。

    馬斯克亦表示,特斯拉公司非常重視中國市場,願加強與中國企業的合作。特斯拉已經在北京與上海建設充電站,電力來源為光伏與電網的結合,可實現24小時不間斷充電。」特斯拉未來還計畫建立超級充電網路。

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

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

  • Linux Systemd 詳細介紹: Unit、Unit File、Systemctl、Target

    Linux Systemd 詳細介紹: Unit、Unit File、Systemctl、Target

    Systemd

    簡介

    CentOS 7 使用 Systemd 替換了SysV

    Ubuntu 從 15.04 開始使用 Systemd

    Systemd 是 Linux 系統工具,用來啟動守護進程,已成為大多數發行版的標準配置

    特點

    優點:

    1. 按需啟動進程,減少系統資源消耗

    2. 并行啟動進程,提高系統啟動速度

      在 SysV-init 時代,將每個服務項目編號,依次執行啟動腳本。Ubuntu 的 Upstart 解決了沒有直接依賴的啟動之間的并行啟動。而 Systemd 通過 Socket 緩存、DBus 緩存和建立臨時掛載點等方法進一步解決了啟動進程之間的依賴,做到了所有系統服務併發啟動。對於用戶自定義的服務,Systemd 允許配置其啟動依賴項目,從而確保服務按必要的順序運行。

      SystemV Upstart 參考上一篇博文:Linux 初始化系統 SystemV Upstart

    3. 使用 CGroup 監視和管理進程的生命周期

      CGroup 提供了類似文件系統的接口,當進程創建子進程時,子進程會繼承父進程的 CGroup。因此無論服務如何啟動新的子進程,所有的這些相關進程都會屬於同一個 CGroup

      在 Systemd 之前的主流應用管理服務都是使用 進程樹 來跟蹤應用的繼承關係的,而進程的父子關係很容易通過 兩次 fork 的方法脫離。

      而 Systemd 則提供通過 CGroup 跟蹤進程關係,引補了這個缺漏。通過 CGroup 不僅能夠實現服務之間訪問隔離,限制特定應用程序對系統資源的訪問配額,還能更精確地管理服務的生命周期

    4. 統一管理服務日誌

    5. 支持快照和系統恢復

    缺點:

    1. 過於複雜,與操作系統的其他部分強耦合,違反”keep simple, keep stupid”的Unix 哲學

    架構圖

    Unit(單元|服務)

    Systemd 可以管理所有系統資源:

    1. 將系統資源劃分為12類
    2. 將每個系統資源稱為一個 Unit。Unit 是 Systemd 管理系統資源的基本單位
    3. 使用一個 Unit File 作為 Unit 的單元文件,Systemd 通過單元文件控制 Unit 的啟動

    例如,MySQL服務被 Systemd 視為一個 Unit,使用一個 mysql.service 作為啟動配置文件

    Unit File(單元文件|配置文件)

    單元文件中包含該單元的描述、屬性、啟動命令等

    類型

    Systemd 將系統資源劃分為12類,對應12種類型的單元文件

    系統資源類型 單元文件擴展名 單元文件描述
    Service .service 封裝守護進程的啟動、停止、重啟和重載操作,是最常見的一種 Unit 文件
    Target .target 定義 target 信息及依賴關係,一般僅包含 Unit 段
    Device .device 對於 /dev 目錄下的硬件設備,主要用於定義設備之間的依賴關係
    Mount .mount 定義文件系統的掛載點,可以替代過去的 /etc/fstab 配置文件
    Automount .automount 用於控制自動掛載文件系統,相當於 SysV-init 的 autofs 服務
    Path .path 用於監控指定目錄或文件的變化,並觸發其它 Unit 運行
    Scope .scope 這種 Unit 文件不是用戶創建的,而是 Systemd 運行時產生的,描述一些系統服務的分組信息
    Slice .slice 用於表示一個 CGroup 的樹
    Snapshot .snapshot 用於表示一個由 systemctl snapshot 命令創建的 Systemd Units 運行狀態快照,可以切回某個快照
    Socket .socket 監控來自於系統或網絡的數據消息
    Swap .swap 定義一個用戶做虛擬內存的交換分區
    Timer .timer 用於配置在特定時間觸發的任務,替代了 Crontab 的功能

    對於操作單元文件的命令,如果缺省擴展名,則默認.service擴展名

    而操作 target 的命令,例如 isolate,則默認.target擴展名

    語法

    單元文件的語法來源於 XDG桌面入口配置文件.desktop文件

    Unit 文件可以分為三個配置區段:

    • Unit 段:所有 Unit 文件通用,用來定義 Unit 的元數據,以及配置與其他 Unit 的關係
    • Install 段:所有 Unit 文件通用,用來定義如何啟動,以及是否開機啟動
    • Service 段:服務(Service)類型的 Unit 文件(後綴為 .service)特有的,用於定義服務的具體管理和執行動作

    單元文件中的區段名和字段名大小寫敏感

    每個區段內都是一些等號連接的鍵值對(鍵值對的等號兩側不能有空格)

    Unit 段

    主要字段如下:

    • Description:當前服務的簡單描述

    • Documentation:文檔地址,可以是一個或多個文檔的 URL 路徑

      【依賴關係】

    • Requires:與其它 Unit 的強依賴關係,如果其中任意一個 Unit 啟動失敗或異常退出,當前 Unit 也會被退出

    • Wants:與其它 Unit 的弱依賴關係,如果其中任意一個 Unit 啟動失敗或異常退出,不影響當前 Unit 繼續執行

      只涉及依賴關係,默認情況下 兩個 Unit 同時啟動

      【啟動順序】

    • After:該字段指定的 Unit 全部啟動完成以後,才會啟動當前 Unit

    • Before:該字段指定的 Unit 必須在當前 Unit 啟動完成之後再啟動

      只涉及啟動順序,不影響啟動結果和運行情況

    • Binds To:與 Requires 相似,該字段指定的 Unit 如果退出,會導致當前 Unit 停止運行

    • Part Of:一個 Bind To 作用的子集,僅在列出的 Unit 失敗或重啟時,終止或重啟當前 Unit,而不會隨列出Unit 的啟動而啟動

    http://manpages.ubuntu.com/manpages/bionic/en/man5/systemd.unit.5.html

    Install 段

    主要字段如下:

    • WantedBy:它的值是一個或多個 target,執行enable命令時,符號鏈接會放入/etc/systemd/system目錄下以 target 名 + .wants後綴構成的子目錄中
    • RequiredBy:它的值是一個或多個 target,執行enable命令時,符號鏈接會放入/etc/systemd/system目錄下以 target 名 + .required後綴構成的子目錄中
    • Alias:當前 Unit 可用於啟動的別名
    • Also:當前 Unit 被 enable/disable 時,會被同時操作的其他 Unit

    http://manpages.ubuntu.com/manpages/bionic/en/man5/systemd.unit.5.html

    Service 段

    主要字段如下:

    【啟動類型】

    • Type:定義啟動時的進程行為。它有以下幾種值。
      • Type=simple:默認值,ExecStart字段啟動的進程為主進程
        • 服務進程不會 fork,如果該服務要啟動其他服務,不要使用此類型啟動,除非該服務是 socket 激活型
      • Type=forkingExecStart字段將以fork()方式從父進程創建子進程啟動,創建後父進程會立即退出,子進程成為主進程。
        • 通常需要指定PIDFile字段,以便 Systemd 能夠跟蹤服務的主進程
        • 對於常規的守護進程(daemon),除非你確定此啟動方式無法滿足需求,使用此類型啟動即可
      • Type=oneshot:只執行一次,Systemd 會等當前服務退出,再繼續往下執行
        • 適用於只執行一項任務、隨後立即退出的服務
        • 通常需要指定RemainAfterExit=yes字段,使得 Systemd 在服務進程退出之後仍然認為服務處於激活狀態
      • Type=dbus:當前服務通過 D-Bus 信號啟動。當指定的 BusName 出現在 DBus 系統總線上時,Systemd認為服務就緒
      • Type=notify:當前服務啟動完畢會發出通知信號,通知 Systemd,然後 Systemd 再啟動其他服務
      • Type=idle:Systemd 會等到其他任務都執行完,才會啟動該服務。
        • 一種使用場合是:讓該服務的輸出,不與其他服務的輸出相混合

    【啟動行為】

    • ExecStart:啟動當前服務的命令

      ExecStart=/bin/echo execstart1
      ExecStart=
      ExecStart=/bin/echo execstart2
      

      順序執行設定的命令,把字段置空,表示清除之前的值

    • ExecStartPre:啟動當前服務之前執行的命令

    • ExecStartPost:啟動當前服務之後執行的命令

    • ExecReload:重啟當前服務時執行的命令

    • ExecStop:停止當前服務時執行的命令

    • ExecStopPost:停止當前服務之後執行的命令

    • RemainAfterExit:當前服務的所有進程都退出的時候,Systemd 仍認為該服務是激活狀態

      • 這個配置主要是提供給一些並非常駐內存,而是啟動註冊后立即退出,然後等待消息按需啟動的特殊類型服務使用的
    • TimeoutSec:定義 Systemd 停止當前服務之前等待的秒數

      注:所有的啟動設置之前,都可以加上一個連詞號(-),表示”抑制錯誤”,即發生錯誤的時候,不影響其他命令的執行。比如,EnvironmentFile=-/etc/sysconfig/sshd(注意等號後面的那個連詞號),就表示即使/etc/sysconfig/sshd文件不存在,也不會拋出錯誤。

    【重啟行為】

    • RestartSec:Systemd 重啟當前服務間隔的秒數
    • KillMode:定義 Systemd 如何停止服務,可能的值包括:
      • control-group(默認值):當前控制組裡面的所有子進程,都會被殺掉
      • process:只殺主進程(sshd 服務,推薦值)
      • mixed:主進程將收到 SIGTERM 信號,子進程收到 SIGKILL 信號
      • none:沒有進程會被殺掉,只是執行服務的 stop 命令。
    • Restart:定義何種情況 Systemd 會自動重啟當前服務,可能的值包括:
      • no(默認值):退出后不會重啟
      • on-success:只有正常退出時(退出狀態碼為0),才會重啟
      • on-failure:非正常退出時(退出狀態碼非0),包括被信號終止和超時,才會重啟(守護進程,推薦值)
      • on-abnormal:只有被信號終止和超時,才會重啟(對於允許發生錯誤退出的服務,推薦值)
      • on-abort:只有在收到沒有捕捉到的信號終止時,才會重啟
      • on-watchdog:超時退出,才會重啟
      • always:不管是什麼退出原因,總是重啟

    【上下文】

    • PIDFile:指向當前服務 PID file 的絕對路徑。

    • User:指定運行服務的用戶

    • Group:指定運行服務的用戶組

    • EnvironmentFile:指定當前服務的環境參數文件。該文件內部的key=value鍵值對,可以用$key的形式,在當前配置文件中獲取

      啟動sshd,執行的命令是/usr/sbin/sshd -D $OPTIONS,其中的變量$OPTIONS就來自EnvironmentFile字段指定的環境參數文件。

    http://manpages.ubuntu.com/manpages/bionic/en/man5/systemd.service.5.html

    佔位符

    在 Unit 文件中,有時會需要使用到一些與運行環境有關的信息,例如節點 ID、運行服務的用戶等。這些信息可以使用佔位符來表示,然後在實際運行中動態地替換為實際的值。

    詳細了解見 https://cloud.tencent.com/developer/article/1516125

    模板

    在現實中,往往有一些應用需要被複制多份運行,就會用到模板文件

    模板文件的寫法與普通單元文件基本相同,只是模板文件名是以 @ 符號結尾。例如:apache@.service

    通過模板文件啟動服務實例時,需要在其文件名的 @ 字符後面附加一個用於區分服務實例的參数字符串,通常這個參數是用於監控的端口號或控制台 TTY 編譯號

    systemctl start apache@8080.service
    

    Systemd 在運行服務時,首先尋找跟單元名完全匹配的單元文件,如果沒有找到,才會嘗試選擇匹配模板

    例如上面的命令,System 首先會在約定的目錄下尋找名為 apache@8080.service 的單元文件,如果沒有找到,而文件名中包含 @ 字符,它就會嘗試去掉後綴參數匹配模板文件。對於 apache@8080.service,Systemd 會找到 apache@.service 模板文件,並通過這個模板文件將服務實例化。

    詳細了解見 https://cloud.tencent.com/developer/article/1516125

    狀態

    systemctl list-unit-files 將會列出文件的 state,包括 static, enabled, disabled, masked, indirect

    • masked

      service軟鏈接到/dev/null

      該單元文件被禁止建立啟動鏈接

    • static

      該單元文件沒有[Install]部分(無法執行),只能作為其他配置文件的依賴

    • enabled

      已建立啟動鏈接

    • disabled

      沒建立啟動鏈接

    https://askubuntu.com/a/731674

    示例

    1. 關掉觸摸板配置文件

      Unit]
      Description=Switch-off Touchpad
      
      [Service]
      Type=oneshot
      ExecStart=/usr/bin/touchpad-off start
      ExecStop=/usr/bin/touchpad-off stop
      RemainAfterExit=yes
      
      [Install]
      WantedBy=multi-user.target
      
      • oneshot 表明這個服務只要運行一次就夠了,不需要長期運行
      • RemainAfterExit字段設為yes,表示進程退出以後,服務仍然保持執行。這樣的話,一旦使用systemctl stop命令停止服務,ExecStop指定的命令就會執行,從而重新開啟觸摸板

    Systemd 內建命令

    systemd-analyze

    Analyze and debug system manager, If no command is passed, Systemd-analyze time is implied

    https://www.freedesktop.org/software/systemd/man/systemd-analyze.html

    systemd-analyze time

    查看初始化耗時

    systemd-analyze blame

    打印所有運行單元,按它們初始化的時間排序。此信息可用於優化啟動時間。注意,輸出可能具有誤導性,因為一個服務的初始化可能非常緩慢,因為它等待另一個服務的初始化完成

    systemd-run

    將一個指定的服務變成後台服務

    未測試

    參考 https://www.freedesktop.org/software/systemd/man/systemd-run.html

    systemctl 系統服務管理命令

    systemctl是 Systemd 的主命令,用於管理系統

    與 service 命令的區別

    1. systemctl 融合了 service 和 chkconfig 的功能
    2. 在 Ubuntu18.04 中沒有自帶 chkconfig 命令;service 命令實際上重定向到 systemctl 命令
    動作 SysV Init 指令 Systemd 指令
    啟動某服務 service httpd start systemctl start httpd
    停止某服務 service httpd stop systemctl stop httpd
    重啟某服務 service httpd restart systemctl restart httpd
    檢查服務狀態 service httpd status systemctl status httpd
    刪除某服務 chkconfig –del httpd 停掉應用,刪除其配置文件
    使服務開機自啟動 chkconfig –level 5 httpd on systemctl enable httpd
    使服務開機不自啟動 chkconfig –level 5 httpd off systemctl disable httpd
    查詢服務是否開機自啟 chkconfig –list | grep httpd systemctl is-enabled httpd
    加入自定義服務 chkconfig –add test systemctl load test
    显示所有已啟動的服務 chkconfig –list systemctl list-unit-files | grep enabled

    參數

    --all

    显示加載到內存的所有單元

    --type

    -t --type=

    显示指定類型(12種類型)的單元

    --state

    --state=

    显示指定狀態的單元或單元文件

    • 單元狀態

      輸入 systemctl list-units --stateTab鍵,显示所有可用的值

    • 單元文件狀態

      另外還可以用 enabled static disabled 等systemctl list-unit-files 显示的狀態

    --failed

    --state=failed

    显示加載失敗的單元

     systemctl --failed
    
    --version

    打印 Systemd 版本

    lfp@legion:/lib/systemd/system$ systemctl --version
    Systemd 237
    +PAM +AUDIT +SELINUX +IMA +APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ +LZ4 +SECCOMP +BLKID +ELFUTILS +KMOD -IDN2 +IDN -PCRE2 default-hierarchy=hybrid
    

    單元命令

    我的理解

    • systemd 對單元的管理,不涉及單元文件自身屬性和內容
    list-units

    相當於systemctl

    列出當前已加載的單元(內存)

    默認情況下僅显示處於激活狀態(正在運行)的單元

    UNIT 單元名

    LOAD 加載狀態

    ACTIVE SUB 執行狀態(大狀態 子狀態)

    DESCRIPTION 描述

    start

    啟動單元

    systemctl start mysql.service
    
    stop

    停止單元

    systemctl stop mysql.service
    
    kill

    殺掉單元進程

    systemctl kill mysql.service
    
    reload

    不終止單元,重新加載 針對該單元的 運行配置文件,而不是 針對 systemd的 該單元的啟動配置文件

    例如啟動 MySQL 服務,reload 可以在不停止服務的情況下重載 MySQL 的配置文件 my.cnf

    restart

    重啟單元

    該單元在重啟之前擁有的資源不會被完全清空,比如文件描述符存儲設施

    systemctl reload mysql.service
    
    status

    status [unit | PID]

    显示單元或進程所屬單元的運行信息

    systemctl status mysql.service
    

    Loaded行:配置文件的位置,是否設為開機啟動

    Active行:表示正在運行

    Main PID行:主進程ID

    CGroup塊:應用的所有子進程

    日誌塊:應用的日誌

    is-active

    判斷指定的單元是否處於激活狀態

    # 默認會打印當前單元的狀態,可以通過 --quiet 參數取消打印
    lfp@legion:~$ systemctl is-active mysql
    active
    
    is-failed

    判斷指定的單元是否處於啟動失敗狀態

    lfp@legion:~$ systemctl is-failed mysql
    active
    
    list-dependencies

    查看單元之間的依賴關係

    systemctl list-dependencies graphical.target 
    systemctl list-dependencies mysql.service
    
    show

    show --property= Unit <==> show -p Unit

    显示單元所有底層參數

    lfp@legion:~$ systemctl show -p MainPID mysql
    MainPID=1061
    
    set-property

    在單元啟動的時候設置運行時的某個屬性,立即生效,並保存在磁盤中作為啟動配置

    如果添加了--runtime則重啟后失效

    並非所有的屬性都可以設置,只是 systemd.resource-control 包含的屬性

    isolate

    切換到某個 target(系統狀態),立即停止該 target 未包含的單元進程。也可以理解為切換 runlevel

    如果沒有指定擴展名,則默認.target

    只有當.target單元文件中的AllowIsolate=yes時,才能使用 isolate 切換;也可以用IgnoreOnIsolate=yes字段來拒絕使用 isolate 切換

    systemctl isolate multi-user.target
    
    cat

    显示單元配置文件的備份文件,包括插入式配置drop-ins,可以完整的看到單元服務的配置。注意這裏打印的依據是磁盤的上內容,如果用戶修改了配置文件(磁盤已修改)但是未執行daemon-reload命令(內存中未更新),那麼該命令显示的配置和實際執行情況有出入

    lfp@legion:~$ systemctl cat mysql.service 
    # /lib/systemd/system/mysql.service
    # MySQL Systemd service file
    
    [Unit]
    Description=MySQL Community Server
    ...
    
    [Install]
    WantedBy=multi-user.target
    
    [Service]
    Type=forking
    ...
    # 這段就显示的是 插入式配置 drop-in 的內容
    # /etc/systemd/system/mysql.service.d/mysql.conf
    # MySQL Systemd service file
    
    [Unit]
    # Description=MySQL Community Server conf
    
    [Service]
    # ExecStartPost=/home/lfp/bin/espeak.sh
    

    單元文件命令

    我的理解

    • systemd 對單元文件自身屬性和內容的管理
    list-unit-files

    列出所有已安裝的單元文件和它們的啟用狀態

    list-units的區別是

    • list-units 僅显示當前已加載到內存中的單元
    • list-unit-files 會讀取單元文件內容,列出所有單元,包括存在於硬盤未加載進內存的單元

    實際測試結果:

    systemctl list-unit-files 显示“348 Unit files listed”

    systemctl list-units –all 显示“405 loaded units listed”

    systemctl list-units 显示 “232 loaded units listed”

    enable

    使某個單元開機自啟動

    這會根據單元文件內容中的[Install]指定的 target 組,創建一個軟鏈接

    lfp@legion:/etc/systemd/system$ vim v2rayL.service
    # 文件內容 [Install] 段
    ......
    [Install]
    WantedBy=multi-user.target
    ......
    
    lfp@legion:/etc/systemd/system$ systemctl is-enabled v2rayL.service 
    disabled
    lfp@legion:/etc/systemd/system$ systemctl enable v2rayL.service 
    # 根據 [Install] 段指定的組,添加軟鏈接
    Created symlink /etc/systemd/system/multi-user.target.wants/v2rayL.service → /etc/systemd/system/v2rayL.service.
    lfp@legion:/etc/systemd/system$ systemctl is-enabled v2rayL.service 
    enabled
    
    disable

    取消某個單元開機自啟動設置,刪除軟鏈接

    這會刪除所有指向該單元文件的軟鏈接,不僅僅是 enable 操作創建的

    reenable

    disable 和 enable 的結合,根據單元文件內容中的 [Install] 段,重置軟鏈接

    is-enabled

    檢查某個單元是否是開機自啟動的(建立的啟動鏈接)

    lfp@legion:~$ systemctl is-enabled mysql
    enabled
    
    get-default

    獲取默認啟動 target,default-target 是指向該 target 的軟鏈接

    set-default

    設置默認啟動 target,同時修改 default-target 指向設定的 target

    systemctl set-default multi-user.target
    

    生命周期管理命令

    daemon-reload

    重新加載所有的單元文件和依賴關係

    對單元文件有修改的時候,需要執行該命令重新加載文件內容

    系統管理命令

    reboot
    systemctl reboot
    

    重啟系統(異步操作)

    it will return after the reboot operation is enqueued, without waiting for it to complete

    poweroff

    關閉系統,切斷電源(異步操作)

    halt

    僅CPU停止工作,其他硬件仍處於開機狀態(異步操作)

    suspend

    暫停系統(異步操作)

    將觸發執行suspend.target

    hibernate

    讓系統進入冬眠狀態(異步操作)

    將觸發執行hibernate.target

    目錄、文件

    /run/systemd/system/

    單元(服務)運行時生成的配置文件所在目錄
    /etc/systemd/system/

    系統或用戶自定義的配置文件,初始化過程中Systemd只執行/etc/systemd/system目錄裏面的配置文件

    /lib/systemd/system/

    軟件安裝時添加的配置文件,類似於 /etc/init.d/

    對於支持 Systemd 的程序,安裝的時候,會自動的在 /lib/systemd/system 目錄添加一個配置文件

    其他目錄都是軟鏈接

    /etc/systemd/system/default.target

    Systemd 執行的第一個單元文件,符號鏈接到默認啟動 target 對應的 .target 單元文件

    優先級

    SysV 的啟動腳本放在/etc/init.d目錄下

    Systemd 的單元文件放在/etc/systemd/system/lib/systemd/system目錄下

    當一個程序在3個目錄下都存在啟動方式時,優先級是/etc/systemd/system --> /lib/systemd/system --> /etc/init.d

    lfp@legion:/etc/init.d$ ll
    -rwxr-xr-x   1 root root  5650 5月  19 22:09 mysql*
    
    lfp@legion:/etc/systemd/system$ ll
    -rw-r--r--  1 root root  511 5月  20 01:42  mysql.service
    
    lfp@legion:/lib/systemd/system$ ll
    -rw-r--r--  1 root root   499 5月  20 01:20  mysql.service
    
    • /etc/systemd/system 裏面的同名service會覆蓋/lib/systemd/system 裏面的

      注意查看文件信息,該同名文件不能是指向 /lib/systemd/system 的軟鏈接

      軟鏈接不會覆蓋而會同步

    • 如果某個程序不存在Systemd 單元文件,那麼會執行/etc/init.d裏面的啟動腳本

    根據啟動過程, /etc/systemd/system/multi-user.target.wants/ 目錄下是很多指向 /lib/systemd/system/目錄的軟鏈接,所以兩個目錄下的單元文件會互相同步。

    如果/etc/systemd/system//etc/systemd/system/multi-user.target.wants/ 同時存在單元文件,測試發現,不管是手動啟動還是開機自啟動,使用的都是 /etc/systemd/system/ 目錄下的service單元文件

    測試
    執行/etc/init.d目錄下的腳本

    mysql 修改 mysql.servicemysql.service.bak 然後通過service mysql restart啟動/etc/init.d/mysql腳本

    下面是啟動后的一些信息

    注:在恢復mysql.service之前,需要先通過service mysql stop 利用/etc/init.d/mysql腳本中的stop結束上面的進程,否則一旦恢復,service mysql stop 執行的操作就不是 /etc/init.d/mysql腳本中的stop,無法結束上面的進程,出現命令無法正常執行的情況

    結束上面的進程,恢復mysql.service,重新啟動

    /etc/systemd/system 覆蓋測試

    未修改前,查看MySQL的狀態

    lfp@legion:~$ service mysql status
    ● mysql.service - MySQL Community Server
    	# 可以發現這裏的 mysql.service 是在 /lib/systemd/system 下面
       Loaded: loaded (/lib/systemd/system/mysql.service; enabled; vendor preset: en
       Active: active (running) since Sat 2020-04-25 18:34:30 CST; 5h 33min ago
     Main PID: 988 (mysqld)
        Tasks: 28 (limit: 4915)
       CGroup: /system.slice/mysql.service
               └─988 /usr/sbin/mysqld --daemonize --pid-file=/run/mysqld/mysqld.pid
    
    4月 25 18:34:30 legion Systemd[1]: Starting MySQL Community Server...
    4月 25 18:34:30 legion Systemd[1]: Started MySQL Community Server.
    

    將 /lib/systemd/system 下面的文件複製到 /etc/systemd/system/ 下面

    sudo cp /lib/systemd/system/mysql.service /etc/systemd/system/
    

    修改 mysql.service

    sudo vim /etc/systemd/system/mysql.service
    

    重啟 mysql.service ,系統提示需要重新加載

    lfp@legion:~$ systemctl restart mysql.service 
    Warning: The Unit file, source configuration file or drop-ins of mysql.service changed on disk. 
    Run 'systemctl daemon-reload' to reload units.
    lfp@legion:~$ systemctl daemon-reload	#  重新加載
    lfp@legion:~$ systemctl restart mysql.service # 重啟
    lfp@legion:~$ systemctl status mysql.service 
    ● mysql.service - MySQL Community Server hahahaha	# 發現這裡是修改之後的,覆蓋了 /lib/systemd/lib 中的
    #                                 這裏也可以看到加載路徑
       Loaded: loaded (/etc/systemd/system/mysql.service; enabled; vendor preset: en
       Active: active (running) since Sun 【2020-04-26】 00:47:02 CST; 5s ago
      Process: 21590 ExecStart=/usr/sbin/mysqld --daemonize --pid-file=/run/mysqld/m
      Process: 21581 ExecStartPre=/usr/share/mysql/mysql-Systemd-start pre (code=exi
     Main PID: 21592 (mysqld)
        Tasks: 27 (limit: 4915)
       CGroup: /system.slice/mysql.service
               └─21592 /usr/sbin/mysqld --daemonize --pid-file=/run/mysqld/mysqld.pi
    
    4月 26 00:47:02 legion Systemd[1]: Starting MySQL Community Server hahahaha...
    4月 26 00:47:02 legion Systemd[1
    

    Target

    兩個含義

    1. 系統的某個狀態稱為一個 target(類似於”狀態點”)

    2. 達到某個系統狀態,所需的一個或多個資源(Unit)稱為一個 target(一個 Unit 組)

      1. target是一個抽象的系統資源,不像MySQL有實體

      2. 如果一個target只包含一個Unit,那麼該 target,沒有對應的目錄,指的就是這個 Unit

        例如 hibernate.target 只包含 systemd-hibernate.service一個Unit

        如果一個target包含多個Unit,那麼該target,有對應的 xxx.target.wants 目錄,指的是目錄裏面所有的Unit

        例如 multi-user.target 包含位於/etc/systemd/system/multi-user.target.wants目錄下的多個 Unit

    target也是一個 Target 類型的系統資源,有對應的單元文件 xxx.target

    Systemd 使用 target 來劃分和管理資源(Unit),啟動(激活)某個 xxx.target 單元文件,通過執行該 target 包含的 Unit,使系統達到某種狀態

    對於狀態點的理解:

    例如,執行systemd suspend命令讓系統暫停,會觸發啟動suspend.target,然後執行裏面的systemd-suspend.service Unit,使系統達到一個暫停的狀態

    傳統的init啟動模式裏面,有 RunLevel 的概念,跟 Target 的作用很類似。不同的是,RunLevel 是互斥的,不可能多個 RunLevel 同時啟動,但是多個 Target 可以同時啟動

    啟動 target

    runlevel是 SysV init 初始化系統中的概念,在Systemd初始化系統中使用的是 Target,他們之間的映射關係是

    Runlevel Target 說明
    0 poweroff.target 關閉系統
    1 rescue.target 維護模式
    2,3,4 multi-user.target 多用戶,無圖形系統(命令行界面)
    5 graphical.target 多用戶,圖形化系統(圖形用戶界面)
    6 reboot.target 重啟系統

    啟動過程

    1. 讀入 /boot 目錄下的內核文件

    2. 內核文件加載完之後,開始執行第一個程序/sbin/init 初始化進程,由 Systemd 初始化系統引導,完成相關的初始化工作

    3. Systemd 執行default.target ,獲知設定的啟動 target

      實際上 default.target 是指向設定的啟動 target 的軟鏈接

    4. Systemd 執行啟動 target 對應的單元文件。根據單元文件中定義的依賴關係,傳遞控制權,依次執行其他 target 單元文件,同時啟動每個 target 包含的單元

      對於圖形化界面,默認 target 是 graphical,Systemd 執行位於/lib/systemd/system/ 目錄下的 graphical.target 單元文件,根據 target 單元文件中定義的依賴關係,依次啟動其他 target 單元文件以及各個 target 包含的位於/etc/systemd/system/目錄下的單元

      例如: graphical.target 的依賴關係是

      [Unit]
      Description=Graphical Interface
      Documentation=man:systemd.special(7)
      Requires=multi-user.target #
      Wants=display-manager.service #
      Conflicts=rescue.service rescue.target
      After=multi-user.target rescue.service rescue.target display-manager.service #
      AllowIsolate=yes
      

      因此,依次啟動 multi-user.target –> basic.target –> sysinit.target –> local-fs.target –>local-fs-pre.target –> …

      同時啟動每個 target 包含的位於/etc/systemd/system/目錄下的Unit

      SysV對應的 rc5.d –> /etc/init.d 目錄下的指定的腳本就不會在開機的時候執行了

    查看默認 target

    systemctl get-default

    lfp@legion:~$ runlevel
    N 5
    lfp@legion:~$ systemctl get-default
    graphical.target
    

    修改默認 target

    systemctl set-default [xxx.target]

    # Ubuntu18.04
    # 圖形用戶界面 切換 命令行界面
    sudo systemctl set-default multi-user.target
    # 命令行界面 切換 圖形用戶界面
     systemctl set-default graphical.target
     reboot
     # 命令行界面 想進入 圖形用戶界面(僅進入一次,重啟系統后仍然會進入命令行模式)
     sudo systemctl start lightdm
    

    https://ask.csdn.net/questions/695344

    https://askubuntu.com/a/788465

    其他操作

    修改配置文件

    1. 直接修改/lib/systemd/system目錄下的單元文件

      如果軟件包更新,修改會被丟棄

    2. /lib/systemd/system中的單元文件複製到/etc/systemd/system/

      如果軟件包更新,不會同步更新

    3. /etc/systemd/system/ 中添加配置(推薦)

    添加配置

    步驟:

    1. /etc/systemd/system/ 目錄下新建<單元名>.d目錄
    2. <單元名>.d目錄下,新建<單元名>.conf文件
    3. <單元名>.conf文件中修改配置

    測試:

    1. 創建目錄及文件

      # /mysql.service.d
      lfp@legion:/etc/systemd/system/mysql.service.d$ ls
      mysql.conf
      
    2. 修改配置

      # MySQL Systemd service config file
      # 不需要所有的組,僅添加需要修改的組及選項
      
      [Unit]
      Description=MySQL Community Server config test
      
      [Service]
      ExecStartPost=/home/lfp/bin/espeak.sh
      
    3. 重啟測試

      lfp@legion:/etc/systemd/system/mysql.service.d$ systemctl daemon-reload
      lfp@legion:/etc/systemd/system/mysql.service.d$ systemctl restart mysql.service
      lfp@legion:/etc/systemd/system/mysql.service.d$ systemctl status mysql.service
      lfp@legion:/etc/systemd/system/mysql.service.d$ systemctl status mysql.service
      #                                                                            描述已經被修改
      ● mysql.service - MySQL Community Server config test
         Loaded: loaded (/lib/systemd/system/mysql.service; enabled; vendor preset: enabled)
      # 加入了配置文件
        Drop-In: /etc/systemd/system/mysql.service.d
                 └─mysql.conf
         Active: active (running) since Thu 2020-05-21 20:18:02 CST; 12min ago
      # 新增的動作執行成功
        Process: 4703 ExecStartPost=/home/lfp/bin/espeak.sh (code=exited, status=0/SUCCESS)
        Process: 4672 ExecStart=/usr/sbin/mysqld --daemonize --pid-file=/run/mysqld/mysqld.pid (code=exited, status=0/SUCCESS)
        Process: 4663 ExecStartPre=/usr/share/mysql/mysql-Systemd-start pre (code=exited, status=0/SUCCESS)
       Main PID: 4674 (mysqld)
          Tasks: 27 (limit: 4915)
         CGroup: /system.slice/mysql.service
                 └─4674 /usr/sbin/mysqld --daemonize --pid-file=/run/mysqld/mysqld.pid
      
      5月 21 20:18:02 legion espeak.sh[4703]: ALSA lib pcm_route.c:867:(find_matching_chmap) Found no matching channel map
      

    添加開機啟動服務

    1. 添加啟動配置文件
    2. 通過 rc.local文件

    /lib/systemd/rc.local.service

    # 如果存在,就自動添加到 multi-user.target
    # This Unit gets pulled automatically into multi-user.target by
    # Systemd-rc-local-generator if /etc/rc.local is executable.
    [Unit]
    Description=/etc/rc.local Compatibility
    Documentation=man:systemd-rc-local-generator(8)
    ConditionFileIsExecutable=/etc/rc.local
    After=network.target
    
    [Service]
    Type=forking
    ExecStart=/etc/rc.local start
    TimeoutSec=0
    RemainAfterExit=yes
    GuessMainPID=no
    
    

    創建 rc.local 文件,賦予可執行權限,即可添加啟動命令

    sudo touch /etc/rc.local
    chmod 755 /etc/rc.local
    
    lfp@legion:/etc$ vim rc.local
    #!/bin/sh -e
    echo "test rc.local" > /usr/local/rclocal.log
    echo "rc.local `date +%Y-%m-%d-%H:%M:%S`" >/home/lfp/log/rclocal.log
    exit 0
    

    system 工具集

    hostnamectl 主機名管理命令

    hostnamectl
    hostnamectl status

    Show current system hostname and related information

    lfp@legion:/lib/systemd/system$ hostnamectl status
       Static hostname: legion
             Icon name: computer-laptop
               Chassis: laptop
            Machine ID: b28xxxxxxxx2ecafa29e
               Boot ID: 21xxxxxxxxxxxx1d3a47504d
      Operating System: Ubuntu 18.04.4 LTS
                Kernel: Linux 5.3.0-51-generic
          Architecture: x86-64
    

    journalctl 日誌管理命令

    Systemd 統一管理所有 Unit 的啟動日誌。帶來的好處就是,可以只用journalctl一個命令,查看所有日誌(內核日誌和應用日誌)。

    配置文件

    /etc/systemd/journald.conf

    日誌保存目錄

    /var/log/journal/

    默認日誌最大限製為所在文件系統容量的 10%,可通過 /etc/systemd/journald.conf 中的 SystemMaxUse 字段來指定

    該目錄是 systemd 軟件包的一部分。若被刪除,systemd 不會自動創建它,直到下次升級軟件包時重建該目錄。如果該目錄缺失,systemd 會將日誌記錄寫入 /run/systemd/journal。這意味着,系統重啟後日志將丟失。

    journalctl -u [服務名]

    查看指定單元的日誌

    journalctl -b
    • journalctl -b -0 显示本次啟動的信息
    • journalctl -b -1 显示上次啟動的信息
    • journalctl -b -2 显示上上次啟動的信息

    參考

    http://manpages.ubuntu.com/manpages/bionic/en/man1/systemctl.1.html

    http://manpages.ubuntu.com/manpages/bionic/en/man5/systemd.unit.5.html

    https://www.cnblogs.com/yingsong/p/6012180.html

    https://www.cnblogs.com/sparkdev/p/8472711.html

    http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-commands.html

    http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-part-two.html

    https://linoxide.com/linux-how-to/systemd-boot-process/

    https://cloud.tencent.com/developer/article/1516125

    https://www.cnblogs.com/sparkdev/p/8472711.html

    https://www.ibm.com/developerworks/cn/linux/1407_liuming_init3/index.html?ca=drs-

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

  • 沒有廁所的大廈──日本核廢燃料棒處理概況

    文:宋瑞文

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

  • 耐用、無害、可降解 科學家發明可預防野火的黏液

    環境資訊中心綜合外電;姜唯 編譯;林大利 審校

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

  • C# 人臉識別庫

    C# 人臉識別庫

    .NET 人臉識別庫 ViewFaceCore

    這是基於 SeetaFace6 人臉識別開發的 .NET 平台下的人臉識別庫
    這是一個使用超簡單的人臉識別庫
    這是一個基於 .NET Standard 2.0 開發的庫
    這個庫已經發布到 NuGet ,你可以一鍵集成到你的項目
    此項目可以免費商業使用

    ⭐、開源

    開源協議:Apache-2.0
    GitHub地址: ViewFaceCore
    十分感謝您的小星星

    一、示例

    示例項目地址:WinForm 攝像頭人臉檢測
    示例項目效果:

     

    二、使用

    一分鐘在你的項目里集成人臉識別

    1. 創建你的 .NET 應用

    .NET Standard >= 2.0
    .NET Core >= 2.0
    .NET Framework >= 4.6.1^2

    2. 使用 Nuget 安裝 ViewFaceCore

    • Author : View
    • Version >= 0.1.1

    此 Nuget 包會自動添加依賴的 C++ 庫,以及最精簡的識別模型。
    如果需要其它場景的識別模型,請下載 SeetaFace6 模型文件。

    3. 在項目中編寫你的代碼

    • 按照 說明 自己編寫
    • 或者參考以下代碼

    簡單的調用示例

     1 static void Main()
     2         {
     3             ViewFace viewFace = new ViewFace((str) => { Debug.WriteLine(str); }); // 初始化人臉識別類,並設置 日誌回調函數
     4             viewFace.DetectorSetting = new DetectorSetting() { FaceSize = 20, MaxWidth = 2000, MaxHeight = 2000, Threshold = 0.5 };
     5 
     6             // 系統默認使用的輕量級識別模型。如果對精度有要求,請切換到 Normal 模式;並下載需要模型文件 放入生成目錄的 model 文件夾中
     7             viewFace.FaceType = FaceType.Normal;
     8             // 系統默認使用5個人臉關鍵點。//不建議改動,除非是使用口罩模型。
     9             viewFace.MarkType = MarkType.Light;
    10 
    11             #region 識別老照片
    12             float[] oldEigenValues;
    13             Bitmap oldImg = (Bitmap)Image.FromFile(@"C:\Users\yangw\OneDrive\圖片\Camera Roll\IMG_20181103_142707.jpg"/*老圖片路徑*/); // 從文件中加載照片 // 或者視頻幀等
    14             var oldFaces = viewFace.FaceDetector(oldImg); // 檢測圖片中包含的人臉信息。(置信度、位置、大小)
    15             if (oldFaces.Length > 0) //識別到人臉
    16             {
    17                 { // 打印人臉信息
    18                     Console.WriteLine($"識別到的人臉數量:{oldFaces.Length} 。人臉信息:\n");
    19                     Console.WriteLine($"序號\t人臉置信度\t位置X\t位置Y\t寬度\t高度");
    20                     for (int i = 0; i < oldFaces.Length; i++)
    21                     {
    22                         Console.WriteLine($"{i + 1}\t{oldFaces[i].Score}\t{oldFaces[i].Location.X}\t{oldFaces[i].Location.Y}\t{oldFaces[i].Location.Width}\t{oldFaces[i].Location.Height}");
    23                     }
    24                     Console.WriteLine();
    25                 }
    26                 var oldPoints = viewFace.FaceMark(oldImg, oldFaces[0]); // 獲取 第一個人臉 的識別關鍵點。(人臉識別的關鍵點數據)
    27                 oldEigenValues = viewFace.Extract(oldImg, oldPoints); // 獲取 指定的關鍵點 的特徵值。
    28             }
    29             else { oldEigenValues = new float[0]; /*未識別到人臉*/ }
    30             #endregion
    31 
    32             #region 識別新照片
    33             float[] newEigenValues;
    34             Bitmap newImg = (Bitmap)Image.FromFile(@"C:\Users\yangw\OneDrive\圖片\Camera Roll\IMG_20181129_224339.jpg"/*新圖片路徑*/); // 從文件中加載照片 // 或者視頻幀等
    35             var newFaces = viewFace.FaceDetector(newImg); // 檢測圖片中包含的人臉信息。(置信度、位置、大小)
    36             if (newFaces.Length > 0) //識別到人臉
    37             {
    38                 { // 打印人臉信息
    39                     Console.WriteLine($"識別到的人臉數量:{newFaces.Length} 。人臉信息:\n");
    40                     Console.WriteLine($"序號\t人臉置信度\t位置X\t位置Y\t寬度\t高度");
    41                     for (int i = 0; i < newFaces.Length; i++)
    42                     {
    43                         Console.WriteLine($"{i + 1}\t{newFaces[i].Score}\t{newFaces[i].Location.X}\t{newFaces[i].Location.Y}\t{newFaces[i].Location.Width}\t{newFaces[i].Location.Height}");
    44                     }
    45                     Console.WriteLine();
    46                 }
    47                 var newPoints = viewFace.FaceMark(newImg, newFaces[0]); // 獲取 第一個人臉 的識別關鍵點。(人臉識別的關鍵點數據)
    48                 newEigenValues = viewFace.Extract(newImg, newPoints); // 獲取 指定的關鍵點 的特徵值。
    49             }
    50             else { newEigenValues = new float[0]; /*未識別到人臉*/ }
    51             #endregion
    52 
    53             try
    54             {
    55                 float similarity = viewFace.Similarity(oldEigenValues, newEigenValues); // 對比兩張照片上的數據,確認是否是同一個人。
    56                 Console.WriteLine($"閾值 = {Face.Threshold[viewFace.FaceType]}\t相似度 = {similarity}");
    57                 Console.WriteLine($"是否是同一個人:{viewFace.IsSelf(similarity)}");
    58             }
    59             catch (Exception e)
    60             { Console.WriteLine(e); }
    61 
    62             Console.ReadKey();
    63         }

    ViewFaceCore 使用示例

     

    三、說明

    命名空間:ViewFaceCore.Sharp : 人臉識別類所在的命名空間

    • 屬性說明:
     

    屬性名稱 類型 說明 默認值
    ModelPath string 獲取或設置模型路徑 [ 如非必要,請勿修改 ] ./model/
    FaceType FaceType 獲取或設置人臉類型 FaceType.Light
    MarkType MarkType 獲取或設置人臉關鍵點類型 MarkType.Light
    DetectorSetting DetectorSetting 獲取或設置人臉檢測器設置 new DetectorSetting()

     

    • 方法說明:

     

     1 using System.Drawing;
     2 using ViewFaceCore.Sharp;
     3 using ViewFaceCore.Sharp.Model;
     4 
     5 // 識別 bitmap 中的人臉,並返回人臉的信息。
     6 FaceInfo[] FaceDetector(Bitmap);
     7 
     8 // 識別 bitmap 中指定的人臉信息 info 的關鍵點坐標。
     9 FaceMarkPoint[] FaceMark(Bitmap, FaceInfo);
    10 
    11 // 提取人臉特徵值。
    12 float[] Extract(Bitmap, FaceMarkPoint[]);
    13 
    14 // 計算特徵值相似度。
    15 float Similarity(float[], float[]);
    16 
    17 // 判斷相似度是否為同一個人。
    18 bool IsSelf(float);

     

    四、實現

    此項目受到了 SeetaFaceEngine.NET 項目的啟發

    這個項目本質上來說還是調用了 SeetaFace 的 C++ 類庫來實現的人臉識別功能。針對本人遇到過的相關的類庫的使用都不太方便,而且使用的 SeetaFace 的版本較老,故萌生了自己重新開發的想法。

    本項目在開發完成之後為了方便調用,採用了 Nuget 包的形式,將所有需要的依賴以及最小識別模型一起打包。在使用時非常簡單,只需要 nuget 安裝,編寫代碼,運行即可,不需要多餘的操作。

    首先查看 SeetaFace ,已經更新到了v3(v6即v3)(上面前輩的項目是基於v1開發的),最新版本暫時沒有開源,但是可以免費商用。然後是根據以前的經驗和 SeetaFace6 文檔的指導,以及前輩的項目,做了以下操作。

    1.對SeetaFace6 的接口進行了 C++ 形式的封裝。

    目前主要實現了 人臉檢測,關鍵點提取,特徵值提取,特徵值對比幾個人臉識別中的基礎接口。有了這幾個接口,可以完整的實現一套人臉識別和驗證的流程。

    • c++封裝的接口代碼如下:
      1 #include "seeta/FaceDetector.h"
      2 #include "seeta/FaceLandmarker.h"
      3 #include "seeta/FaceRecognizer.h"
      4 
      5 #include <time.h>
      6 
      7 #define View_Api extern "C" __declspec(dllexport)
      8 
      9 using namespace std;
     10 
     11 typedef void(_stdcall* LogCallBack)(const char* logText);
     12 
     13 string modelPath = "./model/"; // 模型所在路徑
     14 LogCallBack logger = NULL; // 日誌回調函數
     15 
     16 // 打印日誌
     17 void WriteLog(string str) { if (logger != NULL) { logger(str.c_str()); } }
     18 
     19 void WriteMessage(string fanctionName, string message) { WriteLog(fanctionName + "\t Message:" + message); }
     20 void WriteModelName(string fanctionName, string modelName) { WriteLog(fanctionName + "\t Model.Name:" + modelName); }
     21 void WriteRunTime(string fanctionName, int start) { WriteLog(fanctionName + "\t Run.Time:" + to_string(clock() - start) + " ms"); }
     22 void WriteError(string fanctionName, const std::exception& e) { WriteLog(fanctionName + "\t Error:" + e.what()); }
     23 
     24 // 註冊日誌回調函數
     25 View_Api void V_SetLogFunction(LogCallBack writeLog)
     26 {
     27     logger = writeLog;
     28     WriteMessage(__FUNCDNAME__, "Successed.");
     29 }
     30 
     31 // 設置人臉模型目錄
     32 View_Api void V_SetModelPath(const char* path)
     33 {
     34     modelPath = path;
     35     WriteMessage(__FUNCDNAME__, "Model.Path:" + modelPath);
     36 }
     37 // 獲取人臉模型目錄
     38 View_Api bool V_GetModelPath(char** path)
     39 {
     40     try
     41     {
     42 #pragma warning(disable:4996)
     43         strcpy(*path, modelPath.c_str());
     44 
     45         return true;
     46     }
     47     catch (const std::exception& e)
     48     {
     49         WriteError(__FUNCDNAME__, e);
     50         return false;
     51     }
     52 }
     53 
     54 seeta::FaceDetector* v_faceDetector = NULL;
     55 
     56 // 人臉檢測結果
     57 static SeetaFaceInfoArray detectorInfos;
     58 // 人臉數量檢測器
     59 View_Api int V_DetectorSize(unsigned char* imgData, int width, int height, int channels, double faceSize = 20, double threshold = 0.9, double maxWidth = 2000, double maxHeight = 2000, int type = 0)
     60 {
     61     try {
     62         clock_t start = clock();
     63 
     64         SeetaImageData img = { width, height, channels, imgData };
     65         if (v_faceDetector == NULL) {
     66             seeta::ModelSetting setting;
     67             setting.set_device(SEETA_DEVICE_CPU);
     68             string modelName = "face_detector.csta";
     69             switch (type)
     70             {
     71             case 1: modelName = "mask_detector.csta"; break;
     72             }
     73             setting.append(modelPath + modelName);
     74             WriteModelName(__FUNCDNAME__, modelName);
     75             v_faceDetector = new seeta::FaceDetector(setting);
     76         }
     77 
     78         if (faceSize != 20) { v_faceDetector->set(seeta::FaceDetector::Property::PROPERTY_MIN_FACE_SIZE, faceSize); }
     79         if (threshold != 0.9) { v_faceDetector->set(seeta::FaceDetector::Property::PROPERTY_THRESHOLD, threshold); }
     80         if (maxWidth != 2000) { v_faceDetector->set(seeta::FaceDetector::Property::PROPERTY_MAX_IMAGE_WIDTH, maxWidth); }
     81         if (maxHeight != 2000) { v_faceDetector->set(seeta::FaceDetector::Property::PROPERTY_MAX_IMAGE_HEIGHT, maxHeight); }
     82 
     83         auto infos = v_faceDetector->detect(img);
     84         detectorInfos = infos;
     85 
     86         WriteRunTime("V_Detector", start); // 此方法已經是人臉檢測的全過程,故計時器显示為 人臉識別方法
     87         return infos.size;
     88     }
     89     catch (const std::exception& e)
     90     {
     91         WriteError(__FUNCDNAME__, e);
     92         return -1;
     93     }
     94 }
     95 // 人臉檢測器
     96 View_Api bool V_Detector(float* score, int* x, int* y, int* width, int* height)
     97 {
     98     try
     99     {
    100         //clock_t start = clock();
    101 
    102         for (int i = 0; i < detectorInfos.size; i++, detectorInfos.data++)
    103         {
    104             *score = detectorInfos.data->score;
    105             *x = detectorInfos.data->pos.x;
    106             *y = detectorInfos.data->pos.y;
    107             *width = detectorInfos.data->pos.width;
    108             *height = detectorInfos.data->pos.height;
    109             score++, x++, y++, width++, height++;
    110         }
    111         detectorInfos.data = NULL;
    112         detectorInfos.size = NULL;
    113 
    114         //WriteRunTime(__FUNCDNAME__, start); // 此方法只是將 人臉數量檢測器 獲取到的數據賦值傳遞,並不耗時。故不显示此方法的調用時間
    115         return true;
    116     }
    117     catch (const std::exception& e)
    118     {
    119         WriteError(__FUNCDNAME__, e);
    120         return false;
    121     }
    122 }
    123 
    124 
    125 seeta::FaceLandmarker* v_faceLandmarker = NULL;
    126 // 人臉關鍵點數量
    127 View_Api int V_FaceMarkSize(int type = 0)
    128 {
    129     try
    130     {
    131         clock_t start = clock();
    132 
    133         if (v_faceLandmarker == NULL) {
    134             seeta::ModelSetting setting;
    135             setting.set_device(SEETA_DEVICE_CPU);
    136             string modelName = "face_landmarker_pts68.csta";
    137             switch (type)
    138             {
    139             case 1: modelName = "face_landmarker_mask_pts5.csta"; break;
    140             case 2: modelName = "face_landmarker_pts5.csta"; break;
    141             }
    142             setting.append(modelPath + modelName);
    143             WriteModelName(__FUNCDNAME__, modelName);
    144             v_faceLandmarker = new seeta::FaceLandmarker(setting);
    145         }
    146         int size = v_faceLandmarker->number();
    147 
    148         WriteRunTime(__FUNCDNAME__, start);
    149         return size;
    150     }
    151     catch (const std::exception& e)
    152     {
    153         WriteError(__FUNCDNAME__, e);
    154         return -1;
    155     }
    156 }
    157 // 人臉關鍵點
    158 View_Api bool V_FaceMark(unsigned char* imgData, int width, int height, int channels, int x, int y, int fWidth, int fHeight, double* pointX, double* pointY, int type = 0)
    159 {
    160     try
    161     {
    162         clock_t start = clock();
    163 
    164         SeetaImageData img = { width, height, channels, imgData };
    165         SeetaRect face = { x, y, fWidth, fHeight };
    166         if (v_faceLandmarker == NULL) {
    167             seeta::ModelSetting setting;
    168             setting.set_device(SEETA_DEVICE_CPU);
    169             string modelName = "face_landmarker_pts68.csta";
    170             switch (type)
    171             {
    172             case 1: modelName = "face_landmarker_mask_pts5.csta"; break;
    173             case 2: modelName = "face_landmarker_pts5.csta"; break;
    174             }
    175             setting.append(modelPath + modelName);
    176             WriteModelName(__FUNCDNAME__, modelName);
    177             v_faceLandmarker = new seeta::FaceLandmarker(setting);
    178         }
    179         std::vector<SeetaPointF> _points = v_faceLandmarker->mark(img, face);
    180 
    181         if (!_points.empty()) {
    182             for (auto iter = _points.begin(); iter != _points.end(); iter++)
    183             {
    184                 *pointX = (*iter).x;
    185                 *pointY = (*iter).y;
    186                 pointX++;
    187                 pointY++;
    188             }
    189 
    190             WriteRunTime(__FUNCDNAME__, start);
    191             return true;
    192         }
    193         else { return false; }
    194     }
    195     catch (const std::exception& e)
    196     {
    197         WriteError(__FUNCDNAME__, e);
    198         return false;
    199     }
    200 }
    201 
    202 seeta::FaceRecognizer* v_faceRecognizer = NULL;
    203 // 獲取人臉特徵值長度
    204 View_Api int V_ExtractSize(int type = 0)
    205 {
    206     try
    207     {
    208         clock_t start = clock();
    209 
    210         if (v_faceRecognizer == NULL) {
    211             seeta::ModelSetting setting;
    212             setting.set_id(0);
    213             setting.set_device(SEETA_DEVICE_CPU);
    214             string modelName = "face_recognizer.csta";
    215             switch (type)
    216             {
    217             case 1: modelName = "face_recognizer_mask.csta"; break;
    218             case 2: modelName = "face_recognizer_light.csta"; break;
    219             }
    220             setting.append(modelPath + modelName);
    221             WriteModelName(__FUNCDNAME__, modelName);
    222             v_faceRecognizer = new seeta::FaceRecognizer(setting);
    223         }
    224         int length = v_faceRecognizer->GetExtractFeatureSize();
    225 
    226         WriteRunTime(__FUNCDNAME__, start);
    227         return length;
    228     }
    229     catch (const std::exception& e)
    230     {
    231         WriteError(__FUNCDNAME__, e);
    232         return -1;
    233     }
    234 }
    235 // 提取人臉特徵值
    236 View_Api bool V_Extract(unsigned char* imgData, int width, int height, int channels, SeetaPointF* points, float* features, int type = 0)
    237 {
    238     try
    239     {
    240         clock_t start = clock();
    241 
    242         SeetaImageData img = { width, height, channels, imgData };
    243         if (v_faceRecognizer == NULL) {
    244             seeta::ModelSetting setting;
    245             setting.set_id(0);
    246             setting.set_device(SEETA_DEVICE_CPU);
    247             string modelName = "face_recognizer.csta";
    248             switch (type)
    249             {
    250             case 1: modelName = "face_recognizer_mask.csta"; break;
    251             case 2: modelName = "face_recognizer_light.csta"; break;
    252             }
    253             setting.append(modelPath + modelName);
    254             WriteModelName(__FUNCDNAME__, modelName);
    255             v_faceRecognizer = new seeta::FaceRecognizer(setting);
    256         }
    257         int length = v_faceRecognizer->GetExtractFeatureSize();
    258         std::shared_ptr<float> _features(new float[v_faceRecognizer->GetExtractFeatureSize()], std::default_delete<float[]>());
    259         v_faceRecognizer->Extract(img, points, _features.get());
    260 
    261         for (int i = 0; i < length; i++)
    262         {
    263             *features = _features.get()[i];
    264             features++;
    265         }
    266 
    267         WriteRunTime(__FUNCDNAME__, start);
    268         return true;
    269 
    270     }
    271     catch (const std::exception& e)
    272     {
    273         WriteError(__FUNCDNAME__, e);
    274         return false;
    275     }
    276 }
    277 // 人臉特徵值相似度計算
    278 View_Api float V_CalculateSimilarity(float* leftFeatures, float* rightFeatures, int type = 0)
    279 {
    280     try
    281     {
    282         clock_t start = clock();
    283 
    284         if (v_faceRecognizer == NULL) {
    285             seeta::ModelSetting setting;
    286             setting.set_id(0);
    287             setting.set_device(SEETA_DEVICE_CPU);
    288             string modelName = "face_recognizer.csta";
    289             switch (type)
    290             {
    291             case 1: modelName = "face_recognizer_mask.csta"; break;
    292             case 2: modelName = "face_recognizer_light.csta"; break;
    293             }
    294             setting.append(modelPath + modelName);
    295             WriteModelName(__FUNCDNAME__, modelName);
    296             v_faceRecognizer = new seeta::FaceRecognizer(setting);
    297         }
    298 
    299         auto similarity = v_faceRecognizer->CalculateSimilarity(leftFeatures, rightFeatures);
    300         WriteMessage(__FUNCDNAME__, "Similarity = " + to_string(similarity));
    301         WriteRunTime(__FUNCDNAME__, start);
    302         return similarity;
    303     }
    304     catch (const std::exception& e)
    305     {
    306         WriteError(__FUNCDNAME__, e);
    307         return -1;
    308     }
    309 }
    310 
    311 // 釋放資源
    312 View_Api void V_Dispose()
    313 {
    314     if (v_faceDetector != NULL) delete v_faceDetector;
    315     if (v_faceLandmarker != NULL) delete v_faceLandmarker;
    316     if (v_faceRecognizer != NULL) delete v_faceRecognizer;
    317 }

    C++ 封裝層

    2.採用 C# 對上訴接口進行了導入。

    因為C++的項目測CPU架構區分x86和x64,所以C# 層也需要區分架構封裝

    using System.Runtime.InteropServices;
    using System.Text;
    using ViewFaceCore.Sharp.Model;
    
    namespace ViewFaceCore.Plus
    {
        /// <summary>
        /// 日誌回調函數
        /// </summary>
        /// <param name="logText"></param>
        public delegate void LogCallBack(string logText);
    
        class ViewFacePlus64
        {
            const string LibraryPath = @"FaceLibraries\x64\ViewFace.dll";
            /// <summary>
            /// 設置日誌回調函數(用於日誌打印)
            /// </summary>
            /// <param name="writeLog"></param>
            [DllImport(LibraryPath, EntryPoint = "V_SetLogFunction", CallingConvention = CallingConvention.Cdecl)]
            public static extern void SetLogFunction(LogCallBack writeLog);
    
            /// <summary>
            /// 設置人臉模型的目錄
            /// </summary>
            /// <param name="path"></param>
            [DllImport(LibraryPath, EntryPoint = "V_SetModelPath", CallingConvention = CallingConvention.Cdecl)]
            private extern static void SetModelPath(byte[] path);
            /// <summary>
            /// 設置人臉模型的目錄
            /// </summary>
            /// <param name="path"></param>
            public static void SetModelPath(string path) => SetModelPath(Encoding.UTF8.GetBytes(path));
    
            /// <summary>
            /// 釋放使用的資源
            /// </summary>
            [DllImport(LibraryPath, EntryPoint = "V_Dispose", CallingConvention = CallingConvention.Cdecl)]
            public extern static void ViewDispose();
    
            /// <summary>
            /// 獲取人臉模型的目錄
            /// </summary>
            /// <param name="path"></param>
            [DllImport(LibraryPath, EntryPoint = "V_GetModelPath", CallingConvention = CallingConvention.Cdecl)]
            private extern static bool GetModelPathEx(ref string path);
            /// <summary>
            /// 獲取人臉模型的目錄
            /// </summary>
            public static string GetModelPath() { string path = string.Empty; GetModelPathEx(ref path); return path; }
    
            /// <summary>
            /// 人臉檢測器檢測到的人臉數量
            /// </summary>
            /// <param name="imgData"></param>
            /// <param name="width"></param>
            /// <param name="height"></param>
            /// <param name="channels"></param>
            /// <param name="faceSize">最小人臉是人臉檢測器常用的一個概念,默認值為20,單位像素。
            /// <para>最小人臉和檢測器性能息息相關。主要方面是速度,使用建議上,我們建議在應用範圍內,這個值設定的越大越好。SeetaFace採用的是BindingBox Regresion的方式訓練的檢測器。如果最小人臉參數設置為80的話,從檢測能力上,可以將原圖縮小的原來的1/4,這樣從計算複雜度上,能夠比最小人臉設置為20時,提速到16倍。</para>
            /// </param>
            /// <param name="threshold">檢測器閾值默認值是0.9,合理範圍為[0, 1]。這個值一般不進行調整,除了用來處理一些極端情況。這個值設置的越小,漏檢的概率越小,同時誤檢的概率會提高</param>
            /// <param name="maxWidth">可檢測的圖像最大寬度。默認值2000。</param>
            /// <param name="maxHeight">可檢測的圖像最大高度。默認值2000。</param>
            /// <param name="type"></param>
            /// <returns></returns>
            [DllImport(LibraryPath, EntryPoint = "V_DetectorSize", CallingConvention = CallingConvention.Cdecl)]
            public extern static int DetectorSize(byte[] imgData, int width, int height, int channels, double faceSize = 20, double threshold = 0.9, double maxWidth = 2000, double maxHeight = 2000, int type = 0);
            /// <summary>
            /// 人臉檢測器
            /// <para>調用此方法前必須先調用 <see cref="DetectorSize(byte[], int, int, int, double, double, double, double, int)"/></para>
            /// </summary>
            /// <param name="score">人臉置信度集合</param>
            /// <param name="x">人臉位置集合</param>
            /// <param name="y">人臉位置集合</param>
            /// <param name="width">人臉大小集合</param>
            /// <param name="height">人臉大小集合</param>
            /// <returns></returns>
            [DllImport(LibraryPath, EntryPoint = "V_Detector", CallingConvention = CallingConvention.Cdecl)]
            public extern static bool Detector(float[] score, int[] x, int[] y, int[] width, int[] height);
    
            /// <summary>
            /// 人臉關鍵點數量
            /// </summary>
            /// <returns></returns>
            [DllImport(LibraryPath, EntryPoint = "V_FaceMarkSize", CallingConvention = CallingConvention.Cdecl)]
            public extern static int FaceMarkSize(int type = 0);
            /// <summary>
            /// 人臉關鍵點
            /// </summary>
            /// <param name="imgData"></param>
            /// <param name="width"></param>
            /// <param name="height"></param>
            /// <param name="channels"></param>
            /// <param name="x"></param>
            /// <param name="y"></param>
            /// <param name="fWidth"></param>
            /// <param name="fHeight"></param>
            /// <param name="pointX"></param>
            /// <param name="pointY"></param>
            /// <param name="type"></param>
            /// <returns></returns>
            [DllImport(LibraryPath, EntryPoint = "V_FaceMark", CallingConvention = CallingConvention.Cdecl)]
            public extern static bool FaceMark(byte[] imgData, int width, int height, int channels, int x, int y, int fWidth, int fHeight, double[] pointX, double[] pointY, int type = 0);
    
            /// <summary>
            /// 提取特徵值
            /// </summary>
            /// <param name="imgData"></param>
            /// <param name="width"></param>
            /// <param name="height"></param>
            /// <param name="channels"></param>
            /// <param name="points"></param>
            /// <param name="features"></param>
            /// <param name="type"></param>
            /// <returns></returns>
            [DllImport(LibraryPath, EntryPoint = "V_Extract", CallingConvention = CallingConvention.Cdecl)]
            public extern static bool Extract(byte[] imgData, int width, int height, int channels, FaceMarkPoint[] points, float[] features, int type = 0);
            /// <summary>
            /// 特徵值大小
            /// </summary>
            /// <returns></returns>
            [DllImport(LibraryPath, EntryPoint = "V_ExtractSize", CallingConvention = CallingConvention.Cdecl)]
            public extern static int ExtractSize(int type = 0);
    
            /// <summary>
            /// 計算相似度
            /// </summary>
            /// <param name="leftFeatures"></param>
            /// <param name="rightFeatures"></param>
            /// <param name="type"></param>
            /// <returns></returns>
            [DllImport(LibraryPath, EntryPoint = "V_CalculateSimilarity", CallingConvention = CallingConvention.Cdecl)]
            public extern static float Similarity(float[] leftFeatures, float[] rightFeatures, int type = 0);
        }
        class ViewFacePlus32
        {
            const string LibraryPath = @"FaceLibraries\x86\ViewFace.dll";
            /// <summary>
            /// 設置日誌回調函數(用於日誌打印)
            /// </summary>
            /// <param name="writeLog"></param>
            [DllImport(LibraryPath, EntryPoint = "V_SetLogFunction", CallingConvention = CallingConvention.Cdecl)]
            public static extern void SetLogFunction(LogCallBack writeLog);
    
            /// <summary>
            /// 設置人臉模型的目錄
            /// </summary>
            /// <param name="path"></param>
            [DllImport(LibraryPath, EntryPoint = "V_SetModelPath", CallingConvention = CallingConvention.Cdecl)]
            private extern static void SetModelPath(byte[] path);
            /// <summary>
            /// 設置人臉模型的目錄
            /// </summary>
            /// <param name="path"></param>
            public static void SetModelPath(string path) => SetModelPath(Encoding.UTF8.GetBytes(path));
    
            /// <summary>
            /// 釋放使用的資源
            /// </summary>
            [DllImport(LibraryPath, EntryPoint = "V_Dispose", CallingConvention = CallingConvention.Cdecl)]
            public extern static void ViewDispose();
    
            /// <summary>
            /// 獲取人臉模型的目錄
            /// </summary>
            /// <param name="path"></param>
            [DllImport(LibraryPath, EntryPoint = "V_GetModelPath", CallingConvention = CallingConvention.Cdecl)]
            private extern static bool GetModelPathEx(ref string path);
            /// <summary>
            /// 獲取人臉模型的目錄
            /// </summary>
            public static string GetModelPath() { string path = string.Empty; GetModelPathEx(ref path); return path; }
    
            /// <summary>
            /// 人臉檢測器檢測到的人臉數量
            /// </summary>
            /// <param name="imgData"></param>
            /// <param name="width"></param>
            /// <param name="height"></param>
            /// <param name="channels"></param>
            /// <param name="faceSize">最小人臉是人臉檢測器常用的一個概念,默認值為20,單位像素。
            /// <para>最小人臉和檢測器性能息息相關。主要方面是速度,使用建議上,我們建議在應用範圍內,這個值設定的越大越好。SeetaFace採用的是BindingBox Regresion的方式訓練的檢測器。如果最小人臉參數設置為80的話,從檢測能力上,可以將原圖縮小的原來的1/4,這樣從計算複雜度上,能夠比最小人臉設置為20時,提速到16倍。</para>
            /// </param>
            /// <param name="threshold">檢測器閾值默認值是0.9,合理範圍為[0, 1]。這個值一般不進行調整,除了用來處理一些極端情況。這個值設置的越小,漏檢的概率越小,同時誤檢的概率會提高</param>
            /// <param name="maxWidth">可檢測的圖像最大寬度。默認值2000。</param>
            /// <param name="maxHeight">可檢測的圖像最大高度。默認值2000。</param>
            /// <param name="type"></param>
            /// <returns></returns>
            [DllImport(LibraryPath, EntryPoint = "V_DetectorSize", CallingConvention = CallingConvention.Cdecl)]
            public extern static int DetectorSize(byte[] imgData, int width, int height, int channels, double faceSize = 20, double threshold = 0.9, double maxWidth = 2000, double maxHeight = 2000, int type = 0);
            /// <summary>
            /// 人臉檢測器
            /// <para>調用此方法前必須先調用 <see cref="DetectorSize(byte[], int, int, int, double, double, double, double, int)"/></para>
            /// </summary>
            /// <param name="score">人臉置信度集合</param>
            /// <param name="x">人臉位置集合</param>
            /// <param name="y">人臉位置集合</param>
            /// <param name="width">人臉大小集合</param>
            /// <param name="height">人臉大小集合</param>
            /// <returns></returns>
            [DllImport(LibraryPath, EntryPoint = "V_Detector", CallingConvention = CallingConvention.Cdecl)]
            public extern static bool Detector(float[] score, int[] x, int[] y, int[] width, int[] height);
    
            /// <summary>
            /// 人臉關鍵點數量
            /// </summary>
            /// <returns></returns>
            [DllImport(LibraryPath, EntryPoint = "V_FaceMarkSize", CallingConvention = CallingConvention.Cdecl)]
            public extern static int FaceMarkSize(int type = 0);
            /// <summary>
            /// 人臉關鍵點
            /// </summary>
            /// <param name="imgData"></param>
            /// <param name="width"></param>
            /// <param name="height"></param>
            /// <param name="channels"></param>
            /// <param name="x"></param>
            /// <param name="y"></param>
            /// <param name="fWidth"></param>
            /// <param name="fHeight"></param>
            /// <param name="pointX"></param>
            /// <param name="pointY"></param>
            /// <param name="type"></param>
            /// <returns></returns>
            [DllImport(LibraryPath, EntryPoint = "V_FaceMark", CallingConvention = CallingConvention.Cdecl)]
            public extern static bool FaceMark(byte[] imgData, int width, int height, int channels, int x, int y, int fWidth, int fHeight, double[] pointX, double[] pointY, int type = 0);
    
            /// <summary>
            /// 提取特徵值
            /// </summary>
            /// <param name="imgData"></param>
            /// <param name="width"></param>
            /// <param name="height"></param>
            /// <param name="channels"></param>
            /// <param name="points"></param>
            /// <param name="features"></param>
            /// <param name="type"></param>
            /// <returns></returns>
            [DllImport(LibraryPath, EntryPoint = "V_Extract", CallingConvention = CallingConvention.Cdecl)]
            public extern static bool Extract(byte[] imgData, int width, int height, int channels, FaceMarkPoint[] points, float[] features, int type = 0);
            /// <summary>
            /// 特徵值大小
            /// </summary>
            /// <returns></returns>
            [DllImport(LibraryPath, EntryPoint = "V_ExtractSize", CallingConvention = CallingConvention.Cdecl)]
            public extern static int ExtractSize(int type = 0);
    
            /// <summary>
            /// 計算相似度
            /// </summary>
            /// <param name="leftFeatures"></param>
            /// <param name="rightFeatures"></param>
            /// <param name="type"></param>
            /// <returns></returns>
            [DllImport(LibraryPath, EntryPoint = "V_CalculateSimilarity", CallingConvention = CallingConvention.Cdecl)]
            public extern static float Similarity(float[] leftFeatures, float[] rightFeatures, int type = 0);
        }
    }

    C# 導入層

    3.採用 C# 的面向對象的封裝

    因為C#的項目默認都是 AnyCPU,所以為了簡化調用,在這一層封裝的時候增加了架構判斷,當在你的項目中引用的時候,不用做任何修改。

    且因為C++的C#導入方法在和原生的C#寫法略有差異,且數據的轉換和傳遞比較麻煩,所以類庫中對外隱藏了 C# 導入層。並使用大家都更熟悉的C#的面向對象的方式進行進一步的封裝和簡化。

      1     /// <summary>
      2     /// 人臉識別類
      3     /// </summary>
      4     public class ViewFace
      5     {
      6         bool Platform64 { get; set; } = false;
      7         // <para>需要模型:<see langword=""/></para>
      8 
      9         // ctor
     10         /// <summary>
     11         /// 使用默認的模型目錄初始化人臉識別類
     12         /// </summary>
     13         public ViewFace() : this("./model/") { }
     14         /// <summary>
     15         /// 使用指定的模型目錄初始化人臉識別類
     16         /// </summary>
     17         /// <param name="modelPath">模型目錄</param>
     18         public ViewFace(string modelPath)
     19         {
     20             Platform64 = IntPtr.Size == 8;
     21             if (Platform64)
     22             { ViewFacePlus64.SetModelPath(modelPath); }
     23             else
     24             { ViewFacePlus32.SetModelPath(modelPath); }
     25         }
     26         /// <summary>
     27         /// 使用指定的日誌回調函數初始化人臉識別類
     28         /// </summary>
     29         /// <param name="action">日誌回調函數</param>
     30         public ViewFace(LogCallBack action) : this("./model/", action) { }
     31         /// <summary>
     32         /// 使用指定的模型目錄、日誌回調函數初始化人臉識別類
     33         /// </summary>
     34         /// <param name="modelPath">模型目錄</param>
     35         /// <param name="action">日誌回調函數</param>
     36         public ViewFace(string modelPath, LogCallBack action) : this(modelPath)
     37         {
     38             if (Platform64)
     39             { ViewFacePlus64.SetLogFunction(action); }
     40             else
     41             { ViewFacePlus32.SetLogFunction(action); }
     42         }
     43 
     44         // public property
     45         /// <summary>
     46         /// 獲取或設置模型路徑
     47         /// </summary>
     48         public string ModelPath
     49         {
     50             get
     51             {
     52                 if (Platform64)
     53                 { return ViewFacePlus64.GetModelPath(); }
     54                 else
     55                 { return ViewFacePlus32.GetModelPath(); }
     56             }
     57             set
     58             {
     59                 if (Platform64)
     60                 { ViewFacePlus64.SetModelPath(value); }
     61                 else
     62                 { ViewFacePlus32.SetModelPath(value); }
     63             }
     64         }
     65         /// <summary>
     66         /// 獲取或設置人臉類型
     67         /// <para>
     68         /// <listheader>此屬性可影響到以下方法:</listheader><br />
     69         ///<c><see cref="FaceDetector(Bitmap)"/></c><br />
     70         ///<c><see cref="Extract(Bitmap, FaceMarkPoint[])"/></c><br />
     71         ///<c><see cref="Similarity(float[], float[])"/></c><br />
     72         /// </para>
     73         /// </summary>
     74         public FaceType FaceType { get; set; } = FaceType.Light;
     75         /// <summary>
     76         /// 獲取或設置人臉關鍵點類型
     77         /// <para>
     78         /// <listheader>此屬性可影響到以下方法:</listheader><br />
     79         ///<c><see cref="FaceMark(Bitmap, FaceInfo)"/></c><br />
     80         /// </para>
     81         /// </summary>
     82         public MarkType MarkType { get; set; } = MarkType.Light;
     83         /// <summary>
     84         /// 獲取或設置人臉檢測器設置
     85         /// </summary>
     86         public DetectorSetting DetectorSetting { get; set; } = new DetectorSetting();
     87 
     88 
     89         // public method
     90         /// <summary>
     91         /// 識別 <paramref name="bitmap"/> 中的人臉,並返回人臉的信息。
     92         /// <para>
     93         ///<c><see cref="FaceType"/> <see langword="="/> <see cref="FaceType.Normal"/> <see langword="||"/> <see cref="FaceType.Light"/></c> 時, 需要模型:<see langword="face_detector.csta"/><br/>
     94         ///<c><see cref="FaceType"/> <see langword="="/> <see cref="FaceType.Mask"/></c> 時, 需要模型:<see langword="mask_detector.csta"/><br/>
     95         /// </para>
     96         /// </summary>
     97         /// <param name="bitmap">包含人臉的圖片</param>
     98         /// <returns></returns>
     99         public FaceInfo[] FaceDetector(Bitmap bitmap)
    100         {
    101             byte[] bgr = ImageSet.Get24BGRFromBitmap(bitmap, out int width, out int height, out int channels);
    102             int size;
    103             if (Platform64)
    104             { size = ViewFacePlus64.DetectorSize(bgr, width, height, channels, DetectorSetting.FaceSize, DetectorSetting.Threshold, DetectorSetting.MaxWidth, DetectorSetting.MaxHeight, (int)FaceType); }
    105             else
    106             { size = ViewFacePlus32.DetectorSize(bgr, width, height, channels, DetectorSetting.FaceSize, DetectorSetting.Threshold, DetectorSetting.MaxWidth, DetectorSetting.MaxHeight, (int)FaceType); }
    107             float[] _socre = new float[size];
    108             int[] _x = new int[size];
    109             int[] _y = new int[size];
    110             int[] _width = new int[size];
    111             int[] _height = new int[size];
    112             if (Platform64)
    113             { _ = ViewFacePlus64.Detector(_socre, _x, _y, _width, _height); }
    114             else
    115             { _ = ViewFacePlus32.Detector(_socre, _x, _y, _width, _height); }
    116             List<FaceInfo> infos = new List<FaceInfo>();
    117             for (int i = 0; i < size; i++)
    118             {
    119                 infos.Add(new FaceInfo() { Score = _socre[i], Location = new FaceRect() { X = _x[i], Y = _y[i], Width = _width[i], Height = _height[i] } });
    120             }
    121             return infos.ToArray();
    122         }
    123 
    124         /// <summary>
    125         /// 識別 <paramref name="bitmap"/> 中指定的人臉信息 <paramref name="info"/> 的關鍵點坐標。
    126         /// <para>
    127         ///<see cref="FaceType"/> <see langword="="/> <see cref="FaceType.Normal"/> 時, 需要模型:<see langword="face_landmarker_pts68.csta"/><br/>
    128         ///<see cref="FaceType"/> <see langword="="/> <see cref="FaceType.Mask"/> 時, 需要模型:<see langword="face_landmarker_mask_pts5.csta"/><br/>
    129         ///<see cref="FaceType"/> <see langword="="/> <see cref="FaceType.Light"/> 時, 需要模型:<see langword="face_landmarker_pts5.csta"/><br/>
    130         /// </para>
    131         /// </summary>
    132         /// <param name="bitmap">包含人臉的圖片</param>
    133         /// <param name="info">指定的人臉信息</param>
    134         /// <returns></returns>
    135         public FaceMarkPoint[] FaceMark(Bitmap bitmap, FaceInfo info)
    136         {
    137             byte[] bgr = ImageSet.Get24BGRFromBitmap(bitmap, out int width, out int height, out int channels);
    138             int size;
    139             if (Platform64)
    140             { size = ViewFacePlus64.FaceMarkSize((int)MarkType); }
    141             else
    142             { size = ViewFacePlus32.FaceMarkSize((int)MarkType); }
    143             double[] _pointX = new double[size];
    144             double[] _pointY = new double[size];
    145             bool val;
    146             if (Platform64)
    147             { val = ViewFacePlus64.FaceMark(bgr, width, height, channels, info.Location.X, info.Location.Y, info.Location.Width, info.Location.Height, _pointX, _pointY, (int)MarkType); }
    148             else
    149             { val = ViewFacePlus32.FaceMark(bgr, width, height, channels, info.Location.X, info.Location.Y, info.Location.Width, info.Location.Height, _pointX, _pointY, (int)MarkType); }
    150             if (val)
    151             {
    152                 List<FaceMarkPoint> points = new List<FaceMarkPoint>();
    153                 for (int i = 0; i < size; i++)
    154                 { points.Add(new FaceMarkPoint() { X = _pointX[i], Y = _pointY[i] }); }
    155                 return points.ToArray();
    156             }
    157             else
    158             { throw new Exception("人臉關鍵點獲取失敗"); }
    159         }
    160 
    161         /// <summary>
    162         /// 提取人臉特徵值。
    163         /// <para>
    164         ///<see cref="FaceType"/> <see langword="="/> <see cref="FaceType.Normal"/> 時, 需要模型:<see langword="face_recognizer.csta"/><br/>
    165         ///<see cref="FaceType"/> <see langword="="/> <see cref="FaceType.Mask"/> 時, 需要模型:<see langword="face_recognizer_mask.csta"/><br/>
    166         ///<see cref="FaceType"/> <see langword="="/> <see cref="FaceType.Light"/> 時, 需要模型:<see langword="face_recognizer_light.csta"/><br/>
    167         /// </para>
    168         /// </summary>
    169         /// <param name="bitmap"></param>
    170         /// <param name="points"></param>
    171         /// <returns></returns>
    172         public float[] Extract(Bitmap bitmap, FaceMarkPoint[] points)
    173         {
    174             byte[] bgr = ImageSet.Get24BGRFromBitmap(bitmap, out int width, out int height, out int channels);
    175             float[] features;
    176             if (Platform64)
    177             { features = new float[ViewFacePlus64.ExtractSize((int)FaceType)]; }
    178             else
    179             { features = new float[ViewFacePlus32.ExtractSize((int)FaceType)]; }
    180 
    181             if (Platform64)
    182             { ViewFacePlus64.Extract(bgr, width, height, channels, points, features, (int)FaceType); }
    183             else
    184             { ViewFacePlus32.Extract(bgr, width, height, channels, points, features, (int)FaceType); }
    185             return features;
    186         }
    187 
    188         /// <summary>
    189         /// 計算特徵值相似度。
    190         /// <para>只能計算相同 <see cref="FaceType"/> 計算出的特徵值</para>
    191         /// <para>
    192         ///<see cref="FaceType"/> <see langword="="/> <see cref="FaceType.Normal"/> 時, 需要模型:<see langword="face_recognizer.csta"/><br/>
    193         ///<see cref="FaceType"/> <see langword="="/> <see cref="FaceType.Mask"/> 時, 需要模型:<see langword="face_recognizer_mask.csta"/><br/>
    194         ///<see cref="FaceType"/> <see langword="="/> <see cref="FaceType.Light"/> 時, 需要模型:<see langword="face_recognizer_light.csta"/><br/>
    195         /// </para>
    196         /// </summary>
    197         /// <exception cref="ArgumentException"/>
    198         /// <exception cref="ArgumentNullException"/>
    199         /// <param name="leftFeatures"></param>
    200         /// <param name="rightFeatures"></param>
    201         /// <returns></returns>
    202         public float Similarity(float[] leftFeatures, float[] rightFeatures)
    203         {
    204             if (leftFeatures.Length == 0 || rightFeatures.Length == 0)
    205                 throw new ArgumentNullException("參數不能為空", nameof(leftFeatures));
    206             if (leftFeatures.Length != rightFeatures.Length)
    207                 throw new ArgumentException("兩個參數長度不一致");
    208 
    209 
    210             if (Platform64)
    211             { return ViewFacePlus64.Similarity(leftFeatures, rightFeatures, (int)FaceType); }
    212             else
    213             { return ViewFacePlus32.Similarity(leftFeatures, rightFeatures, (int)FaceType); }
    214         }
    215 
    216         /// <summary>
    217         /// 判斷相似度是否為同一個人。
    218         /// </summary>
    219         /// <param name="similarity">相似度</param>
    220         /// <returns></returns>
    221         public bool IsSelf(float similarity) => similarity > Face.Threshold[FaceType];
    222 
    223         /// <summary>
    224         /// 釋放資源
    225         /// </summary>
    226         ~ViewFace()
    227         {
    228             if (Platform64)
    229             { ViewFacePlus64.ViewDispose(); }
    230             else
    231             { ViewFacePlus32.ViewDispose(); }
    232         }
    233     }

    C# 面向對象層

     

    五、也許…

    • 此項目還未實現 SeetaFace6 中的許多特性,也許:

        想起 GitHub 密碼,持續更新…
        刪除代碼倉庫跑路…

    • 如果在使用過程中遇到問題,你也許可以:

        在 GitHub 報告Bug…
        向我 發送郵件

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案