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維修中心

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  ?

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

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

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

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

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

Google 懸賞 3,000 萬,縮小逆變器體積至十分之一

 

對綠能產業相當關心的 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維修中心

Tesla Model S 遭爆有瑕疵,完美形象破功

    特斯拉當家電動車 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/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

Tesla Model 3 有對手了!LG Chem 研發長程電動車電池

Tesla 又有新對手了,南韓 LG Chem 目前也在開發最新電池技術,且無論航程或售價均足以與特斯拉研發中的 Model 3 匹敵。   Model S 是目前市面上唯一航程可以達 200 英哩的電動車,但即使是最低售價也高達 7.1 萬美元。特斯拉計畫在 2017 年推出 Model 3,售價將壓低至每輛 3.5 萬美元,但售價仍然不敵 LG Chem 正將推出的電動車。   LG Chem 位於美國的研究單位主管 Prabhakar Patil 證實,正在開發續航力 200 英哩的電池,目前已有幾家汽車業者表示對該項技術有興趣,產品最快於 2017 年就可上市,售價將介於 3 至 3.5 萬美元間。   Patil 雖然沒有明講,但外界猜想對該項技術有興趣的汽車業者,一定包含通用汽車。LG Chem 目前已是通用油電混合動力車 Volt 的電池供應商,且通用之前表示,將推出續航力 200 英哩的電動車,起價最低 3 萬美元,符合 Patil 設定的條件。

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

【其他文章推薦】

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

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

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

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

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

鴻海再加碼大陸 布局電動車市場

鴻海 13 日對外表示,將發行上限 240 億元無擔保普通公司債募資,以償還短期負債。另外,將透過轉投資第三地,赴大陸投資重慶元創汽車整線集成公司約新台幣 4.9 億元,強化車用相關布局。   重慶元創汽車整線集成公司主要從事汽車模、夾、檢具的設計開發與製造。市場人士預期,鴻海該項投資主要為電動車事業播種,未來有機會整合其自動化相關應用在汽車生產線上。   鴻海近期對電動車相當有興趣,市場預期,鴻海要將製造消費性電子的技術,應用在生產電動車上,讓電動車的價格更親民。

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

【其他文章推薦】

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

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

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

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

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

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

挽救電動車產業 印度政府再次開啟投資

    印度政府日前通過一項 23 億美元的電動車產業振興計劃──2020 國家電動車行動計劃(NEMMP 2020 plan),打算重新帶起終止補貼後明顯下滑的電動車銷量。   印度政府過去對電動車的銷售補貼相當成功,全盛時期銷售量甚至超過 10 萬輛,但當新暨再生能源部 MNRE 在 2012 年 3 月停止補貼後,銷售量開始下滑,到了 2013 年,只剩 2.1 萬輛。幾年前,印度有 65 家電動車製造商,現在只剩 10家還在營運。   NEMMP 計劃目標是在 2020 年有 6 到 7 百萬輛電動車上路,而其中 4 到 5 百萬輛為二輪車,若達到該銷量數字,還能節省超過 200 萬噸燃料的使用。   雖然大多數電動車公司在過去兩年來已停止營運,但假如 NEMMP 2020 可在接下來的 3 或 4 個月間實施,預期印度電動車產業仍可復甦。     (圖片來源:)

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

【其他文章推薦】

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

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

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

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

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

排氣不符規定 德要求戴姆勒召回數十萬輛柴油車

摘錄自2019年10月12日中央通訊社德國報導

德國汽車製造商戴姆勒(Daimler)12日表示,聯邦交通管理局(KBA)以違反排氣規定為由,要求召回數十萬輛柴油車。戴姆勒說,這波召回的數量估計將達6位數,並表示會「與有關當局合作」。

公司在聲明中表示,這次的召回涉及至少26萬輛Sprinter廂型車,並表示所有車輛都在2016年6月之前生產。

德國福斯汽車(Volkswagen)2015年承認在全球1100萬輛柴油車上安裝非法「減效裝置」(defeat device),包括歐洲850萬輛及美國60萬輛車,德國有關當局隨後展開這起造假醜聞「柴油門」調查。自從「柴油門」4年前爆發,這場排放造假醜聞就對汽車產業造成巨大後果。部分汽車排放與呼吸道及心血管疾病有關的有害氮氧化物,高達法律規定數值的40倍。

據德國媒體報導,聯邦交通管理局本月稍早展開調查,懷疑戴姆勒安裝「非法軟體」,試圖讓車輛在實驗室測試時的排汙量看起來比實際低。戴姆勒早已召回約70萬輛車,包括德國境內就有近30萬輛車被召回。

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

【其他文章推薦】

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

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

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

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

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

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