諾貝爾物理獎揭曉 三得主讓宇宙、天文學徹底改觀

摘錄自2019年10月8日聯合報報導

諾貝爾物理獎8日揭曉,總獎金900萬瑞典克朗(約台幣2800萬元),諾貝爾委員會決定,其中一半頒給加拿大裔美籍宇宙學家皮博(James Peebles),表彰他在物理宇宙學的理論發現,另一半頒給瑞士科學家梅爾(Michel Mayor)和奎洛茲(Didier Queloz),表彰他們發現太陽系之外的行星「系外行星」,是人類首次發現系外行星。

皮博現年84歲,目前是美國普林斯頓大學名譽教授,被譽為最有影響力的宇宙學家之一。諾貝爾委員會說,皮博關於宇宙及其數以十億計星系與星系團的理論架構,是「從大爆炸至今,我們了解的宇宙史的基礎」,他的研究使用理論工具和計算過程,詮釋宇宙初期以來的痕跡,這些研究創造了適當條件,促使過50年來的宇宙學「徹底改觀」。

皮博的研究顯示,人類知道的物質如恆星、行星和人類自己,只占宇宙5%,其餘95%都是由「未知的暗物質和暗能量」構成。

梅爾和奎洛茲1995年發現系外行星「飛馬座51b」,與木星大小類似,諾貝爾委員會說,這兩名科學家「帶動天文學的革命」,從那時候開始,科學家在銀河系發現四千多顆系外行星,「如今仍在這個奇異新世界持續探索,發現各色各樣的系外行星,在大小、形態和軌道方面都不同」。

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

【其他文章推薦】

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

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

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

※超省錢租車方案

聯合利華承諾:2025年前減半塑膠用量

摘錄自2019年10月8日聯合報報導

英荷消費品生產商聯合利華(Unilever)今天表示,2025年前將減半旗下產品使用的新塑膠量,從目前1年使用70萬公噸塑膠,減半到低於35萬公噸。

聯合利華將減少35萬公噸「原生塑膠」用量,其中10萬公噸來自直接減少塑膠包裝,例如製造可重複利用的包裝或可補充包裝、採用替代包裝,或是完全不會用到塑膠的「裸賣」方式。

另25公噸減量目標則會從使用回收塑膠下手。

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

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

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

※超省錢租車方案

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

全球氣候抗爭大串聯 華爾街銅牛濺血

摘錄自2019年10月8日世界日報報導

響應環保組織「反抗滅絕」氣候抗爭全球大串聯活動的示威人士,7日在世界各地主要城市紛紛上街抗議,要求各國政府要針對氣候變遷採取更為迫切的因應之道。紐約知名景點華爾街銅牛也遭到波及,被抗議人士灑上道具鮮血。

一名示威者還爬到銅牛背上,搖著一面綠色旗幟。不過,數名抗議人士稍後便動手清理潑灑在地面上的道具鮮血。「反抗滅絕」華爾街示威行動籌畫者Justin Becker接受訪問時說,石油企業與華爾街金融機構關係密切:「這裡沾染了這個世界的鮮血。」

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

強制歐美改善空污 《哥德堡議定書》修訂版正式生效

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

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

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

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

Jmeter系列(30)- 詳解 JDBC Request

如果你想從頭學習Jmeter,可以看看這個系列的文章哦

https://www.cnblogs.com/poloyy/category/1746599.html

 

前言

  • JDBC Request 主要是向數據庫發送一個 JDBC 請求(sql 語句),並獲取返回的數據集
  • 它需要和數據庫連接池配置(JDBC Connection Configuration)一起使用,可參考此篇博文:https://www.cnblogs.com/poloyy/p/13182706.html

 

JDBC Request

 

JDBC Request 界面介紹

 

字段含義

字段 含義

Variable Name Bound to Pool

數據庫連接池配置的名稱

Query Type

sql 語句的類型

SQL Query

  • sql 語句
  • 語句結尾不需要添加 ; 
  • 變量用 ? 佔位

Parameter values

需要傳遞的變量值,多個變量用 , 分隔

Parameter types

變量類型

Variable Names

  • 保存sql語句返回的數據和返回數據的總行數
  • 用 , 分隔
  • 跳過列用空

Result Variable Name

一個 Object 變量存儲所有返回值

Query timeout(s)

超時時間;默認0,代表無限時間

Limit ResultSet

和 limit 類似作用,限制 sql 語句返回結果集的行數

Handle ResultSet

如何定義 callable statements 返回的結果集;默認是存儲為字符串

後續通過各種栗子來深入理解常用字段的含義

 

舉栗子的前提

需要自己找一個有數據庫的數據來練手哦!這裏拿的表數據如下哈

 

只有 sql 語句的栗子

JDBC Request

沒啥特別的,平時 sql 怎麼寫,這裏就怎麼寫

 

運行結果

 

參數化的栗子

JDBC Request

 

運行結果

 

知識點

  • 有幾個問號,Parameter value、Parameter type 填寫值的數量要保持一致,用,分隔
  • 問號其實是佔位符,如果學過編程的童鞋應該也知道這種寫法,可以避免 SQL 注入的問題
  • sql 中使用佔位符時,Query Type 必須選擇 Prepared Select Statement 或者 Prepared Update Statement 
  • 我們可以用 Jmeter 變量去賦值,看下面栗子

 

