using HalconDotNet; using Newtonsoft.Json; using OpenCvSharp; using System.Collections.Concurrent; using System.Diagnostics; using System.Text; using static LB_VisionProcesses.Alogrithms.OpenCvSharp.FindModelTool; using Point = OpenCvSharp.Point; using Size = OpenCvSharp.Size; namespace LB_VisionProcesses.Alogrithms.OpenCvSharp { [Process("OpenCvSharp_模板匹配工具", Category = "OpenCvSharp工具", Description = "创建OpenCvSharp_模板匹配工具")] public class FindModelTool : TAlgorithm { public enum ModelType { 灰度匹配 }; public OModel ModelID = new OModel(); public FindModelTool() { strProcessClass = "LB_VisionProcesses.Alogrithms.OpenCvSharp.FindModelTool"; strProcessName = "OpenCvSharp_模板匹配工具"; Params.Inputs.Add("ModelType", ModelType.灰度匹配); //AngleStart:搜索时的起始角度[需要转换为弧度] Params.Inputs.Add("AngleStart", -5.0); //AngleExtent:搜索时的角度范围,0表示无角度搜索[需要转换为弧度] Params.Inputs.Add("AngleExtent", 10.0); //AngleStep:角度步长--弧度[角度步长 >= 0和角度步长 <= pi / 16] Params.Inputs.Add("AngleStep", "auto"); Params.Inputs.Add("ScaleRMin", 0.9); Params.Inputs.Add("ScaleRMax", 1.1); Params.Inputs.Add("ScaleCMin", 0.9); Params.Inputs.Add("ScaleCMax", 1.1); //MinScore:被找到的模板最小分数 Params.Inputs.Add("MinScore", 0.5); //NumMatches:要找到的模板最多的实例数,0则找到所有可能的匹配 Params.Inputs.Add("NumMatches", 0); //MaxOverlap:允许找到的模型实例的最大重叠比例, 建议值:0.0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0 Params.Inputs.Add("MaxOverlap", 0.2); //SubPixel:计算精度的设置 //'none' 不适用亚像素,最大误差为半个像素 //'interpolation' 差值得到亚像素精度 //'least_squares', 'least_squares_high', 'least_squares_very_high' //'max_deformation 1', 'max_deformation 2', 'max_deformation 3', 'max_deformation 4' Params.Inputs.Add("SubPixel", "none"); //NumLevels:搜索时金字塔的层级,0表示不使用金字塔 Params.Inputs.Add("NumLevels", 0); //Greediness:贪婪度,搜索启发式,一般都设为2,越小速度越快,容易出现找不到的情况 Params.Inputs.Add("Greediness", 2); Params.Inputs.Add("ResultType", ""); Params.Inputs.Add("MinCount", 0); Params.Inputs.Add("MaxCount", 9999); Params.Outputs.Add("CenterX", new List()); Params.Outputs.Add("CenterY", new List()); Params.Outputs.Add("Phi", new List()); Params.Outputs.Add("Score", new List()); Params.Outputs.Add("Count", 0); } private static readonly object lockObj = new object(); /// /// 运行算子 /// public override void TAlgorithmMain() { lock (lockObj) { try { if (InputImage == null) { Msg = "输入图片为空"; Result = false; return; } if (!(InputImage is Mat)) { Msg = "输入图片格式不为Mat"; Result = false; return; } #region 裁剪区域 object DomainImage = null; if (!ReduceDomainImage(InputImage, ref DomainImage)) { Msg = "裁剪区域失败"; Result = false; return; } Mat hoDomainImage = DomainImage as Mat; //判断是否为灰度图 try { if (hoDomainImage.Channels() != 1) Cv2.CvtColor(hoDomainImage, hoDomainImage, ColorConversionCodes.RGB2GRAY); //转换后再次检查是否为灰度图 if (hoDomainImage.Channels() != 1) { Msg = "输入图片不为灰度图"; Result = false; return; } } catch { Msg = "输入图片不为灰度图且转换失败"; Result = false; return; } #endregion //判断是否有模板 if (ModelID.hoImage == null || ModelID.hoImage.Empty()) { Msg = "未创建模板"; Result = false; return; } #region 算子逻辑 Record = new ObjectRecord(); ModelType type = ModelID.Type; int ho_ModelWidth = ModelID.Width; int ho_ModelHeight = ModelID.Height; double AngleStart = Convert.ToDouble(Params.Inputs["AngleStart"]); double AngleExtent = Convert.ToDouble(Params.Inputs["AngleExtent"]); double MinScore = Convert.ToDouble(Params.Inputs["MinScore"]); int NumMatches = ProcessParams.ConvertToInt32(Params.Inputs["NumMatches"]); double MaxOverlap = Convert.ToDouble(Params.Inputs["MaxOverlap"]); string SubPixel = ProcessParams.ConvertToString(Params.Inputs["SubPixel"]); string NumLevels = ProcessParams.ConvertToString(Params.Inputs["NumLevels"]); double Greediness = Convert.ToDouble(Params.Inputs["Greediness"]); double ScaleRMin = Convert.ToDouble(Params.Inputs["ScaleRMin"]); double ScaleRMax = Convert.ToDouble(Params.Inputs["ScaleRMax"]); double ScaleCMin = Convert.ToDouble(Params.Inputs["ScaleCMin"]); double ScaleCMax = Convert.ToDouble(Params.Inputs["ScaleCMax"]); double ScaleMin = ScaleCMin > ScaleRMin ? ScaleRMin : ScaleCMin; double ScaleMax = ScaleCMax > ScaleRMax ? ScaleRMax : ScaleCMax; double ScaleStep = (ScaleMax - ScaleMin) / Greediness; List results = new List(); switch (type) { case ModelType.灰度匹配: // 执行灰度匹配 results = MultiAngleTemplateMatch(hoDomainImage, ModelID.hoImage, AngleStart, AngleStart + AngleExtent, AngleExtent / Greediness , ScaleMin, ScaleMax, ScaleStep, MinScore, MaxOverlap); break; default: Msg = "不支持的模板"; Result = false; return; } #endregion #region 结果处理 List CenterX = new List(); List CenterY = new List(); List Phi = new List(); List Score = new List(); for (int i = 0; i < results.Count; i++) { CenterX.Add(results[i].Center.X); CenterY.Add(results[i].Center.Y); Phi.Add(results[i].Angle / 180 * Math.PI); HOperatorSet.GenRectangle2(out HObject hRectangle, CenterY[i], CenterX[i], Phi[i], ho_ModelWidth / 2, ho_ModelHeight / 2); ((ObjectRecord)Record).AddRecord(hRectangle); } Params.Outputs["CenterX"] = CenterX; Params.Outputs["CenterY"] = CenterY; Params.Outputs["Phi"] = Phi; Params.Outputs["Score"] = Score; Params.Outputs["Count"] = Score.Count; #endregion #region 生成OutputImage给后续处理 try { OutputImage = hoDomainImage; } catch (Exception ex) { Msg = "生成OutputImage失败,原因是:" + ex.ToString(); Result = false; return; } #endregion if (Msg == "运行超时") { Result = false; Record.ChangeAll2False(); return; } int MinCount = ProcessParams.ConvertToInt32(Params.Inputs["MinCount"]); int MaxCount = ProcessParams.ConvertToInt32(Params.Inputs["MaxCount"]); if (CenterX.Count < MinCount || CenterX.Count > MaxCount) { Msg = string.Format("结果个数超出范围({0},{1})", MinCount, MaxCount); Record.ChangeAll2False(); Result = false; return; } Msg = "运行成功"; Result = true; return; } catch (Exception ex) { Msg = "运行失败,原因是:" + ex.ToString().TrimEnd(); OutputImage = null; Result = false; return; } finally { if (!Result) { Params.Outputs["CenterX"] = 0; Params.Outputs["CenterY"] = 0; Params.Outputs["Phi"] = 0; Params.Outputs["Score"] = 0; Params.Outputs["Count"] = 0; } bCompleted = true; #region 内存释放 #endregion } } } /// /// 带角度的灰度匹配 /// /// /// /// /// /// /// /// /// /// /// /// public static List MultiAngleTemplateMatch( Mat source, Mat template, double startAngle = 0, double endAngle = 360, double angleStep = 10, double scaleStart = 0.5, double scaleEnd = 2.0, double scaleStep = 0.1, double threshold = 0.8, double NMS = 0.3) { var allMatches = new List(); // 生成角度序列 var angles = GenerateAngleSequence(startAngle, endAngle, angleStep); // 生成缩放序列 var scales = GenerateScaleSequence(scaleStart, scaleEnd, scaleStep); // 多线程加速方法[存在匹配结果不一致的情况] //allMatches = MultiScaleAngleTemplateMatchFullParallel(source, template, scales, angles, threshold); foreach (double scale in scales) { // 缩放模板 Mat scaledTemplate = new Mat(); if (scale != 1.0) { Cv2.Resize(template, scaledTemplate, new Size(template.Width * scale, template.Height * scale)); } else scaledTemplate = template.Clone(); if (scaledTemplate.Width > source.Width || scaledTemplate.Height > source.Height) continue; foreach (double angle in angles) { // 旋转模板 using (Mat rotatedTemplate = RotateImageForTemplateMatching(scaledTemplate, angle)) { if (rotatedTemplate.Empty()) continue; using (Mat resultMatrix = new Mat()) { Cv2.MatchTemplate(source, rotatedTemplate, resultMatrix, TemplateMatchModes.CCoeffNormed); // 查找最佳匹配位置 double minVal, maxVal; Point minLoc, maxLoc; Cv2.MinMaxLoc(resultMatrix, out minVal, out maxVal, out minLoc, out maxLoc); if (maxVal >= threshold) { var rotatedRect = new RotatedRect( new Point2f(maxLoc.X + rotatedTemplate.Width / 2.0f, maxLoc.Y + rotatedTemplate.Height / 2.0f), new Size2f(rotatedTemplate.Width, rotatedTemplate.Height), (float)angle); allMatches.Add(new MatchResult { RotatedRect = rotatedRect, Score = maxVal, Scale = scale }); } } } } } // 应用旋转矩形的非极大值抑制 return ApplyRotatedNMS(allMatches, NMS); } public static List MultiScaleAngleTemplateMatchFullParallel( Mat source, Mat template, double[] scales, double[] angles, double threshold = 0.8) { var allMatches = new ConcurrentBag(); // 预处理源图像 // 创建所有尺度-角度组合 var combinations = from scale in scales from angle in angles select new { Scale = scale, Angle = angle }; Parallel.ForEach(combinations, combination => { try { ProcessScaleAngleCombination(source, template, combination.Scale, combination.Angle, threshold, allMatches); } catch (Exception ex) { Debug.WriteLine($"处理尺度 {combination.Scale}, 角度 {combination.Angle} 时出错: {ex.Message}"); } }); return allMatches.ToList(); } private static void ProcessScaleAngleCombination(Mat source, Mat template, double scale, double angle, double threshold, ConcurrentBag allMatches) { Mat scaledTemplate = new Mat(); // 缩放 if (scale != 1.0) { Cv2.Resize(template, scaledTemplate, new Size(template.Width * scale, template.Height * scale)); } else { scaledTemplate = template.Clone(); } if (scaledTemplate.Width > source.Width || scaledTemplate.Height > source.Height) return; // 旋转 using (Mat rotatedTemplate = RotateImageForTemplateMatching(scaledTemplate, angle)) { if (rotatedTemplate.Empty()) return; using (Mat resultMatrix = new Mat()) { Cv2.MatchTemplate(source, rotatedTemplate, resultMatrix, TemplateMatchModes.CCoeffNormed); double minVal, maxVal; Point minLoc, maxLoc; Cv2.MinMaxLoc(resultMatrix, out minVal, out maxVal, out minLoc, out maxLoc); if (maxVal >= threshold) { var rotatedRect = new RotatedRect( new Point2f(maxLoc.X + rotatedTemplate.Width / 2.0f, maxLoc.Y + rotatedTemplate.Height / 2.0f), new Size2f(rotatedTemplate.Width, rotatedTemplate.Height), (float)angle); allMatches.Add(new MatchResult { RotatedRect = rotatedRect, Score = maxVal, Scale = scale }); } } } } public static Mat RotateImageForTemplateMatching(Mat template, double angle, double scale = 1.0) { if (template.Empty()) return new Mat(); try { double radians = angle * Math.PI / 180.0; double sin = Math.Abs(Math.Sin(radians)); double cos = Math.Abs(Math.Cos(radians)); int newWidth = (int)Math.Ceiling((template.Width * cos + template.Height * sin) * scale); int newHeight = (int)Math.Ceiling((template.Width * sin + template.Height * cos) * scale); // 确保使用相同的类型创建画布 Mat canvas = new Mat(newHeight, newWidth, template.Type(), Scalar.Black); int xOffset = (newWidth - template.Width) / 2; int yOffset = (newHeight - template.Height) / 2; Rect roi = new Rect(xOffset, yOffset, template.Width, template.Height); using (Mat canvasRoi = new Mat(canvas, roi)) { template.CopyTo(canvasRoi); } Point2f center = new Point2f(newWidth / 2.0f, newHeight / 2.0f); using (Mat rotationMatrix = Cv2.GetRotationMatrix2D(center, angle, scale)) { Mat rotated = new Mat(); Cv2.WarpAffine(canvas, rotated, rotationMatrix, new Size(newWidth, newHeight), InterpolationFlags.Linear, BorderTypes.Constant, Scalar.Black); // 确保输出类型与输入一致 if (rotated.Type() != template.Type()) { Mat converted = new Mat(); rotated.ConvertTo(converted, template.Type()); rotated.Dispose(); canvas.Dispose(); return converted; } canvas.Dispose(); return rotated; } } catch (Exception ex) { Debug.WriteLine($"旋转图像时出错: {ex.Message}"); return new Mat(); } } private static List ApplyRotatedNMS(List matches, double overlapThreshold) { var results = new List(); var remainingMatches = new List(matches); while (remainingMatches.Count > 0) { // 取分数最高的匹配 var bestMatch = remainingMatches[0]; results.Add(bestMatch.RotatedRect); remainingMatches.RemoveAt(0); // 移除与最佳匹配重叠度高的其他匹配 for (int i = remainingMatches.Count - 1; i >= 0; i--) { double overlap = CalculateRotatedOverlap(bestMatch.RotatedRect, remainingMatches[i].RotatedRect); if (overlap > overlapThreshold) { remainingMatches.RemoveAt(i); } } } return results; } public class MatchResult { public RotatedRect RotatedRect { get; set; } public double Score { get; set; } public double Scale { get; set; } } private static double CalculateRotatedOverlap(RotatedRect rect1, RotatedRect rect2) { // 计算两个旋转矩形的交并比 Point2f[] vertices1 = rect1.Points(); Point2f[] vertices2 = rect2.Points(); // 使用轮廓面积计算重叠度 using (Mat img1 = new Mat(1000, 1000, MatType.CV_8UC1, Scalar.Black)) using (Mat img2 = new Mat(1000, 1000, MatType.CV_8UC1, Scalar.Black)) { // 绘制第一个矩形 Point[] points1 = vertices1.Select(p => new Point(p.X + 500, p.Y + 500)).ToArray(); Cv2.FillConvexPoly(img1, points1, Scalar.White); // 绘制第二个矩形 Point[] points2 = vertices2.Select(p => new Point(p.X + 500, p.Y + 500)).ToArray(); Cv2.FillConvexPoly(img2, points2, Scalar.White); // 计算交集 using (Mat intersection = new Mat()) { Cv2.BitwiseAnd(img1, img2, intersection); double intersectionArea = Cv2.CountNonZero(intersection); double unionArea = Cv2.CountNonZero(img1) + Cv2.CountNonZero(img2) - intersectionArea; return intersectionArea / unionArea; } } } private static double[] GenerateAngleSequence(double start, double end, double step) { var angles = new List(); for (double angle = start; angle < end; angle += step) { angles.Add(angle); } return angles.ToArray(); } private static double[] GenerateScaleSequence(double start, double end, double step) { var scales = new List(); for (double scale = start; scale <= end; scale += step) { scales.Add(scale); } return scales.ToArray(); } public bool CreateModel(Mat Template, ModelType modelType = ModelType.灰度匹配, string NumLevels = "auto" , double AngleStart = -5, double AngleExtent = 10, string AngleStep = "auto" , double ScaleRMin = 0.9, double ScaleRMax = 1.1, string ScaleRStep = "auto" , double ScaleCMin = 1.1, double ScaleCMax = 0.9, string ScaleCStep = "auto" , string Optimization = "none", string Metric = "ignore_local_polarity" , string Contrast = "auto", int MinContrast = 10) { try { if (Template == null || Template.Empty()) Template = new Mat(); #region 参数介绍 //Template: : //reduce_domain后的模板图像 //NumLevels ,//金字塔的层数,可设为“auto”或0—10的整数 //AngleStart ,//模板旋转的起始角度 //AngleExtent ,//模板旋转角度范围, >=0 //AngleStep ,//旋转角度的步长, >=0 and <=pi/16 //Optimization ,//设置模板优化和模板创建方法 //Metric , //匹配方法设置 //Contrast ,//设置对比度 //MinContrast // 设置最小对比度 #endregion switch (modelType) { case ModelType.灰度匹配: //HOperatorSet.CreateLocalDeformableModel(Template, NumLevels == "auto" ? NumLevels : Convert.ToInt16(NumLevels) // , AngleStart, AngleExtent, AngleStep == "auto" ? AngleStep : Convert.ToDouble(AngleStep) // , ScaleRMin, ScaleRMax, ScaleRStep == "auto" ? ScaleRStep : Convert.ToDouble(ScaleRStep) // , ScaleCMin, ScaleCMax, ScaleCStep == "auto" ? ScaleCStep : Convert.ToDouble(ScaleCStep) // , Optimization, Metric // , Contrast, MinContrast // , new HTuple(), new HTuple() // , out ModelID.hvModel); ModelID.hoImage = Template.Clone(); ModelID.Type = ModelType.灰度匹配; return true; default: //HOperatorSet.CreateAnisoShapeModel(Template, NumLevels == "auto" ? NumLevels : Convert.ToInt16(NumLevels) // , AngleStart, AngleExtent, AngleStep == "auto" ? AngleStep : Convert.ToDouble(AngleStep) // , ScaleRMin, ScaleRMax, ScaleRStep == "auto" ? ScaleRStep : Convert.ToDouble(ScaleRStep) // , ScaleCMin, ScaleCMax, ScaleCStep == "auto" ? ScaleCStep : Convert.ToDouble(ScaleCStep) // , Optimization, Metric // , Contrast, MinContrast, out ModelID.hvModel); ModelID.hoImage = Template.Clone(); ModelID.Type = ModelType.灰度匹配; return true; } } catch { return false; } } /// /// 加载算法 /// /// 完整路径带.json /// public override bool Load(string fullPath) { try { if (string.IsNullOrEmpty(fullPath)) return false; if (!fullPath.Contains(".json")) { Debug.WriteLine("文件路径不完整"); return false; } if (fullPath.StartsWith(".\\")) { // 判断原字符串长度是否大于等于2,避免越界 if (fullPath.Length >= 2) { // 替换开头两个字符 fullPath = Application.StartupPath + fullPath.Substring(2); Debug.WriteLine($"修改后的字符串: {fullPath}"); } } // 获取不带文件名的目录路径 string directoryPath = Path.GetDirectoryName(fullPath); strProcessName = Path.GetFileNameWithoutExtension(fullPath); // 修正真实路径,模板匹配工具其路径为一个文件夹,而不是一个文件 fullPath = directoryPath + "\\" + strProcessName + "\\" + strProcessName + ".json"; if (!File.Exists(fullPath)) { Debug.WriteLine("文件不存在创建空文件"); ModelID = new OModel(fullPath, ModelType.灰度匹配, strProcessName); Save(directoryPath); ModelID.Save(fullPath, ModelID.Type); return true; } string strJson = string.Empty; using (StreamReader streamReader = new StreamReader(fullPath, Encoding.UTF8)) { strJson = streamReader.ReadToEnd(); streamReader.Close(); } Params = JsonConvert.DeserializeObject(strJson); if (Params == null) return false; // 反序列化后修复数据 Params.FixDeserializedData(); strProcessName = Path.GetFileNameWithoutExtension(fullPath); if (!Enum.TryParse(Params.Inputs["ModelType"].ToString(), out ModelType modelType)) return false; if (!ModelID.Load(fullPath, modelType)) return false; return true; } catch { return false; } } /// /// 保存算法 /// /// 不带.json /// public override bool Save(string filePath) { try { // 修正真实路径,模板匹配工具其路径为一个文件夹,而不是一个文件 filePath += ("\\" + strProcessName); Params.Inputs.Add("ModelType", ModelID.Type.ToString()); if (!base.Save(filePath) || !ModelID.Save(filePath + "\\" + strProcessName + ".json", ModelID.Type)) return false; return true; } catch { return false; } } } public class OModel { public OModel(string modelName = "") { ModelName = modelName; } public OModel(string modelFullPath, ModelType modelType, string modelName) { ModelFullPath = modelFullPath; ModelName = modelName; Type = modelType; switch (modelType) { case ModelType.灰度匹配: Load(ModelFullPath, modelType); break; default: Load(ModelFullPath, modelType); break; } } /// /// 模板路径 /// public string ModelFullPath = "C:\\MyVisionModel\\ModelName.temp"; /// /// 模板名称 /// public string ModelName { get { return Path.GetFileNameWithoutExtension(ModelFullPath); } set { // 获取文件的扩展名(包括点) string fileExtension = Path.GetExtension(ModelFullPath); // 获取文件所在的文件夹路径 string directoryPath = Path.GetDirectoryName(ModelFullPath); ModelFullPath = directoryPath + "\\" + value + fileExtension; } } /// /// 模板图片 /// public Mat hoImage; public int Width { get { if (hoImage == null || hoImage.Empty()) return 0; return hoImage.Width; } } public int Height { get { if (hoImage == null || hoImage.Empty()) return 0; return hoImage.Height; } } /// /// 模板类型 /// public ModelType Type = ModelType.灰度匹配; /// /// 加载模板(带.json) /// /// 完整路径带.json /// public bool Load(string fullPath, ModelType modelType) { try { if (string.IsNullOrEmpty(fullPath)) return false; string filePath = Path.GetDirectoryName(fullPath); ModelName = Path.GetFileNameWithoutExtension(fullPath); ModelFullPath = filePath + "\\" + ModelName; ModelFullPath += ".model"; Type = modelType; //switch (modelType) //{ // case ModelType.局部变形模板: // ModelFullPath += ".dfm"; // if (File.Exists(ModelFullPath)) // HOperatorSet.ReadDeformableModel(ModelFullPath, out hvModel); // break; // case ModelType.各向异形模板: // default: // if (File.Exists(ModelFullPath)) // HOperatorSet.ReadShapeModel(ModelFullPath, out hvModel); // break; //} string ImageFileName = Path.GetFileNameWithoutExtension(fullPath); string ImageFullPath = filePath + "\\" + ImageFileName + ".bmp"; if (File.Exists(ImageFullPath)) hoImage = Cv2.ImRead(ImageFullPath); if (hoImage == null || hoImage.Empty()) return false; if (hoImage.Channels() != 1) Cv2.CvtColor(hoImage, hoImage, ColorConversionCodes.RGB2GRAY); return true; } catch { Type = ModelType.灰度匹配; return false; } } /// /// 保存模板(路径带.model) /// /// 带.model /// public bool Save(string fullPath) { try { if (string.IsNullOrEmpty(fullPath)) return false; string filePath = Path.GetDirectoryName(fullPath); //判断文件夹是否存在 if (!Directory.Exists(filePath)) { try { Directory.CreateDirectory(filePath); } catch (Exception) { } } ModelName = Path.GetFileNameWithoutExtension(fullPath); // 使用 Path.GetExtension 提取扩展名 //switch (Type) //{ // case ModelType.局部变形模板: // HOperatorSet.WriteDeformableModel(hvModel, fullPath); // break; // case ModelType.各向异形模板: // default: // HOperatorSet.WriteShapeModel(hvModel, fullPath); // break; //} string ImageFileName = Path.GetFileNameWithoutExtension(fullPath); string ImageFullPath = filePath + "\\" + ImageFileName + ".bmp"; Cv2.ImWrite(ImageFullPath, hoImage); //HOperatorSet.WriteImage(hoImage, "bmp", 0, ImageFullPath); return true; } catch { return false; } } /// /// 保存模板(路径带.json) /// /// 带.json /// public bool Save(string fullPath, ModelType modelType) { try { if (string.IsNullOrEmpty(fullPath)) return false; string filePath = Path.GetDirectoryName(fullPath); //判断文件夹是否存在 if (!Directory.Exists(filePath)) { try { Directory.CreateDirectory(filePath); } catch (Exception) { } } ModelName = Path.GetFileNameWithoutExtension(fullPath); ModelFullPath = filePath + "\\" + ModelName + ".model"; Type = modelType; //switch (modelType) //{ // case ModelType.各向异形模板: // HOperatorSet.WriteDeformableModel(hvModel, ModelFullPath); // break; // default: // HOperatorSet.WriteShapeModel(hvModel, ModelFullPath); // break; //} string ImageFileName = Path.GetFileNameWithoutExtension(fullPath); string ImageFullPath = filePath + "\\" + ImageFileName + ".bmp"; Cv2.ImWrite(ImageFullPath, hoImage); return true; } catch { return false; } } } }