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

新能源汽車利好消息層出不窮 下半年產銷量有望爆發式增長—第五屆新能源汽車峰會暨展覽會

中汽協發佈的最新資料顯示,今年上半年中國新能源汽車生產20692輛,銷售20477輛,同比分別增長2.3倍和2.2倍,產銷量已經超過去年全年的1.7萬輛。其中純電動汽車產銷分別完成12185輛和11777輛,插電式混合動力汽車產銷分別完成8507輛和8700輛。在新政頻發、充電基礎設施和電池產業鏈發展及車企爭奪多番力量推動下,新能源汽車下半年產銷量有望呈爆發式增長。

新政4連發護航

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萬人次覆蓋。

【2014年新能源汽車頒獎典禮獎項設置】

年度優秀電動汽車電池生廠商獎
年度優秀新能源汽車諮詢公司獎
年度優秀新能源汽車解決方案提供商獎
終身成就獎
企業社會責任商獎
優秀核心零部件提供商獎
優秀新能源汽車服務商獎
優秀新能源汽車技術提供商獎

【展商評價】

“是一個尋找合作夥伴的理想場所,本次參展讓我們受益頗多,明年會一如既往支援綠色汽車大會!”—— 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網頁設計為架站首選

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

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

※回頭車貨運收費標準

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/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

美電動車銷量 7 月破萬持續看漲

美國汽車媒體 GreenCarReports 報導,由於日產和通用兩家公司的聆風和雪佛蘭沃藍達在美國市場銷量表現不俗,美國 7 月插電式電動汽車銷量繼續看漲。聆風和雪佛蘭沃藍達的月度銷售量相加第二次超過 5000 台,上次月度銷量超過 5000 台是去年 8 月。    數據顯示,2014 年 7 月美國插電式電動車整體銷量為 1 萬 533 輛。其中日產聆風當月在美國本土市場的銷量為 3,019 輛,位居第一;其次是雪佛蘭沃藍達,銷量為 2,020 輛,取代了此前豐田普銳斯季軍的位置;豐田普銳斯當季銷量為 1,371 輛。   不難發現,美國新能源汽車市場的主要車型相對集中。日產的聆風、雪佛蘭沃藍達和豐田的普銳斯一直占據主導地位,也是美國傳統汽車品牌中新能源汽車領域的三大陣地。事實上,日產聆風電動車 7 月在美國市場的銷量並不是歷史最高銷量。其聆風電動車銷量的高峰是今年 5 月,共計銷售 3,117 輛,同比上漲 46%。

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

【其他文章推薦】

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

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

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

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

※超省錢租車方案