參數化+變量的栗子

JDBC Request

 

運行結果

 

知識點

  • 如果在 sql 語句中使用變量,且是字符串類型,需要加上引號(前提是變量值沒有加引號),如 ${name} 
  • 如果在 Parameter values 中使用變量,且是字符串類型,不需要加上引號,只需要在 Parameter types 里寫明為 varchar 即可

 

使用 Variable Names 的栗子

結構樹

 

JDBC Request

添加一個 Debug Sampler 就知道這個字段有什麼作用了

 

JDBC Request 運行結果

 

調試取樣器運行結果

 

知識點

  • mysql:數據庫連接池對象
  • a_#、b_#、c_#、d_#:代錶行數
  • a_1:第 1 行、第 1 列
  • b_2:第 2 行、第 2 列
  • c_3:第 3 行、第 3 列
  • d_3:第 3 行、第 4 列
  • 以此類推….
  • 一般如果 HTTP 請求需要用到 sql 查出來的數據的話,就會用到 Variable names 這個字段

 

使用 Result variable name 的栗子

JDBC Request

 

Debug Sampler  運行結果

 

知識點

該變量是個數組,每一個元素代表一條記錄

 

重點

關於通過 Variable names、Result variable name 獲取到的值如何提取,我們將在下一篇文章中詳細講解

 

使用 Limit ResultSet 的栗子

JDBC Request

 

 

運行結果

 

知識點

  • Limit ResultSet 是對 sql 語句返回的結果集限制行數
  •  limit 10 限制只返回了 10 條數據,然後 Limit ResultSet = 6 限制結果集最終只返回 6 條數據

 

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

【其他文章推薦】

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

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

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

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

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

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

Halcon斑點分析BlobAnalysis解析

斑點分析的算法非常簡單:在圖像中,相關對象的像素(也稱為前景)通過其灰度值來識別。例如,圖中示例显示了液體中的組織顆粒。這些粒子是明亮的,液體(背景)是暗的。通過選擇明亮的像素(閾值),可以很容易檢測到顆粒。在許多應用中,暗像素和亮像素的簡單條件不再成立,但結果相同可以通過額外的預處理或像素選擇/分組的替代方法來實現。

在這種情況下,斑點分析的優點是HALCON提供了大量算子使其具有極大的靈活性。此外,這些方法通常具有很高的性能。斑點分析也可以與許多其他視覺任務相結合,例如作為預處理步驟,靈活地生成交互區域。

基本概念

斑點分析主要包括三個部分:

  1. 獲取圖像

  2. 分割圖像

    採集圖像后,接下來的任務是選擇前景像素。這也稱為分割。結果
    在HALCON中通常將此過程為Blob(二進制大對象),數據類型為區域(a region)。

  3. 提取目標特徵

    在最後一步中,將計算出諸如面積(像素數),重心或方向之類的特徵

該基本概念的一個示例是以下程序,該程序屬於上述示例。在此,從文件中獲取圖像。使用閾值(threshold)選擇大於120的所有像素。然後,引入了一個不太明顯的步驟:算子連接(connection)將所有亮像素的集合分離為所謂的連接組件。此步驟的效果是我們將劃分出多個區域,而不是閾值(threshold)返回的單個區域。該程序的最後一步是一些功能的計算。在此,算子area_center確定了大小(像素數)和重心。請注意area_center返回了三個值(每個參數有一個值)。

read_image (Image, 'particle')
threshold (Image, BrightPixels, 120, 255)
connection (BrightPixels, Particles)
area_center (Particles, Area, Row, Column)

擴展概念

在許多情況下,斑點分析將比上述示例更高級。原因是混亂或不均勻的照明。此外,經常需要進行后處理,例如將元素特徵轉換為真實世界單位或結果可視化。

使用RIO(Region Of Interest)

可以通過使用感興趣區域來加快斑點分析。搜索的斑點區域被限制越多。搜索將更快更強大。

對齊RIO或圖像

在某些應用中,關注區域必須相對於另一個對象對齊。或者圖像本身可以對齊,例如通過旋轉或裁剪。

校正圖像

與對齊類似,可能需要校正圖像,例如消除鏡頭畸變或轉換圖像的參考點。

預處理圖像(過濾)

下一個重要部分是圖像的預處理。在這裏,像mean_image或gauss_filter這樣的運算符可用於消除噪音。一個快速但不太完美的替代方案是binomial_filter。運算符middle_image對於抑制小斑點或細線很有用。算子anisotropic_diffusion(各向異性擴散)對保留邊緣的平滑很有用,最後使用fill_interlace消除由隔行交錯相機(攝像機視頻流圖像)引起的缺陷

提取分割參數

代替使用固定的閾值,可以為每個圖像動態提取它們。例如具有多個峰值的灰度值直方圖,每個對象類別一個。在這裏,您可以使用算子gray_histo_abs和histo_to_thresh。作為高級替代方案,可以將算子intensity與參考圖像結合使用,僅適用於背景:在設置過程中,將確定背景區域的平均灰度值。如果平均灰度值已更改,則可以相應調整閾值。

分割圖像

對於分割,可以使用各種方法。最簡單的方法是threshold(閾值),指定一個屬於前景對象的值範圍。另一個非常常見的方法是dyn_threshold。在此,第二張圖像將作為參考圖像。通過這種方法,使用局部閾值而不是全局閾值。這些局部閾值存儲在參考圖像中。可以通過拍攝空背景圖片將其設為靜態作為參考圖像,也可以使用平滑濾鏡(例如mean_image)

