環境資訊中心綜合外電;姜唯 編譯;林大利 審校
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※超省錢租車方案
※別再煩惱如何寫文案,掌握八大原則!
※回頭車貨運收費標準
※教你寫出一流的銷售文案?
如果你想從頭學習Jmeter,可以看看這個系列的文章哦
https://www.cnblogs.com/poloyy/category/1746599.html
| 字段 | 含義 |
|
Variable Name Bound to Pool |
數據庫連接池配置的名稱 |
|
Query Type |
sql 語句的類型 |
|
SQL Query |
|
|
Parameter values |
需要傳遞的變量值,多個變量用 , 分隔 |
|
Parameter types |
變量類型 |
|
Variable Names |
|
|
Result Variable Name |
一個 Object 變量存儲所有返回值 |
|
Query timeout(s) |
超時時間;默認0,代表無限時間 |
|
Limit ResultSet |
和 limit 類似作用,限制 sql 語句返回結果集的行數 |
|
Handle ResultSet |
如何定義 callable statements 返回的結果集;默認是存儲為字符串 |
後續通過各種栗子來深入理解常用字段的含義
需要自己找一個有數據庫的數據來練手哦!這裏拿的表數據如下哈
沒啥特別的,平時 sql 怎麼寫,這裏就怎麼寫
添加一個 Debug Sampler 就知道這個字段有什麼作用了
該變量是個數組,每一個元素代表一條記錄
關於通過 Variable names、Result variable name 獲取到的值如何提取,我們將在下一篇文章中詳細講解
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※帶您來了解什麼是 USB CONNECTOR ?
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!
※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※教你寫出一流的銷售文案?
斑點分析的算法非常簡單:在圖像中,相關對象的像素(也稱為前景)通過其灰度值來識別。例如,圖中示例显示了液體中的組織顆粒。這些粒子是明亮的,液體(背景)是暗的。通過選擇明亮的像素(閾值),可以很容易檢測到顆粒。在許多應用中,暗像素和亮像素的簡單條件不再成立,但結果相同可以通過額外的預處理或像素選擇/分組的替代方法來實現。
在這種情況下,斑點分析的優點是HALCON提供了大量算子使其具有極大的靈活性。此外,這些方法通常具有很高的性能。斑點分析也可以與許多其他視覺任務相結合,例如作為預處理步驟,靈活地生成交互區域。
斑點分析主要包括三個部分:
獲取圖像
分割圖像
採集圖像后,接下來的任務是選擇前景像素。這也稱為分割。結果
在HALCON中通常將此過程為Blob(二進制大對象),數據類型為區域(a region)。
提取目標特徵
在最後一步中,將計算出諸如面積(像素數),重心或方向之類的特徵
該基本概念的一個示例是以下程序,該程序屬於上述示例。在此,從文件中獲取圖像。使用閾值(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)
在許多情況下,斑點分析將比上述示例更高級。原因是混亂或不均勻的照明。此外,經常需要進行后處理,例如將元素特徵轉換為真實世界單位或結果可視化。
可以通過使用感興趣區域來加快斑點分析。搜索的斑點區域被限制越多。搜索將更快更強大。
在某些應用中,關注區域必須相對於另一個對象對齊。或者圖像本身可以對齊,例如通過旋轉或裁剪。
與對齊類似,可能需要校正圖像,例如消除鏡頭畸變或轉換圖像的參考點。
下一個重要部分是圖像的預處理。在這裏,像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維修中心
本文始發於個人公眾號: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網頁設計為架站首選
※評比南投搬家公司費用收費行情懶人包大公開
※幫你省時又省力,新北清潔一流服務好口碑
※回頭車貨運收費標準
.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
此 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++ 形式的封裝。
目前主要實現了 人臉檢測,關鍵點提取,特徵值提取,特徵值對比幾個人臉識別中的基礎接口。有了這幾個接口,可以完整的實現一套人臉識別和驗證的流程。
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# 面向對象層
五、也許…
想起 GitHub 密碼,持續更新…
刪除代碼倉庫跑路…
在 GitHub 報告Bug…
向我 發送郵件
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
※超省錢租車方案
用好數據映射,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上安裝即可。
創建數據映射的模型類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呢?
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,例如:string,int,long,GUID等,但這就需要你自己去保證這些數據不超限並且唯一。
例如,我們可以寫成:
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"
}
就是常規的數據字段,直接寫就成。
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)
}
說Decimal特殊,是因為MongoDB在早期,是不支持Decimal的。直到MongoDB v3.4開始,數據庫才正式支持Decimal。
所以,如果使用的是v3.4以後的版本,可以直接使用,而如果是以前的版本,需要用以下的方式:
[BsonRepresentation(BsonType.Double, AllowTruncation = true)]
public decimal price { get; set; }
其實就是把Decimal通過映射,轉為Double存儲。
把類作為一個數據集的一個字段。這是MongoDB作為文檔NoSQL數據庫的特色。這樣可以很方便的把相關的數據組織到一條記錄中,方便展示時的查詢。
我們在項目中添加兩個類Contact和Author:
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"
}
]
}
}
這樣的數據結構,用着不要太爽!
枚舉字段在使用時,跟類字段相似。
創建一個枚舉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"
}
日期字段會稍微有點坑。
這個坑其實並不源於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
創建完了,怎麼用?
如果你只想對某個特定映射的特定字段使用,比方只對CollectionModel的post_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跟當前時間显示完全一樣了,你統計,你分組,可以隨便霍霍了。
這個需求很奇怪。我們希望在一個Key-Value的文檔中,保存一個Key-Value的數據。但這個需求又是真實存在的,比方保存一個用戶的標籤和標籤對應的命中次數。
數據聲明很簡單:
public Dictionary<string, int> extra_info { get; set; }
MongoDB定義了三種保存屬性:Document、ArrayOfDocuments、ArrayOfArrays,默認是Document。
屬性寫法是這樣的:
[BsonDictionaryOptions(DictionaryRepresentation.ArrayOfDocuments)]
public Dictionary<string, int> 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還給出了一些映射屬性,供大家看心情使用。
這個屬性是用來改數據集中的字段名稱用的。
看代碼:
[BsonElement("pt")]
public DateTime post_time { get; set; }
在不加BsonElement的情況下,通過數據映射寫到數據集中的文檔,字段名就是變量名,上面這個例子,字段名就是post_time。
加上BsonElement后,數據集中的字段名會變為pt。
看名稱就知道,這是用來設置字段的默認值的。
看代碼:
[BsonDefaultValue("This is a default title")]
public string title { get; set; }
當寫入的時候,如果映射中不傳入值,則數據庫會把這個默認值存到數據集中。
這個屬性是用來在映射類中的數據類型和數據集中的數據類型做轉換的。
看代碼:
[BsonRepresentation(BsonType.String)]
public int favor { get; set; }
這段代表表示,在映射類中,favor字段是int類型的,而存到數據集中,會保存為string類型。
前邊Decimal轉換和枚舉轉換,就是用的這個屬性。
這個屬性用來忽略某些字段。忽略的意思是:映射類中某些字段,不希望被保存到數據集中。
看代碼:
[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地圖已可更新顯示潭子電動車充電站設置地點!!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
回顧:上一次已經把消息的布局以及樣式做好了
效果圖:
在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 ?
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!
※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※教你寫出一流的銷售文案?

對綠能產業相當關心的 Google,在分散式能源領域,當然也不會缺席。今年 5 月,Google 公布「小盒計畫(Little Box Challenge)」,若是有員工能研發出縮小版的逆變器,就能獲得 100 萬美元(約合新台幣 3,000 萬元)獎金。而最近 Google 將這項計畫對外開放,參賽者在 2015 年 7 月前都能報名參加,最後交由電機電子工程師學會(IEEE)評選出優勝者。
逆變器在綠能產業中扮演關鍵性的角色,舉凡連接太陽能板、風力發電機、電動車等電網裝置,都需要逆變器參與其中,而逆變器的主要用途就是把太陽能板接收能量後產生的直流電,轉換為交流電併入電網當中;電動車作為電力儲存裝置時,若要將電能輸出,也同樣必須藉由逆變器轉換為交流電併入電網。

逆變器體積縮小有利微電網發展 然而,隨著屋頂太陽能板裝設越來越普遍,龐大的逆變器顯得笨重又不符合效益,這也是 Google 為何如此迫切希望大幅縮小逆變器的體積與重量的原因。 Google 的「小盒計畫」希望將逆變器體積縮為現在的十分之一,相當於一台小筆電的大小;每立方英吋的功率密度須超過 50 瓦特;轉換效率最低值至少為 95%;耐高溫性的部分,須可承受攝氏 60 度的高溫,其他標準則包括一些能讓逆變器與電網連結作用的規格限制。而綜結以上這些規格,最重要的,當然還是參賽者能否縮小逆變器的體積大小了。

假如能研發出小型逆變器,Google 希望能在偏遠地區打造低成本的微電網,或在停電時,能以電動車維持穩定電力供給,讓他們在綠能產業的佈局上,又多了一項投資。 在矽谷科技企業中,Google 對綠能的投資可說是最不手軟的一家,綠能涉獵範圍也最廣泛。就如先前《科技新報》提到的,過去 5 年內,Google 在太陽能與風能等綠能上的投資,已超過 10 億美元。除了持有「大西洋風能網」項目建設中 37.5 % 的股份、投資美國離岸風力發電骨幹計畫 50 億美元外,在電網上的參與也越來越活躍。 過去幾個月以來,Google 開發名為「顛覆電網(Bottom Up Grid)」的專案,陸續招募電力或電子工程師,希望能從最根本改善電力轉換系統。看來現在除了佈局綠能產業外,與綠能息息相關的電網系統,也成為 Google 改革的目標了。 本文全文授權自《科技新報》──
本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※為什麼 USB CONNECTOR 是電子產業重要的元件?
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※台北網頁設計公司全省服務真心推薦
※想知道最厲害的網頁設計公司"嚨底家"!
※新北清潔公司,居家、辦公、裝潢細清專業服務
※推薦評價好的iphone維修中心
中汽協發佈的最新資料顯示,今年上半年中國新能源汽車生產20692輛,銷售20477輛,同比分別增長2.3倍和2.2倍,產銷量已經超過去年全年的1.7萬輛。其中純電動汽車產銷分別完成12185輛和11777輛,插電式混合動力汽車產銷分別完成8507輛和8700輛。在新政頻發、充電基礎設施和電池產業鏈發展及車企爭奪多番力量推動下,新能源汽車下半年產銷量有望呈爆發式增長。
2014年初以來中國中央政府確定了40個新能源汽車示範城市或區域,預計未來2年內累計推廣規模將超過30萬輛。而且近期北京、上海、廣東、江蘇等9個示範區域的實施細則陸續落地。補貼政策從購車、用車、配套等多環節提升新能源汽車經濟性,驅動產業快速發展。
國家機關購買新能源汽車管理辦法發佈。中國中央政府門戶網站7月13日刊登消息,《政府機關及公共機構購買新能源汽車實施方案》(下稱《方案》)印發。主要內容包括,2014年至2016年,中央國家機關以及88個新能源汽車推廣應用城市的政府機關及公共機構購買的新能源汽車占當年配備更新總量的比例不低於30%,以後逐年提高。方案明確規定至2016年公車採購中購買的新能源汽車占當年配備更新總量的比例不低於30%,這意味著新能源車迎來超過300億元的市場份額。《方案》的出臺將加快政府機關採購新能源汽車的速度。
國務院:《關於加快新能源汽車推廣應用的指導意見》。《意見》明確,要以純電驅動為新能源汽車發展的主要戰略取向,重點發展純電動汽車、插電式混合動力汽車和燃料電池汽車,以市場主導和政府扶持相結合,建立長期穩定的新能源汽車發展政策體系,創造良好發展環境,加快培育市場,促進新能源汽車產業健康發展。
發改委出臺電動汽車用電價格政策,私家車執行居民電價。為貫徹落實國務院辦公廳《關於加快新能源汽車推廣應用的指導意見》精神,利用價格杠杆促進電動汽車推廣應用,近日,國家發展改革委下發《關於電動汽車用電價格政策有關問題的通知》,確定對電動汽車充換電設施用電實行扶持性電價政策。《通知》的下發,將有利於降低電動汽車使用成本,在鼓勵消費者購買使用電動汽車、促進電動汽車推廣應用方面發揮積極作用。
中國財政部、工信部頒佈新能源汽車免征購置稅。8月6日,財政部、工信部等3部門發佈《關於免征新能源汽車車輛購置稅的公告》,這是自7月份以來,國家相關部門發佈的第四個新能源車政策。上述公告稱,自2014年9月1日至2017年12月31日,對購置的新能源汽車免征車輛購置稅;對免征車輛購置稅的新能源汽車,由工業和資訊化部、國家稅務總局通過發佈《免征車輛購置稅的新能源汽車車型目錄》實施管理。
中國政策引導給力。《政府機關及公共機構購買新能源汽車實施方案》要求政府和公共機關配建相應的充電基礎設施。充電介面與新能源汽車數量比例不低於1:1。同時,北京即將出臺《關於推進物業管理區域新能源小客車自用充電設施安裝的通知》,物業不配合充電樁建設將被罰分。此外,針對新能源車推廣,即將出臺配套檔要求,新建社區停車位配建充電樁的不低於18%,這也將作為規劃審批條件之一。充電樁建設方面,此前上海市發佈了相關辦法,鼓勵社會企業開展充電設施建設和服務,對公共充電設施的投資給予30%的補貼。
國際中德電動汽車充電項目正式啟動。7月8日,默克爾與中國工信部部長苗圩在清華大學共同拉動代表中德電動車合作專案的手柄,中德電動汽車充電項目正式啟動。根據合作協定,未來中國和德國電動車將實現充電介面標準完全統一,雙方還將簽署充電通信協定,最終實現充電設施的完全共用。這意味著未來比亞迪等中國電動車品牌,將與寶馬、奧迪等德國汽車品牌使用相同的充電設施。
中國國網或將退出城市充電設施建設,引入更多社會資本參與建設。繼開放電動汽車充換電設施市場之後,國家電網在充電設施建設領域將再退一步,退出城市中的電動汽車充電設施建設市場,專注於交通幹道上的充電設施建設。多個接近國家電網的人士透露,國家電網的這一決定已經下發各級省級電網公司。決定的主體內容即是:國網將全面退出城市充電設施建領域,引入更多社會資本參與建設,國網則全力推進交通幹道,也即城際互聯充電網路建設。
寶馬欲同賓士等競爭對手共用最新電池技術。寶馬集團日前表示,為了實現規模經濟,對於同競爭對手共用最新電池技術持開放態度。前不久寶馬和三星SDI簽約共同開發電池單元技術,並擴大電池訂單量。
富士康逾20億投資安徽設鋰電池生產線。富士康子公司鋰科科技將在安慶市經開區投資20.9億元,分期建設高分子聚合物電芯及電池組生產專案。富士康還在安徽醞釀其他投資專案,“其中會有新能源汽車方向。”富士康從2005年收購臺灣安泰電業後才正式進入汽車領域,目前主要涉及汽車電子及新能源汽車兩大板塊,分別由安泰電業及鋰科科技兩家公司主導。
松下與特斯拉達成協議,總投資60億建超級電池廠。松下與特斯拉就美國建設電動汽車電池工廠一事達成基本協議。松下的總投資額為1千億日元左右。松下已向特斯拉出資,同時向該公司的電動汽車供應鋰離子電池。電池工廠將在特斯拉主導下建設,將配合需求,分階段擴大產能,松下的初期投資預計為200億~300億日元。松下已將汽車領域定位為成長戰略的支柱,將加快擴大作為環保車核心零部件的電池業務。
三星SDI正與寶馬、大眾、克萊斯勒、福特及印度馬恒達等車企磋商有關電動車電池組供應計畫,也準備參與到中國政府大力推進的新能源車產業。三星SDI在全球電動車專案已達10餘個。同時,繼松下之後,三星SDI從2015年起也將為大眾汽車提供電池。大眾新開發的D-segment中型電動車也將採用三星電池。據瞭解,在2014年初的底特律車展上,大眾就已經採用了三星SDI的電池。三星SDI相關負責人員表示,“寶馬在中國市場快速發展其電動車專案且已初見成效,相信今後電動車電池的供貨量將會高速增長,三星與寶馬的合作也將呈現雙贏局面”。
八家汽車巨頭加盟EPRI 制定和完善電動汽車行業標準。近日八家已經涉足電動汽車領域的巨頭本田,寶馬,克萊斯勒,通用,福特,賓士,三菱和豐田宣佈同其他7家企業共同成立電力科學研究院(EPRI),來制定和完善混合動力和純電動汽車的儀錶、內部結構、充電等相關標準。近日網通社在對將推出的新電池技術進行統計後發現:全新材質電池以及現有鋰離子電池的改進,正成為電動汽車發展的趨勢,未來隨著新電池技術的推出,電動車續航里程有望達到特斯拉四倍,達到1600公里之多。
寶馬集團發佈未來戰略佈局中國市場。日前,寶馬集團在其新聞發佈會上確認,寶馬集團與華晨中國汽車控股有限公司(以下簡稱“華晨汽車”)的合資協議延長至2028年;基於這一合作基礎,寶馬集團公佈了接下來在中國市場的一系列戰略佈局,包括擴大在華產能、拓展國產產品線,以及在中國投產先進的發動機等,展示了寶馬集團對中國市場更加強勁的投入。寶馬集團對於中國市場寄予厚望,認為中國有望成為世界上最大的新能源汽車市場。目前,寶馬集團和華晨寶馬可以提供的電動汽車解決方案包括BMW i系列、採用新能源動力的BMW車型,以及之諾品牌產品,以期在中國“綠色交通”發展過程中搶佔先機。
比亞迪牽手廣汽成立新能源客車公司。8月4日晚間,比亞迪和廣汽集團同時發佈公告稱,二者將成立合資公司生產新能源客車,雙方聯手佈局新能源汽車市場。據業內分析,比亞迪攜手廣汽集團將有助於發揮二者在汽車製造銷售方面的優勢。廣汽集團固有的汽車製造能力及銷售管道將為比亞迪目前上市的新能源車型拓展銷路。比亞迪整車製造能力方面的缺陷也可借力廣汽集團彌補,公司可以專攻擴大電池產能。
陳虹醞釀前瞻技術部,上汽研發資源向新能源傾斜。上汽集團董事長陳虹要對上汽研發體系進行整合,醞釀推出前瞻性技術部,未來每年都會投入3億用於前瞻科技研究,主要涉及新能源、輕量化及車聯網三大領域。
上汽集團宣佈要與阿裡巴巴聯合造新能源車。據悉,上汽和阿裡的“互聯網汽車”從源頭上重新定義汽車和車載系統。其目標是使使用者通過汽車與網路無縫對接,徹底改變當前車載系統功能簡單、使用者體驗不佳的種種局限。
樂視與北汽或將打造“樂視電動汽車”。北汽董事長徐和誼也在密會樂視CEO賈躍亭,並向外界釋放信號表示願意“代工生產樂視汽車”。而近日。搜狐前副總編、汽車事業部總經理何毅從搜狐離職加盟樂視,未來或負責組建汽車團隊。北汽對於新能源車的發展信心滿滿,稱2年內將打造“中國的特斯拉”。
目前國內新能源汽車在售車型接近20款,預計未來兩年還將投放接近30款新車,投放速度明顯加快。隨著產品投放加快,各車企對於新能源汽車產能的準備,尤其是電池等產業鏈的配套能力進行了充分準備,特斯拉完成消費者教育,消費者開始考慮購買新能源汽車,分時租賃等新商業模式引進,進一步促進新能源銷量提升。新能源政策的不斷落地,及特斯拉的鯰魚攪動效應,無論是中國資本市場還是實體企業,都掀起了一股新能源汽車熱。
在此背景下,將於11月12日-14日在中國北京召開。大會由中國汽車工業協會和決策者會議聯合主辦,交通大學汽車工程研究院協辦,得到日本汽車工業協會和眾多一線整車商鼎力支持。主題為新增長局勢下的中國新能源汽車產業動態聚焦,專注於新能源整車商項目、戰略規劃以及對核心設備的需求、對於新能源汽車電氣以及動力系統、電控系統、智慧汽車創新、動力電池沖換電基建建設系統的案例分析以及核心技術的全面探討。作為中國電動汽車行業領先的峰會,新能源汽車峰會歷經4年發展,無論是參會企業,還是贊助商數量都位居國內前列。歡迎政府機關行業協會、海內外汽車生產商、科研單位、大學院校、汽車電池生產商、核心零部件提供商、整體服務解決方案提供商及其他服務提供者來電諮詢。
600+業內權威專家業內專業人士,400+專業參展觀眾,來自于320+行業知名企業單位,23+個國家
120+位參會代表來自語全球領先整車商,以及110+核心零部件提供商企業代表
40+ 知名權威發言人,為您敘說新能源汽車行業熱點資訊
16+ 小時商務交流機會,貫穿於雞尾酒會,小組討論,交流午宴及提問互動環節
6 場專題討論,為您深度解析關注行業熱點
5 年歷史,鑄就行業年度盛會
實效性:展會期間將進行一對一會談、頒獎典禮,突出實效和品牌,做大做深供求雙方專業化配對洽談工作,為廣大業內人士及下游應用企業提供集中領略行業最新趨勢的機會。
品牌化:作為中國電動汽車行業最早商業化運作的峰會,歷經四年發展,無論是參會數量,還是贊助商數量,在國內同行業峰會中,都是雄踞前列,深受業界同仁的認可和讚揚,其知名度和美譽度在業內廣為流傳。成為中國電動汽車行業名符其實的第一會。
國際化:往屆嘉賓有來自美國、日本、韓國、德國、丹麥、義大利、臺灣等國家和地區的國際企業參會,已經成為電動汽車行業的資訊分享、技術交流、貿易採購平臺。
專業化:是國內目前唯一的電動汽車行業的專業峰會之一,一年一屆。內容包含電動汽車(含混合動力)的整車、零部件、管理系統、充電站及相關配套設施等. 將吸引來自中國電動汽車企業超320家企業巨頭高層參觀,雲集政府機關行業協會,整車商,大學院校及研究院,零部件百強企業,核心技術設備提供商。
全媒體曝光:主辦方將基於網站、雜誌、微信、微博等多媒體平臺,在展前、展中、展後分別做全方位即時報導,預計將會實現超過10萬人次覆蓋。
年度優秀電動汽車電池生廠商獎
年度優秀新能源汽車諮詢公司獎
年度優秀新能源汽車解決方案提供商獎
終身成就獎
企業社會責任商獎
優秀核心零部件提供商獎
優秀新能源汽車服務商獎
優秀新能源汽車技術提供商獎
“是一個尋找合作夥伴的理想場所,本次參展讓我們受益頗多,明年會一如既往支援綠色汽車大會!”—— Shinry Technologies Co., Ltd
“通過很好的管道將我們的產品展現在客戶面前,非常滿意。”—— AGC Automtive
“綠色汽車大會提供一個行業人士交流的平臺,參會參展企業眾多,達到了我們的參展期望值。”—— Thermal Hazard Technology
“我們同時參加了峰會和展覽,非常值得推薦的活動!”—— 捷特科
“非常滿意,無論是活動內容,參會嘉賓,還是規模層次都很出色,會推薦個同事。”—— W.E.T. Automotive Systems (China) Ltd
展會網站:
連絡人: 邱小姐(Elva Qiu)
電話 : +86 21 63931899-2041
手機:+86 18930215786
郵箱:
QQ:1147789586
本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能
※台北網頁設計公司這麼多該如何選擇?
※智慧手機時代的來臨,RWD網頁設計為架站首選
※評比南投搬家公司費用收費行情懶人包大公開
※幫你省時又省力,新北清潔一流服務好口碑
※回頭車貨運收費標準
特斯拉當家電動車 Model S 不再完美無缺,去年給予 Model S 最高評級的消費者評鑒雜誌《Consumer Reports》點出 Model S 仍許多有小缺陷待改進。 《Consumer Reports》指出,Model S 多在跑超過一萬哩後出現問題,例如里程數超過 1.2 萬哩後,中央控制螢幕在會有反白的狀況,使多項功能無法操作。此外,還有車頂會發出異常噪音,前置行李箱蓋會自動開啟等問題。 Model S 去年 5 月在《Consumer Reports》評鑑中拿下 99 分(滿分 100 分),創史上最高分,之後並在 11 月獲選為年度 10 大好車第一名。 對此,特斯拉執行長 Elon Musk 7 月 31 日曾坦承,較早一批出廠的 Model S 的確有些生產上的瑕疵,但目前出廠的新車多已作修正。 (圖片來源:)
本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
※超省錢租車方案