├── _config.yml ├── preview.gif ├── EdgeBasedTemplateMatching ├── Images │ ├── search1.jpg │ ├── search2.jpg │ ├── search3.jpg │ └── template.jpg ├── MainWindow.xaml.cs ├── App.xaml.cs ├── App.xaml ├── EdgeBasedTemplateMatching.csproj ├── BitmapSourceConvert.cs ├── MainWindow.xaml └── MainWindowViewModel.cs ├── EdgeBasedTemplateMatching.sln ├── README.md ├── .gitattributes └── .gitignore /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IOL0ol1/Edge-Based-Template-Matching/HEAD/preview.gif -------------------------------------------------------------------------------- /EdgeBasedTemplateMatching/Images/search1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IOL0ol1/Edge-Based-Template-Matching/HEAD/EdgeBasedTemplateMatching/Images/search1.jpg -------------------------------------------------------------------------------- /EdgeBasedTemplateMatching/Images/search2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IOL0ol1/Edge-Based-Template-Matching/HEAD/EdgeBasedTemplateMatching/Images/search2.jpg -------------------------------------------------------------------------------- /EdgeBasedTemplateMatching/Images/search3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IOL0ol1/Edge-Based-Template-Matching/HEAD/EdgeBasedTemplateMatching/Images/search3.jpg -------------------------------------------------------------------------------- /EdgeBasedTemplateMatching/Images/template.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IOL0ol1/Edge-Based-Template-Matching/HEAD/EdgeBasedTemplateMatching/Images/template.jpg -------------------------------------------------------------------------------- /EdgeBasedTemplateMatching/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace EdgeBasedTemplateMatching 4 | { 5 | /// 6 | /// Interaction logic for MainWindow.xaml 7 | /// 8 | public partial class MainWindow : Window 9 | { 10 | public MainWindow() 11 | { 12 | InitializeComponent(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /EdgeBasedTemplateMatching/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | 9 | namespace EdgeBasedTemplateMatching 10 | { 11 | /// 12 | /// Interaction logic for App.xaml 13 | /// 14 | public partial class App : Application 15 | { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /EdgeBasedTemplateMatching/App.xaml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /EdgeBasedTemplateMatching.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30717.126 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EdgeBasedTemplateMatching", "EdgeBasedTemplateMatching\EdgeBasedTemplateMatching.csproj", "{E3CDBF1A-A59B-42FB-B40F-AD8F3080435E}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {E3CDBF1A-A59B-42FB-B40F-AD8F3080435E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {E3CDBF1A-A59B-42FB-B40F-AD8F3080435E}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {E3CDBF1A-A59B-42FB-B40F-AD8F3080435E}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {E3CDBF1A-A59B-42FB-B40F-AD8F3080435E}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {44B66E5F-0C8D-424C-9632-C2FE2018DB55} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Edge Based Template Matching 2 | ============================== 3 | 4 | ![click to preview](preview.gif) 5 | 6 | ### References 7 | 8 | [Edge Based Template Matching](https://www.codeproject.com/Articles/99457/Edge-Based-Template-Matching) 9 | 10 | 11 | 12 | ### Different 13 | 14 | These differences can write less code and use opencv acceleration 15 | 16 | #### 1 Use Canny directly 17 | The reference article implements the Canny algorithm itself. 18 | Inserts the operation that creates the gradient template into it. 19 | 20 | The implementation here is different. 21 | First use Canny algorithm to find the edges. 22 | Then use FindContours find the edges to create a gradient template. 23 | 24 | #### 2 Use CartToPolar to get magnitude 25 | Referencing the article by traversing the calculation magnitude. 26 | 27 | Here use the opencv's CartToPolar to get the magnitude and direction directly 28 | 29 | 30 | 31 | ### Library 32 | 33 | Prism.Core 34 | OpenCvSharp4.Windows 35 | MaterialDesignThemes 36 | PropertyTools.Wpf 37 | 38 | ### Information collected on the network 39 | 40 | [Prof. Dr. Carsten Steger(Halcon)](https://iuks.in.tum.de/members/steger/publications) 41 | [Some video and publications(At the bottom)](http://campar.in.tum.de/Main/AndreasHofhauser) 42 | [shape_based_matching](https://github.com/meiqua/shape_based_matching) 43 | -------------------------------------------------------------------------------- /EdgeBasedTemplateMatching/EdgeBasedTemplateMatching.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WinExe 5 | net6.0-windows 6 | true 7 | EdgeBasedTemplateMatching 8 | EdgeBasedTemplateMatching 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Always 23 | 24 | 25 | Always 26 | 27 | 28 | Always 29 | 30 | 31 | Always 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /EdgeBasedTemplateMatching/BitmapSourceConvert.cs: -------------------------------------------------------------------------------- 1 | using OpenCvSharp; 2 | using OpenCvSharp.WpfExtensions; 3 | using System; 4 | using System.Globalization; 5 | using System.Windows.Data; 6 | using System.Windows.Media.Imaging; 7 | 8 | namespace EdgeBasedTemplateMatching 9 | { 10 | 11 | 12 | internal class BitmapSourceConvert : IValueConverter 13 | { 14 | /// 15 | /// mat to bitmapsource 16 | /// 17 | /// 18 | /// 19 | /// 20 | /// 21 | /// 22 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 23 | { 24 | if (value is Mat mat) 25 | { 26 | return mat.ToBitmapSource(); 27 | } 28 | return null; 29 | } 30 | 31 | /// 32 | /// bitmapsource to mat 33 | /// 34 | /// 35 | /// 36 | /// 37 | /// 38 | /// 39 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 40 | { 41 | if (value is BitmapSource bitmap) 42 | { 43 | return bitmap.ToMat(); 44 | } 45 | return null; 46 | } 47 | 48 | 49 | } 50 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /EdgeBasedTemplateMatching/MainWindow.xaml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 35 | 36 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 62 | 63 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /EdgeBasedTemplateMatching/MainWindowViewModel.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32; 2 | using OpenCvSharp; 3 | using Prism.Commands; 4 | using Prism.Mvvm; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Diagnostics; 8 | using System.IO; 9 | using System.Linq; 10 | using System.Windows.Input; 11 | 12 | namespace EdgeBasedTemplateMatching 13 | { 14 | public class MainWindowViewModel : BindableBase 15 | { 16 | public MainWindowViewModel() 17 | { 18 | LoadCommand = new DelegateCommand(LoadExecute); 19 | TrainCommand = new DelegateCommand(TrainTemplate); 20 | SearchCommand = new DelegateCommand(MatchSearch); 21 | } 22 | 23 | public ICommand LoadCommand { get; private set; } 24 | public ICommand TrainCommand { get; private set; } 25 | public ICommand SearchCommand { get; private set; } 26 | 27 | private Mat template; 28 | 29 | /// 30 | /// Image used to create template 31 | /// 32 | public Mat Template 33 | { 34 | get { return template; } 35 | set 36 | { 37 | template = value; 38 | RaisePropertyChanged(); 39 | } 40 | } 41 | 42 | private Mat destination; 43 | 44 | /// 45 | /// Image to be processed 46 | /// 47 | public Mat Destination 48 | { 49 | get { return destination; } 50 | set 51 | { 52 | destination = value; 53 | RaisePropertyChanged(); 54 | } 55 | } 56 | 57 | /// 58 | /// Load template and destination image 59 | /// 60 | /// 61 | private void LoadExecute(string i) 62 | { 63 | OpenFileDialog dialog = new OpenFileDialog(); 64 | dialog.InitialDirectory = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), "Images"); 65 | dialog.Filter = "JPG|*.jpg|PNG|*.png|BMP|*.bmp|All|*.*"; 66 | if (dialog.ShowDialog().Value == true) 67 | { 68 | if (dialog.CheckFileExists) 69 | { 70 | string file = dialog.FileName; 71 | if (i.ToLower().Contains("t")) 72 | { 73 | Template?.Dispose(); 74 | Template = new Mat(file); 75 | } 76 | else 77 | { 78 | Destination?.Dispose(); 79 | Destination = new Mat(file); 80 | } 81 | } 82 | } 83 | } 84 | 85 | public TrainParame TrainParame { get; } = new TrainParame(); 86 | 87 | public SearchParame SearchParame { get; } = new SearchParame(); 88 | 89 | 90 | /// 91 | /// Result data 92 | /// 93 | private List results = new List(); 94 | 95 | 96 | 97 | /// 98 | /// Create a template 99 | /// 100 | private void TrainTemplate() 101 | { 102 | try 103 | { 104 | results.Clear(); 105 | using (Mat src = new Mat()) 106 | using (Mat output = new Mat()) 107 | using (Mat gx = new Mat()) 108 | using (Mat gy = new Mat()) 109 | using (Mat magnitude = new Mat()) 110 | using (Mat direction = new Mat()) 111 | { 112 | /// convert to gray image 113 | Cv2.CvtColor(template, src, ColorConversionCodes.RGB2GRAY); 114 | 115 | /// using the canny algorithm to get edges 116 | Cv2.Canny(src, output, TrainParame.Threshold1, TrainParame.Threshold2, TrainParame.ApertureSize, TrainParame.L2gradient); 117 | Cv2.FindContours(output, out var contours, out var hierarchy, TrainParame.Mode, TrainParame.Method); 118 | 119 | 120 | /// use the sobel filter on the template image which returns the gradients in the X (Gx) and Y (Gy) direction. 121 | Cv2.Sobel(src, gx, MatType.CV_64F, 1, 0, 3); 122 | Cv2.Sobel(src, gy, MatType.CV_64F, 0, 1, 3); 123 | 124 | /// compute the magnitude and direction(radians) 125 | Cv2.CartToPolar(gx, gy, magnitude, direction); 126 | 127 | /// save edge info 128 | var sum = new Point2d(0, 0); 129 | for (int i = 0, m = contours.Length; i < m; i++) 130 | { 131 | for (int j = 0, n = contours[i].Length; j < n; j++) 132 | { 133 | var cur = contours[i][j]; 134 | var fdx = gx.At(cur.Y, cur.X, 0); // dx 135 | var fdy = gy.At(cur.Y, cur.X, 0); // dy 136 | var der = new Point2d(fdx, fdy); // (dx,dy) 137 | var mag = magnitude.At(cur.Y, cur.X, 0); // √(dx²+dy²) 138 | var dir = direction.At(cur.Y, cur.X, 0); // atan2(dy,dx) 139 | results.Add(new PointInfo 140 | { 141 | Point = cur, 142 | Derivative = der, 143 | Direction = dir, 144 | Magnitude = mag == 0 ? 0 : 1 / mag, 145 | }); 146 | sum += cur; 147 | } 148 | } 149 | 150 | /// update Center and Offset in PointInfo 151 | var center = new Point2d(sum.X / results.Count, sum.Y / results.Count); 152 | foreach (var item in results) 153 | { 154 | item.Update(center); 155 | } 156 | 157 | /// overlay display origin image, edge(green) and center point(red) 158 | Cv2.DrawContours(template, new[] { results.Select(_ => _.Point) }, -1, Scalar.LightGreen, 2); 159 | //Cv2.DrawContours(template, contours, -1, Scalar.LightGreen, 2); 160 | Cv2.Circle(template, center.ToPoint(), 2, Scalar.Red, -1); 161 | } 162 | 163 | /// update UI 164 | RaisePropertyChanged(nameof(Template)); 165 | } 166 | catch (Exception ex) 167 | { 168 | Trace.TraceError(ex.Message); 169 | Trace.TraceError(ex.StackTrace); 170 | } 171 | } 172 | 173 | 174 | /// 175 | /// NCC to find template 176 | /// 177 | private void MatchSearch() 178 | { 179 | Stopwatch stopwatch = new Stopwatch(); 180 | try 181 | { 182 | Trace.TraceInformation("NCC matching start"); 183 | stopwatch.Start(); 184 | 185 | /// convert to gray image 186 | using (Mat src = new Mat()) 187 | using (Mat gx = new Mat()) 188 | using (Mat gy = new Mat()) 189 | using (Mat direction = new Mat()) 190 | using (Mat magnitude = new Mat()) 191 | { 192 | 193 | Cv2.CvtColor(destination, src, ColorConversionCodes.RGB2GRAY); 194 | 195 | /// use the sobel filter on the source image which returns the gradients in the X (Gx) and Y (Gy) direction. 196 | Cv2.Sobel(src, gx, MatType.CV_64F, 1, 0, 3); 197 | Cv2.Sobel(src, gy, MatType.CV_64F, 0, 1, 3); 198 | 199 | /// compute the magnitude and direction 200 | Cv2.CartToPolar(gx, gy, magnitude, direction); 201 | 202 | var minScore = SearchParame.MinScore; 203 | var greediness = SearchParame.Greediness; 204 | 205 | /// ncc match search 206 | long noOfCordinates = results.Count; 207 | double normMinScore = minScore / noOfCordinates; // normalized min score 208 | double normGreediness = (1 - greediness * minScore) / (1 - greediness) / noOfCordinates; 209 | double partialScore = 0; 210 | double resultScore = 0; 211 | Point center = new Point(); 212 | 213 | for (int i = 0, h = src.Height; i < h; i++) 214 | { 215 | for (int j = 0, w = src.Width; j < w; j++) 216 | { 217 | double partialSum = 0; 218 | for (var m = 0; m < noOfCordinates; m++) 219 | { 220 | var item = results[m]; 221 | var curX = (int)(j + item.Offset.X); 222 | var curY = (int)(i + item.Offset.Y); 223 | var iTx = item.Derivative.X; 224 | var iTy = item.Derivative.Y; 225 | if (curX < 0 || curY < 0 || curY > src.Height - 1 || curX > src.Width - 1) 226 | continue; 227 | 228 | var iSx = gx.At(curY, curX, 0); 229 | var iSy = gy.At(curY, curX, 0); 230 | 231 | if ((iSx != 0 || iSy != 0) && (iTx != 0 || iTy != 0)) 232 | { 233 | var mag = magnitude.At(curY, curX, 0); 234 | var matGradMag = mag == 0 ? 0 : 1 / mag; // 1/√(dx²+dy²) 235 | partialSum += ((iSx * iTx) + (iSy * iTy)) * (item.Magnitude * matGradMag); 236 | } 237 | 238 | var sumOfCoords = m + 1; 239 | partialScore = partialSum / sumOfCoords; 240 | /// check termination criteria 241 | /// if partial score score is less than the score than needed to make the required score at that position 242 | /// break serching at that coordinate. 243 | if (partialScore < Math.Min((minScore - 1) + normGreediness * sumOfCoords, normMinScore * sumOfCoords)) 244 | break; 245 | } 246 | if (partialScore > resultScore) 247 | { 248 | resultScore = partialScore; 249 | center.X = j; 250 | center.Y = i; 251 | } 252 | } 253 | } 254 | 255 | /// overlay display origin image, edge(green) and center point(red) 256 | Cv2.DrawContours(destination, new[] { results.Select(_ => _.Offset.ToPoint()) }, -1, Scalar.LightGreen, 2, offset: center); 257 | Cv2.Circle(destination, center, 5, Scalar.Red, -1); 258 | Trace.TraceInformation($"NCC matching score {resultScore}. time: {stopwatch.Elapsed.TotalMilliseconds} ms"); 259 | } 260 | 261 | RaisePropertyChanged(nameof(Destination)); 262 | } 263 | catch (Exception ex) 264 | { 265 | Trace.TraceError(ex.Message); 266 | Trace.TraceError(ex.StackTrace); 267 | } 268 | finally 269 | { 270 | stopwatch.Stop(); 271 | } 272 | } 273 | 274 | } 275 | 276 | /// 277 | /// Train parame 278 | /// 279 | public class TrainParame 280 | { 281 | 282 | /// 283 | /// Canny threshold1 284 | /// 285 | [PropertyTools.DataAnnotations.Category("Canny")] 286 | [PropertyTools.DataAnnotations.DisplayName("threshold1")] 287 | public double Threshold1 { get; set; } = 10; 288 | 289 | /// 290 | /// Canny threshold2 291 | /// 292 | [PropertyTools.DataAnnotations.Category("Canny")] 293 | [PropertyTools.DataAnnotations.DisplayName("threshold2")] 294 | public double Threshold2 { get; set; } = 100; 295 | 296 | /// 297 | /// Canny apertureSize 298 | /// 299 | [PropertyTools.DataAnnotations.Category("Canny")] 300 | [PropertyTools.DataAnnotations.DisplayName("apertureSize")] 301 | public int ApertureSize { get; set; } = 3; 302 | 303 | /// 304 | /// Canny L2gradient 305 | /// 306 | [PropertyTools.DataAnnotations.Category("Canny")] 307 | [PropertyTools.DataAnnotations.DisplayName("L2gradient")] 308 | public bool L2gradient { get; set; } = false; 309 | 310 | /// 311 | /// FindContours mode 312 | /// 313 | [PropertyTools.DataAnnotations.Category("FindContours")] 314 | [PropertyTools.DataAnnotations.DisplayName("mode")] 315 | public RetrievalModes Mode { get; set; } = RetrievalModes.External; 316 | 317 | /// 318 | /// FindContours method 319 | /// 320 | [PropertyTools.DataAnnotations.Category("FindContours")] 321 | [PropertyTools.DataAnnotations.DisplayName("method")] 322 | public ContourApproximationModes Method { get; set; } = ContourApproximationModes.ApproxNone; 323 | 324 | } 325 | 326 | /// 327 | /// Search parame 328 | /// 329 | public class SearchParame 330 | { 331 | /// 332 | /// min score to skip search 333 | /// 334 | [PropertyTools.DataAnnotations.DisplayName("Minscore to skip search")] 335 | public double MinScore { get; set; } = 0.9; 336 | 337 | /// 338 | /// greediness for search 339 | /// 340 | [PropertyTools.DataAnnotations.DisplayName("Greediness for search")] 341 | public double Greediness { get; set; } = 0.9; 342 | 343 | } 344 | 345 | /// 346 | /// Point information. 347 | /// 348 | /// 349 | /// ----→ dx 350 | /// |\ )m 351 | /// | \ 352 | /// | \ 353 | /// ↓ \ 354 | /// dy c 355 | /// 356 | /// Derivative = (dx,dy) 357 | /// Magnitude = 1/c = 1/√(dx²+dy²) 358 | /// Direction = atan2(dy,dx) (not currently in use) 359 | /// 360 | public class PointInfo 361 | { 362 | /// 363 | /// Point of edge 364 | /// 365 | /// 366 | /// (x,y) 367 | /// 368 | public Point Point; 369 | 370 | /// 371 | /// Center of edge 372 | /// 373 | /// 374 | /// (x0,y0) 375 | /// 376 | public Point2d Center { get; private set; } 377 | 378 | /// 379 | /// Point-Center 380 | /// 381 | /// 382 | /// (x-x0,y-y0) 383 | /// 384 | public Point2d Offset { get; private set; } 385 | 386 | /// 387 | /// Derivative at Point 388 | /// 389 | /// 390 | /// (dx,dy) 391 | /// 392 | public Point2d Derivative; 393 | 394 | /// 395 | /// Magnitude at Point 396 | /// 397 | /// 398 | /// 1/√(dx²+dy²) 399 | /// 400 | public double Magnitude; 401 | 402 | /// 403 | /// Direction at Point 404 | /// 405 | /// 406 | /// atan2(dy,dx) (not currently in use) 407 | /// 408 | public double Direction; 409 | 410 | /// 411 | /// Calc Offset with Point by center 412 | /// 413 | /// 414 | public void Update(Point2d center) 415 | { 416 | Center = center; 417 | Offset = Point - center; 418 | } 419 | } 420 | 421 | 422 | } 423 | --------------------------------------------------------------------------------