處理區域

一旦斑點區域被分割。通常需要對其進行修改,例如,通過抑制小區域,給定方向或接近其他區域的區域。在這種情況下,形態算子open_circle和opening_rectangle通常可用於抑制噪聲,closeing_circle和closing-rectanglel填補空白。可以使用select_shape,select_shape_std和select-proto-proto選擇具有特定功能的斑點。

特徵提取

最終處理時,將提取斑點的特徵,所需功能的類型取決於應用程序。類型列表可以在參考手冊的“Regions/Features”和”Image/Features”中找到。

將結果轉換為世界坐標

諸如面積或重心之類的要素通常必須轉換為世界坐標。這可以通過HALCON相機
校準實現。

可視化結果

最後,你可能要显示圖像的斑點(區域)和特徵。

靈感來源於Halcon官方文檔

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

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

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

新北清潔公司,居家、辦公、裝潢細清專業服務

※推薦評價好的iphone維修中心

LeetCode 80,不使用外部空間的情況下對有序數組去重

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

今天是LeetCode專題的第49篇文章,我們一起來看LeetCode的第80題,有序數組去重II(Remove Duplicates from Sorted Array II)。

這題的官方難度是Medium,通過率是43.3%,點贊1104,反對690。這題的通過率有一點點高,然後點贊比也不是很高。說明這題偏容易,並且大家的評價偏低。也的確如此,我個人覺得,大家評價不好的主要原因還是這題偏容易了一些。

題面

其實從題目的標題當中我們已經可以得到很多信息了,實際上也的確如此,這題的題面和標題八九不離十,需要我們對一個有序的數組進行去重。不過去重的條件是最多允許一個元素出現兩次,也就是要將多餘的元素去掉。並且題目還限制了需要我們在原數組進行操作,對於空間複雜度的要求是。由於我們去除了元素之後會帶來數組長度的變化,所以我們最後需要返回完成之後數組的長度。

這是一種常規的做法,在C++以及一些古老的語言當中數組是不能變更長度的。我們想要在原數組上刪除數據,只能將要刪除的數據移動到數組末尾,然後返回變更之後的數組長度。這樣下游就通過返回的數組長度得知變更之後的數量變化。由於新晉的一些語言,比如Java、Python都支持數組長度變動,所以很少在這些語言的代碼當中看到這樣的用法了。

樣例

Given nums = [0,0,1,1,1,1,2,3,3],

Your function should return length = 7, with the first seven elements of nums being modified to 0, 0, 1, 1, 2, 3 and 3 respectively.

It doesn't matter what values are set beyond the returned length. 

在這個樣例當中,由於1出現了4次,所以我們需要刪除掉2個1,那麼刪除之後的數組長度也會減少2,所以我們需要返回7,表示刪除之後的新的數組的有效長度是7。並且保證原數組當中前5個元素是[0, 0, 1, 1, 2, 3]

題解

刪除重複的元素本身並不複雜,唯一麻煩的是我們怎麼在不引入額外存儲的情況下完成這一點。如果你能抓住數組是有序的這一點,應該很容易想通:既然數組是有序的,那麼相同的元素必然排在一起。

既然相同的元素排在一起,那麼我們可以利用一個變量存儲當前元素出現的次數。如果遇到不同的元素,則將次數置為1。這樣我們就可以判斷出究竟哪些元素需要刪除,哪些元素需要保留了。

但是這就又引入了另外一個問題,我們怎麼來刪除這些重複的元素呢?因為我們不能引入額外的數組,需要在當前數組上完成。我們可以先假設沒有這個限制,我們會怎麼做?

new_nums = []
cur = None
for i in range(n):
    if cur == nums[i]:
        count += 1
 else:
        count = 1
        cur = nums[i]
    if count > 2:
        continue
    new_nums.append(nums[i])

由於有這個限制,所以我們要做的就是把new_nums這個數組去掉,其實去掉是很簡單的,因為我們可以讓nums這個數組自己覆蓋自己。因為產出的數據的數量一定是小於等於數組長度的,所以不會出現數組越界的問題。我們只需要維護一個下標記錄nums數組當中允許覆蓋的位置即可。

這個也是非常常見的做法,我們在之前的題目當中也曾經見到過。

class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        # start是起始覆蓋指針,指向第一個可以覆蓋的位置
        start, cur, cnt = 0, None, 0
        n = len(nums)
        if n == 0:
            return 0
        for i in range(n):
            if cur == nums[i]:
                cnt += 1
            else:
                cnt = 1
                cur = nums[i]
            # 如果數量超過2,說明當前元素應該捨棄,則continue
            if cnt > 2:
                continue
            # 否則用當前元素覆蓋start位置,並且start移動一位
            else:
                nums[start] = nums[i]
                start += 1
        return start

關於這段代碼,還有一個簡化版本,我們可以把cnt變量也省略掉。因為元素是有序的,我們可以直接用nums[i]和nums[i-2]進行判斷,如果相等,那麼說明重複的元素一定超過了兩個,當前元素需要跳過。

簡化之後的代碼如下:

class Solution(object):
    def removeDuplicates(self, nums):
        """  :type nums: List[int]  :rtype: int  """
        i = 0
        for n in nums:
            if i < 2 or n != nums[i - 2]:
                nums[i] = n
                i += 1
        return i

總結

今天的題目不難,總體來說算是Medium偏低難度,主要有兩點值得稱道。第一點是C++風格inplace變更數組的做法,第二點就是數組自我覆蓋的方法。除此之外,題目幾乎沒什麼難度,我想大家應該都能想出解法來。

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

本文使用 mdnice 排版

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

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

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準

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…
    向我 發送郵件

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

MongoDB via Dotnet Core數據映射詳解

用好數據映射,MongoDB via Dotnet Core開發變會成一件超級快樂的事。

一、前言

MongoDB這幾年已經成為NoSQL的頭部數據庫。

由於MongoDB free schema的特性,使得它在互聯網應用方面優於常規數據庫,成為了相當一部分大廠的主數據選擇;而它的快速布署和開發簡單的特點,也吸引着大量小開發團隊的支持。

關於MongoDB快速布署,我在15分鐘從零開始搭建支持10w+用戶的生產環境(二)里有寫,需要了可以去看看。

作為一個數據庫,基本的操作就是CRUD。MongoDB的CRUD,不使用SQL來寫,而是提供了更簡單的方式。

方式一、BsonDocument方式

BsonDocument方式,適合能熟練使用MongoDB Shell的開發者。MongoDB Driver提供了完全覆蓋Shell命令的各種方式,來處理用戶的CRUD操作。

這種方法自由度很高,可以在不需要知道完整數據集結構的情況下,完成數據庫的CRUD操作。

方式二、數據映射方式

數據映射是最常用的一種方式。準備好需要處理的數據類,直接把數據類映射到MongoDB,並對數據集進行CRUD操作。

下面,對數據映射的各個部分,我會逐個說明。

    為了防止不提供原網址的轉載,特在這裏加上原文鏈接:https://www.cnblogs.com/tiger-wang/p/13185605.html

二、開發環境&基礎工程

這個Demo的開發環境是:Mac + VS Code + Dotnet Core 3.1.2。

建立工程:

% dotnet new sln -o demo
The template "Solution File" was created successfully.
cd demo 
% dotnet new console -o demo
The template "Console Application" was created successfully.

Processing post-creation actions...
Running 'dotnet restore' on demo/demo.csproj...
  Determining projects to restore...
  Restored demo/demo/demo.csproj (in 162 ms).

Restore succeeded.
% dotnet sln add demo/demo.csproj 
Project `demo/demo.csproj` added to the solution.

建立工程完成。

下面,增加包mongodb.driver到工程:

cd demo
% dotnet add package mongodb.driver
  Determining projects to restore...
info : Adding PackageReference for package 'mongodb.driver' into project 'demo/demo/demo.csproj'.
info : Committing restore...
info : Writing assets file to disk. Path: demo/demo/obj/project.assets.json
log  : Restored /demo/demo/demo.csproj (in 6.01 sec).

項目準備完成。

看一下目錄結構:

% tree .
.
├── demo
│   ├── Program.cs
│   ├── demo.csproj
│   └── obj
│       ├── demo.csproj.nuget.dgspec.json
│       ├── demo.csproj.nuget.g.props
│       ├── demo.csproj.nuget.g.targets
│       ├── project.assets.json
│       └── project.nuget.cache
└── demo.sln

mongodb.driver是MongoDB官方的數據庫SDK,從Nuget上安裝即可。

三、Demo準備工作

創建數據映射的模型類CollectionModel.cs,現在是個空類,後面所有的數據映射相關內容會在這個類進行說明:

public class CollectionModel
{

}

並修改Program.cs,準備Demo方法,以及連接數據庫:

class Program
{

    private const string MongoDBConnection = "mongodb://localhost:27031/admin";

    private static IMongoClient _client = new MongoClient(MongoDBConnection);
    private static IMongoDatabase _database = _client.GetDatabase("Test");
    private static IMongoCollection<CollectionModel> _collection = _database.GetCollection<CollectionModel>("TestCollection");

    static async Task Main(string[] args)
    
{
        await Demo();
        Console.ReadKey();
    }

    private static async Task Demo()
    
{
    }
}

四、字段映射

從上面的代碼中,我們看到,在生成Collection對象時,用到了CollectionModel

IMongoDatabase _database = _client.GetDatabase("Test");
IMongoCollection<CollectionModel> _collection = _database.GetCollection<CollectionModel>("TestCollection");

這兩行,其實就完成了一個映射的工作:把MongoDB中,Test數據庫下,TestCollection數據集(就是SQL中的數據表),映射到CollectionModel這個數據類中。換句話說,就是用CollectionModel這個類,來完成對數據集TestCollection的所有操作。

保持CollectionModel為空,我們往數據庫寫入一行數據:

private static async Task Demo()
{
    CollectionModel new_item = new CollectionModel();
    await _collection.InsertOneAsync(new_item);
}

執行,看一下寫入的數據:


    "_id" : ObjectId("5ef1d8325327fd4340425ac9")
}

OK,我們已經寫進去一條數據了。因為映射類是空的,所以寫入的數據,也只有_id一行內容。

但是,為什麼會有一個_id呢?

1. ID字段

MongoDB數據集中存放的數據,稱之為文檔(Document)。每個文檔在存放時,都需要有一個ID,而這個ID的名稱,固定叫_id

當我們建立映射時,如果給出_id字段,則MongoDB會採用這個ID做為這個文檔的ID,如果不給出,MongoDB會自動添加一個_id字段。

例如:

public class CollectionModel
{

    public ObjectId _id { get; set; }
    public string title { get; set; }
    public string content { get; set; }
}

public class CollectionModel
{

    public string title { get; set; }
    public string content { get; set; }
}

在使用上是完全一樣的。唯一的區別是,如果映射類中不寫_id,則MongoDB自動添加_id時,會用ObjectId作為這個字段的數據類型。

ObjectId是一個全局唯一的數據。

當然,MongoDB允許使用其它類型的數據作為ID,例如:stringintlongGUID等,但這就需要你自己去保證這些數據不超限並且唯一。

例如,我們可以寫成:

public class CollectionModel
{

    public long _id { get; set; }
    public string title { get; set; }
    public string content { get; set; }
}

我們也可以在類中修改_id名稱為別的內容,但需要加一個描述屬性BsonId

public class CollectionModel
{

    [BsonId]
    public ObjectId topic_id { get; set; }
    public string title { get; set; }
    public string content { get; set; }
}

這兒特別要注意:BsonId屬性會告訴映射,topic_id就是這個文檔數據的ID。MongoDB在保存時,會將這個topic_id轉成_id保存到數據集中。

在MongoDB數據集中,ID字段的名稱固定叫_id。為了代碼的閱讀方便,可以在類中改為別的名稱,但這不會影響MongoDB中存放的ID名稱。

修改Demo代碼:

private static async Task Demo()
{
    CollectionModel new_item = new CollectionModel()
    {
        title = "Demo",
        content = "Demo content",
    };
    await _collection.InsertOneAsync(new_item);
}

跑一下Demo,看看保存的結果:


    "_id" : ObjectId("5ef1e1b1bc1e18086afe3183"), 
    "title" : "Demo"
    "content" : "Demo content"
}

2. 簡單字段

就是常規的數據字段,直接寫就成。

public class CollectionModel
{

    [BsonId]
    public ObjectId topic_id { get; set; }
    public string title { get; set; }
    public string content { get; set; }
    public int favor { get; set; }
}

保存后的數據:


    "_id" : ObjectId("5ef1e9caa9d16208de2962bb"), 
    "title" : "Demo"
    "content" : "Demo content"
    "favor" : NumberInt(100)
}

3. 一個的特殊的類型 – Decimal

說Decimal特殊,是因為MongoDB在早期,是不支持Decimal的。直到MongoDB v3.4開始,數據庫才正式支持Decimal。

所以,如果使用的是v3.4以後的版本,可以直接使用,而如果是以前的版本,需要用以下的方式:

[BsonRepresentation(BsonType.Double, AllowTruncation = true)]
public decimal price { get; set; }

其實就是把Decimal通過映射,轉為Double存儲。

4. 類字段

把類作為一個數據集的一個字段。這是MongoDB作為文檔NoSQL數據庫的特色。這樣可以很方便的把相關的數據組織到一條記錄中,方便展示時的查詢。

我們在項目中添加兩個類ContactAuthor

public class Contact
{

    public string mobile { get; set; }
}
public class Author
{

    public string name { get; set; }
    public List<Contact> contacts { get; set; }
}

然後,把Author加到CollectionModel中:

public class CollectionModel
{

    [BsonId]
    public ObjectId topic_id { get; set; }
    public string title { get; set; }
    public string content { get; set; }
    public int favor { get; set; }
    public Author author { get; set; }
}

嗯,開始變得有點複雜了。

完善Demo代碼:

private static async Task Demo()
{
    CollectionModel new_item = new CollectionModel()
    {
        title = "Demo",
        content = "Demo content",
        favor = 100,
        author = new Author
        {
            name = "WangPlus",
            contacts = new List<Contact>(),
        }
    };

    Contact contact_item1 = new Contact()
    {
        mobile = "13800000000",
    };
    Contact contact_item2 = new Contact()
    {
        mobile = "13811111111",
    };
    new_item.author.contacts.Add(contact_item1);
    new_item.author.contacts.Add(contact_item2);

    await _collection.InsertOneAsync(new_item);
}

保存的數據是這樣的:


    "_id" : ObjectId("5ef1e635ce129908a22dfb5e"), 
    "title" : "Demo"
    "content" : "Demo content"
    "favor" : NumberInt(100),
    "author" : {
        "name" : "WangPlus"
        "contacts" : [
            {
                "mobile" : "13800000000"
            }, 
            {
                "mobile" : "13811111111"
            }
        ]
    }
}

這樣的數據結構,用着不要太爽!

5. 枚舉字段

枚舉字段在使用時,跟類字段相似。

創建一個枚舉TagEnumeration

public enum TagEnumeration
{
    CSharp = 1,
    Python = 2,
}

加到CollectionModel中:

public class CollectionModel
{

    [BsonId]
    public ObjectId topic_id { get; set; }
    public string title { get; set; }
    public string content { get; set; }
    public int favor { get; set; }
    public Author author { get; set; }
    public TagEnumeration tag { get; set; }
}

修改Demo代碼:

private static async Task Demo()
{
    CollectionModel new_item = new CollectionModel()
    {
        title = "Demo",
        content = "Demo content",
        favor = 100,
        author = new Author
        {
            name = "WangPlus",
            contacts = new List<Contact>(),
        },
        tag = TagEnumeration.CSharp,
    };
    /* 後邊代碼略過 */
}

運行后看數據:


    "_id" : ObjectId("5ef1eb87cbb6b109031fcc31"), 
    "title" : "Demo"
    "content" : "Demo content"
    "favor" : NumberInt(100), 
    "author" : {
        "name" : "WangPlus"
        "contacts" : [
            {
                "mobile" : "13800000000"
            }, 
            {
                "mobile" : "13811111111"
            }
        ]
    }, 
    "tag" : NumberInt(1)
}

在這裏,tag保存了枚舉的值。

我們也可以保存枚舉的字符串。只要在CollectionModel中,tag聲明上加個屬性:

public class CollectionModel
{

    [BsonId]
    public ObjectId topic_id { get; set; }
    public string title { get; set; }
    public string content { get; set; }
    public int favor { get; set; }
    public Author author { get; set; }
    [BsonRepresentation(BsonType.String)]
    public TagEnumeration tag { get; set; }
}

數據會變成:


    "_id" : ObjectId("5ef1ec448f1d540919d15904"), 
    "title" : "Demo"
    "content" : "Demo content"
    "favor" : NumberInt(100), 
    "author" : {
        "name" : "WangPlus"
        "contacts" : [
            {
                "mobile" : "13800000000"
            }, 
            {
                "mobile" : "13811111111"
            }
        ]
    }, 
    "tag" : "CSharp"
}

6. 日期字段

日期字段會稍微有點坑。

這個坑其實並不源於MongoDB,而是源於C#的DateTime類。我們知道,時間根據時區不同,時間也不同。而DateTime並不準確描述時區的時間。

我們先在CollectionModel中增加一個時間字段:

public class CollectionModel
{

    [BsonId]
    public ObjectId topic_id { get; set; }
    public string title { get; set; }
    public string content { get; set; }
    public int favor { get; set; }
    public Author author { get; set; }
    [BsonRepresentation(BsonType.String)]
    public TagEnumeration tag { get; set; }
    public DateTime post_time { get; set; }
}

修改Demo:

private static async Task Demo()
{
    CollectionModel new_item = new CollectionModel()
    {
        /* 前邊代碼略過 */
        post_time = DateTime.Now, /* 2020-06-23T20:12:40.463+0000 */
    };
    /* 後邊代碼略過 */
}

運行看數據:


    "_id" : ObjectId("5ef1f1b9a75023095e995d9f"), 
    "title" : "Demo"
    "content" : "Demo content"
    "favor" : NumberInt(100), 
    "author" : {
        "name" : "WangPlus"
        "contacts" : [
            {
                "mobile" : "13800000000"
            }, 
            {
                "mobile" : "13811111111"
            }
        ]
    }, 
    "tag" : "CSharp"
    "post_time" : ISODate("2020-06-23T12:12:40.463+0000")
}

對比代碼時間和數據時間,會發現這兩個時間差了8小時 – 正好的中國的時區時間。

MongoDB規定,在數據集中存儲時間時,只會保存UTC時間。

如果只是保存(像上邊這樣),或者查詢時使用時間作為條件(例如查詢post_time < DateTime.Now的數據)時,是可以使用的,不會出現問題。

但是,如果是查詢結果中有時間字段,那這個字段,會被DateTime默認設置為DateTimeKind.Unspecified類型。而這個類型,是無時區信息的,輸出显示時,會造成混亂。

為了避免這種情況,在進行時間字段的映射時,需要加上屬性:

[BsonDateTimeOptions(Kind = DateTimeKind.Local)]
public DateTime post_time { get; set; }

這樣做,會強制DateTime類型的字段為DateTimeKind.Local類型。這時候,從显示到使用就正確了。

但是,別高興的太早,這兒還有一個但是。

這個但是是這樣的:數據集中存放的是UTC時間,跟我們正常的時間有8小時時差,如果我們需要按日統計,比方每天的銷售額/點擊量,怎麼搞?上面的方式,解決不了。

當然,基於MongoDB自由的字段處理,可以把需要統計的字段,按年月日時分秒拆開存放,像下面這樣的:

class Post_Time
{

    public int year { get; set; }
    public int month { get; set; }
    public int day { get; set; }
    public int hour { get; set; }
    public int minute { get; set; }
    public int second { get; set; }
}

能解決,但是Low哭了有沒有?

下面,終極方案來了。它就是:改寫MongoDB中對於DateTime字段的序列化類。噹噹當~~~

先創建一個類MyDateTimeSerializer

public class MyDateTimeSerializer : DateTimeSerializer
{
    public override DateTime Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
    
{
        var obj = base.Deserialize(context, args);
        return new DateTime(obj.Ticks, DateTimeKind.Unspecified);
    }
    public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, DateTime value)
    
{
        var utcValue = new DateTime(value.Ticks, DateTimeKind.Utc);
        base.Serialize(context, args, utcValue);
    }
}

代碼簡單,一看就懂。

注意,使用這個方法,上邊那個對於時間加的屬性[BsonDateTimeOptions(Kind = DateTimeKind.Local)]一定不要添加,要不然就等着哭吧:P

創建完了,怎麼用?

如果你只想對某個特定映射的特定字段使用,比方只對CollectionModelpost_time字段來使用,可以這麼寫:

[BsonSerializer(typeof(MyDateTimeSerializer))]
public DateTime post_time { get; set; }

或者全局使用:

BsonSerializer.RegisterSerializer(typeof(DateTime), new MongoDBDateTimeSerializer());

BsonSerializer是MongoDB.Driver的全局對象。所以這個代碼,可以放到使用數據庫前的任何地方。例如在Demo中,我放在Main里了:

static async Task Main(string[] args)
{
    BsonSerializer.RegisterSerializer(typeof(DateTime), new MyDateTimeSerializer());

    await Demo();
    Console.ReadKey();
}

這回看數據,數據集中的post_time跟當前時間显示完全一樣了,你統計,你分組,可以隨便霍霍了。

7. Dictionary字段

這個需求很奇怪。我們希望在一個Key-Value的文檔中,保存一個Key-Value的數據。但這個需求又是真實存在的,比方保存一個用戶的標籤和標籤對應的命中次數。

數據聲明很簡單:

public Dictionary<stringint> extra_info { get; set; }

MongoDB定義了三種保存屬性:DocumentArrayOfDocumentsArrayOfArrays,默認是Document

屬性寫法是這樣的:

[BsonDictionaryOptions(DictionaryRepresentation.ArrayOfDocuments)]
public Dictionary<stringint> extra_info { get; set; }

這三種屬性下,保存在數據集中的數據結構有區別。

DictionaryRepresentation.Document


    "extra_info" : {
        "type" : NumberInt(1), 
        "mode" : NumberInt(2)
    }
}

DictionaryRepresentation.ArrayOfDocuments


    "extra_info" : [
        {
            "k" : "type"
            "v" : NumberInt(1)
        }, 
        {
            "k" : "mode"
            "v" : NumberInt(2)
        }
    ]
}

DictionaryRepresentation.ArrayOfArrays


    "extra_info" : [
        [
            "type"
            NumberInt(1)
        ], 
        [
            "mode"
            NumberInt(2)
        ]
    ]
}

這三種方式,從數據保存上並沒有什麼區別,但從查詢來講,如果這個字段需要進行查詢,那三種方式區別很大。

如果採用BsonDocument方式查詢,DictionaryRepresentation.Document無疑是寫着最方便的。

如果用Builder方式查詢,DictionaryRepresentation.ArrayOfDocuments是最容易寫的。

DictionaryRepresentation.ArrayOfArrays就算了。數組套數組,查詢條件寫死人。

我自己在使用時,多數情況用DictionaryRepresentation.ArrayOfDocuments

五、其它映射屬性

上一章介紹了數據映射的完整內容。除了這些內容,MongoDB還給出了一些映射屬性,供大家看心情使用。

1. BsonElement屬性

這個屬性是用來改數據集中的字段名稱用的。

看代碼:

[BsonElement("pt")]
public DateTime post_time { get; set; }

在不加BsonElement的情況下,通過數據映射寫到數據集中的文檔,字段名就是變量名,上面這個例子,字段名就是post_time

加上BsonElement后,數據集中的字段名會變為pt

2. BsonDefaultValue屬性

看名稱就知道,這是用來設置字段的默認值的。

看代碼:

[BsonDefaultValue("This is a default title")]
public string title { get; set; }

當寫入的時候,如果映射中不傳入值,則數據庫會把這個默認值存到數據集中。

3. BsonRepresentation屬性

這個屬性是用來在映射類中的數據類型和數據集中的數據類型做轉換的。

看代碼:

[BsonRepresentation(BsonType.String)]
public int favor { get; set; }

這段代表表示,在映射類中,favor字段是int類型的,而存到數據集中,會保存為string類型。

前邊Decimal轉換和枚舉轉換,就是用的這個屬性。

4. BsonIgnore屬性

這個屬性用來忽略某些字段。忽略的意思是:映射類中某些字段,不希望被保存到數據集中。

看代碼:

[BsonIgnore]
public string ignore_string { get; set; }

這樣,在保存數據時,字段ignore_string就不會被保存到數據集中。

六、總結

數據映射本身沒什麼新鮮的內容,但在MongoDB中,如果用好了映射,開發過程從效率到爽的程度,都不是SQL可以相比的。正所謂:

一入Mongo深似海,從此SQL是路人。

謝謝大家!

(全文完)

本文的配套代碼在https://github.com/humornif/Demo-Code/tree/master/0015/demo

 

 

微信公眾號:老王Plus

掃描二維碼,關注個人公眾號,可以第一時間得到最新的個人文章和內容推送

本文版權歸作者所有,轉載請保留此聲明和原文鏈接

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

【其他文章推薦】

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

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

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

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

※別再煩惱如何寫文案,掌握八大原則!

Day10-微信小程序實戰-交友小程序-實現刪除好友信息與子父組件間通信

回顧:上一次已經把消息的布局以及樣式做好了

效果圖:

 

 在removeList.js文件中,messageId就是發起這個消息的用戶了

先查看一下自定義組件的生命周期

https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/lifetimes.html

 lifetimes: {
    attached: function() {
      // 在組件實例進入頁面節點樹時執行
    },
    detached: function() {
      // 在組件實例被從頁面節點樹移除時執行
    },
  }

直接就是在lifttimes裏面進行定義的(直接就是在methods的同級的下面加上即可了)

因為要對用戶的信息進行渲染,就可以看成是一個一個的對象,所以就可以在removeLIst.js中定義一個對象

然後遇到的問題就和之前是一樣的了,就是我們得到的數據太多了,沒必要全部都要,可以選擇性的要,只需要頭像和昵稱

(所以就可以在get前面來一個field)

lifetimes: {
    attached: function () {
      // 一進來就會進行它了
      db.collection('users').doc(this.data.messageId)
      .field({
        userPhoto : true,
        nickName : true
      })
      .get().then((res)=>{
        this.setData({
            userMessage : res.data
        });
      });
    }
  }

這樣的話我們在這個頁面裏面就可以得到用戶的數據了,剩下的就是直接可以在wxml中用了

<!--components/removeList/removeList.wxml-->
<movable-area class="area">
     <movable-view direction="horizontal" class="view">{{ userMessage.nickName }}</movable-view>
     <image src="{{ userMessage.userPhoto }}" />
     <view class="delete">刪除</view>
 </movable-area>

效果圖:

 

 在之後設置刪除功能之前,先設置一下就是只要點擊了消息列表中用戶的頭像之後,就可以跳轉到這個用戶的詳情頁了

可以直接 在編輯個人信息的頁面 editUserInfo.wxml中COPY代碼  

在設置這個跳轉頁面的url的時候,因為同時要給這個url傳遞參數的,所以這個時候就要用大括號括起來了

<!--components/removeList/removeList.wxml-->
<movable-area class="area">
     <movable-view direction="horizontal" class="view">{{ userMessage.nickName }}</movable-view>
     <navigator url="{{'/pages/detail/detail?userId=' + userMessage._id}}" open-type="navigate">
     <image src="{{ userMessage.userPhoto }}" />
     </navigator>
     <view class="delete">刪除</view>
 </movable-area>

即可實現,點擊頭像跳轉到個人的詳情頁面

 

二、下面就是對刪除功能進行設計

一開始的就是,點擊了之後,要給用戶一個提示信息,讓用戶可以選擇是取消還是確定的,這裏用的是一個wx.showModel這樣一個內置的方法

 

所以就要另外的給“點擊了確定”加邏輯了,就要在微信開放文檔裏面細看這個API了

https://developers.weixin.qq.com/miniprogram/dev/api/ui/interaction/wx.showModal.html

wx.showModal({
  title: '提示',
  content: '這是一個模態彈窗',
  success (res) {
    if (res.confirm) {
      console.log('用戶點擊確定')
    } else if (res.cancel) {
      console.log('用戶點擊取消')
    }
  }
})

把查到的賦值給list,然後在用數組的filter進行刪除即可了

通過fileter過濾之後,就是過濾初和我們不想要的東西,然後把這些東西再次賦值為list,然後我們把前後的list打印出來會發現:

 

 確實是過濾掉了的

 由於如果要刪掉的話,就設計了removeList這個組件和message這各頁面之間的通信了,並且是子組件像父組件,用到事件來做的

https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/events.html

<!-- 當自定義組件觸發“myevent”事件時,調用“onMyEvent”方法 -->
<component-tag-name bindmyevent="onMyEvent" />
<!-- 或者可以寫成 -->
<component-tag-name bind:myevent="onMyEvent" />

所以在message.wxml中隊子組件remove-list設置

<remove-list wx:for="{{ userMessage }}" wx:key="{{index}}" messageId="{{ item }}"
     bindmyevent="onMyEvent"/> 
    

這樣事件監聽就寫好了,但是如何在組件中觸發呢,我們回到removelist.js中

繼續查看山脈的鏈接-微信開發文檔

Component({
  properties: {},
  methods: {
    onTap: function(){
      var myEventDetail = {} // detail對象,提供給事件監聽函數
      var myEventOption = {} // 觸發事件的選項
      this.triggerEvent('myevent', myEventDetail, myEventOption)
    }
  }
})

在removelist.js中通過:

 this.triggerEvent('myevent',list) 

前面參數,要和在 message.wxml設置的 bindmyevent,後面的myevent對應上

第二個參數就是我們 過濾剩下的list

給message傳過去之後

  onMyEvent(ev){
  this.setData({
    userMessage : ev.detail
  });

通過這樣的設置出現了一個bug,就是我們刪除第一條信息的時候,直接把第二條刪掉了,第一條被留下來了

當我們查看數據庫的時候,留下來的就是第二條信息,但是在前端显示的是第一條信息留下,第二條信息沒了

要這樣修改:

onMyEvent(ev){
    this.setData({
      userMessage : []
    },()=>{
        this.setData({
          userMessage : ev.detail
        });
    });
  }
  

先賦值為空,之後再次調用removelist,再把過濾的數組進行賦值  

 

 

 也就是全部清空之後,再重新渲染的

 

 整個邏輯:

1、在數據庫中用戶的頭像和昵稱找到,然後獲取數據

 

2、點擊刪除按鈕的時候,彈出提示框,如果用戶點了缺點刪除的話,之後我們先查詢

 找到之後,把那個消息在message列表中過濾掉

 

 3、然後再重新的更新,之後就觸發子父通信,把更新之後的list傳給

 

4、父組件拿到removelist這組件的信息

 

 拿到就更新我們的列表,這樣的話列表就發送了變化了

 

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

【其他文章推薦】

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

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

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

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

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

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