HarrisCornersDetector.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652
  1. namespace VisualMath.Accord.Imaging
  2. {
  3. using System.Collections.Generic;
  4. using System.Drawing;
  5. using System.Drawing.Imaging;
  6. using AForge.Imaging;
  7. using AForge.Imaging.Filters;
  8. using AForge;
  9. /// <summary>
  10. /// Harris Corners Detector.
  11. /// </summary>
  12. /// <remarks>
  13. /// <para>This class implements the Harris corners detector.</para>
  14. /// <para>Sample usage:</para>
  15. /// <code>
  16. /// // create corners detector's instance
  17. /// HarrisCornersDetector hcd = new HarrisCornersDetector( );
  18. /// // process image searching for corners
  19. /// Point[] corners = hcd.ProcessImage( image );
  20. /// // process points
  21. /// foreach ( Point corner in corners )
  22. /// {
  23. /// // ...
  24. /// }
  25. /// </code>
  26. ///
  27. /// <para>
  28. /// References:
  29. /// <list type="bullet">
  30. /// <item><description>
  31. /// P. D. Kovesi. MATLAB and Octave Functions for Computer Vision and Image Processing.
  32. /// School of Computer Science and Software Engineering, The University of Western Australia.
  33. /// Available in: http://www.csse.uwa.edu.au/~pk/Research/MatlabFns/Spatial/harris.m</description></item>
  34. /// <item><description>
  35. /// C.G. Harris and M.J. Stephens. "A combined corner and edge detector",
  36. /// Proceedings Fourth Alvey Vision Conference, Manchester.
  37. /// pp 147-151, 1988.</description></item>
  38. /// <item><description>
  39. /// Alison Noble, "Descriptions of Image Surfaces", PhD thesis, Department
  40. /// of Engineering Science, Oxford University 1989, p45.</description></item>
  41. /// </list>
  42. /// </para>
  43. /// </remarks>
  44. ///
  45. /// <seealso cref="MoravecCornersDetector"/>
  46. /// <seealso cref="SusanCornersDetector"/>
  47. ///
  48. public class HarrisCornersDetector : ICornersDetector
  49. {
  50. private float k = 0.04f;
  51. private float threshold = 1000f;
  52. private double sigma = 1.4;
  53. private int r = 3;
  54. /// <summary>
  55. /// Harris parameter k. Default value is 0.04.
  56. /// </summary>
  57. public float K
  58. {
  59. get { return k; }
  60. set { k = value; }
  61. }
  62. /// <summary>
  63. /// Harris threshold. Default value is 1000.
  64. /// </summary>
  65. public float Threshold
  66. {
  67. get { return threshold; }
  68. set { threshold = value; }
  69. }
  70. /// <summary>
  71. /// Gaussian smoothing sigma. Default value is 1.4.
  72. /// </summary>
  73. public double Sigma
  74. {
  75. get { return sigma; }
  76. set { sigma = value; }
  77. }
  78. /// <summary>
  79. /// Non-maximum suppression window radius. Default value is 3.
  80. /// </summary>
  81. public int Suppression
  82. {
  83. get { return r; }
  84. set { r = value; }
  85. }
  86. /// <summary>
  87. /// Initializes a new instance of the <see cref="HarrisCornersDetector"/> class.
  88. /// </summary>
  89. public HarrisCornersDetector()
  90. {
  91. }
  92. /// <summary>
  93. /// Initializes a new instance of the <see cref="HarrisCornersDetector"/> class.
  94. /// </summary>
  95. public HarrisCornersDetector(float k)
  96. : this()
  97. {
  98. this.k = k;
  99. }
  100. /// <summary>
  101. /// Initializes a new instance of the <see cref="HarrisCornersDetector"/> class.
  102. /// </summary>
  103. public HarrisCornersDetector(float k, float threshold)
  104. : this()
  105. {
  106. this.k = k;
  107. this.threshold = threshold;
  108. }
  109. /// <summary>
  110. /// Initializes a new instance of the <see cref="HarrisCornersDetector"/> class.
  111. /// </summary>
  112. public HarrisCornersDetector(float k, float threshold, double sigma)
  113. : this()
  114. {
  115. this.k = k;
  116. this.threshold = threshold;
  117. this.sigma = sigma;
  118. }
  119. /// <summary>
  120. /// Process image looking for corners.
  121. /// </summary>
  122. ///
  123. /// <param name="image">Source image data to process.</param>
  124. ///
  125. /// <returns>Returns list of found corners (X-Y coordinates).</returns>
  126. ///
  127. /// <exception cref="UnsupportedImageFormatException">
  128. /// The source image has incorrect pixel format.
  129. /// </exception>
  130. ///
  131. public List<IntPoint> ProcessImage(UnmanagedImage image)
  132. {
  133. // check image format
  134. if (
  135. (image.PixelFormat != PixelFormat.Format8bppIndexed) &&
  136. (image.PixelFormat != PixelFormat.Format24bppRgb) &&
  137. (image.PixelFormat != PixelFormat.Format32bppRgb) &&
  138. (image.PixelFormat != PixelFormat.Format32bppArgb)
  139. )
  140. {
  141. throw new UnsupportedImageFormatException("Unsupported pixel format of the source image.");
  142. }
  143. // make sure we have grayscale image
  144. UnmanagedImage grayImage = null;
  145. if (image.PixelFormat == PixelFormat.Format8bppIndexed)
  146. {
  147. grayImage = image;
  148. }
  149. else
  150. {
  151. // create temporary grayscale image
  152. grayImage = Grayscale.CommonAlgorithms.BT709.Apply(image);
  153. }
  154. // get source image size
  155. int width = grayImage.Width;
  156. int height = grayImage.Height;
  157. int stride = grayImage.Stride;
  158. int offset = stride - width;
  159. // 1. Calculate partial differences
  160. UnmanagedImage diffx = UnmanagedImage.Create(width, height, PixelFormat.Format8bppIndexed);
  161. UnmanagedImage diffy = UnmanagedImage.Create(width, height, PixelFormat.Format8bppIndexed);
  162. UnmanagedImage diffxy = UnmanagedImage.Create(width, height, PixelFormat.Format8bppIndexed);
  163. unsafe
  164. {
  165. // Compute dx and dy
  166. byte* src = (byte*)grayImage.ImageData.ToPointer();
  167. byte* dx = (byte*)diffx.ImageData.ToPointer();
  168. byte* dy = (byte*)diffy.ImageData.ToPointer();
  169. byte* dxy = (byte*)diffxy.ImageData.ToPointer();
  170. // for each line
  171. for (int y = 0; y < height; y++)
  172. {
  173. // for each pixel
  174. for (int x = 0; x < width; x++, src++, dx++, dy++)
  175. {
  176. // TODO: Place those verifications
  177. // outside the innermost loop
  178. if (x == 0 || x == width - 1 ||
  179. y == 0 || y == height - 1)
  180. {
  181. *dx = *dy = 0; continue;
  182. }
  183. int h = -(src[-stride - 1] + src[-1] + src[stride - 1]) +
  184. (src[-stride + 1] + src[+1] + src[stride + 1]);
  185. *dx = (byte)(h > 255 ? 255 : h < 0 ? 0 : h);
  186. int v = -(src[-stride - 1] + src[-stride] + src[-stride + 1]) +
  187. (src[+stride - 1] + src[+stride] + src[+stride + 1]);
  188. *dy = (byte)(v > 255 ? 255 : v < 0 ? 0 : v);
  189. }
  190. src += offset;
  191. dx += offset;
  192. dy += offset;
  193. }
  194. // Compute dxy
  195. dx = (byte*)diffx.ImageData.ToPointer();
  196. dxy = (byte*)diffxy.ImageData.ToPointer();
  197. // for each line
  198. for (int y = 0; y < height; y++)
  199. {
  200. // for each pixel
  201. for (int x = 0; x < width; x++, dx++, dxy++)
  202. {
  203. if (x == 0 || x == width - 1 ||
  204. y == 0 || y == height - 1)
  205. {
  206. *dxy = 0; continue;
  207. }
  208. int v = -(dx[-stride - 1] + dx[-stride] + dx[-stride + 1]) +
  209. (dx[+stride - 1] + dx[+stride] + dx[+stride + 1]);
  210. *dxy = (byte)(v > 255 ? 255 : v < 0 ? 0 : v);
  211. }
  212. dx += offset;
  213. dxy += offset;
  214. }
  215. }
  216. // 2. Smooth the diff images
  217. if (sigma > 0.0)
  218. {
  219. GaussianBlur blur = new GaussianBlur(sigma);
  220. blur.ApplyInPlace(diffx);
  221. blur.ApplyInPlace(diffy);
  222. blur.ApplyInPlace(diffxy);
  223. }
  224. // 3. Compute Harris Corner Response
  225. float[,] H = new float[height, width];
  226. unsafe
  227. {
  228. byte* ptrA = (byte*)diffx.ImageData.ToPointer();
  229. byte* ptrB = (byte*)diffy.ImageData.ToPointer();
  230. byte* ptrC = (byte*)diffxy.ImageData.ToPointer();
  231. float M, A, B, C;
  232. for (int y = 0; y < height; y++)
  233. {
  234. for (int x = 0; x < width; x++)
  235. {
  236. A = *(ptrA++);
  237. B = *(ptrB++);
  238. C = *(ptrC++);
  239. // Harris corner measure
  240. M = (A * B - C * C) - (k * ((A + B) * (A + B)));
  241. if (M > threshold)
  242. H[y, x] = M;
  243. else H[y, x] = 0;
  244. }
  245. ptrA += offset;
  246. ptrB += offset;
  247. ptrC += offset;
  248. }
  249. }
  250. // Free resources
  251. diffx.Dispose();
  252. diffy.Dispose();
  253. diffxy.Dispose();
  254. if (image.PixelFormat != PixelFormat.Format8bppIndexed)
  255. grayImage.Dispose();
  256. // 4. Suppress non-maximum points
  257. List<IntPoint> cornersList = new List<IntPoint>();
  258. // for each row
  259. for (int y = r, maxY = height - r; y < maxY; y++)
  260. {
  261. // for each pixel
  262. for (int x = r, maxX = width - r; x < maxX; x++)
  263. {
  264. float currentValue = H[y, x];
  265. // for each windows' row
  266. for (int i = -r; (currentValue != 0) && (i <= r); i++)
  267. {
  268. // for each windows' pixel
  269. for (int j = -r; j <= r; j++)
  270. {
  271. if (H[y + i, x + j] > currentValue)
  272. {
  273. currentValue = 0;
  274. break;
  275. }
  276. }
  277. }
  278. // check if this point is really interesting
  279. if (currentValue != 0)
  280. {
  281. cornersList.Add(new IntPoint(x, y));
  282. }
  283. }
  284. }
  285. return cornersList;
  286. }
  287. /// <summary>
  288. /// Process image looking for corners.
  289. /// </summary>
  290. ///
  291. /// <param name="imageData">Source image data to process.</param>
  292. ///
  293. /// <returns>Returns list of found corners (X-Y coordinates).</returns>
  294. ///
  295. /// <exception cref="UnsupportedImageFormatException">
  296. /// The source image has incorrect pixel format.
  297. /// </exception>
  298. ///
  299. public List<IntPoint> ProcessImage(BitmapData imageData)
  300. {
  301. return ProcessImage(new UnmanagedImage(imageData));
  302. }
  303. /// <summary>
  304. /// Process image looking for corners.
  305. /// </summary>
  306. ///
  307. /// <param name="image">Source image data to process.</param>
  308. ///
  309. /// <returns>Returns list of found corners (X-Y coordinates).</returns>
  310. ///
  311. /// <exception cref="UnsupportedImageFormatException">
  312. /// The source image has incorrect pixel format.
  313. /// </exception>
  314. ///
  315. public List<IntPoint> ProcessImage(Bitmap image)
  316. {
  317. // check image format
  318. if (
  319. (image.PixelFormat != PixelFormat.Format8bppIndexed) &&
  320. (image.PixelFormat != PixelFormat.Format24bppRgb) &&
  321. (image.PixelFormat != PixelFormat.Format32bppRgb) &&
  322. (image.PixelFormat != PixelFormat.Format32bppArgb)
  323. )
  324. {
  325. throw new UnsupportedImageFormatException("Unsupported pixel format of the source");
  326. }
  327. // lock source image
  328. BitmapData imageData = image.LockBits(
  329. new Rectangle(0, 0, image.Width, image.Height),
  330. ImageLockMode.ReadOnly, image.PixelFormat);
  331. List<IntPoint> corners;
  332. try
  333. {
  334. // process the image
  335. corners = ProcessImage(new UnmanagedImage(imageData));
  336. }
  337. finally
  338. {
  339. // unlock image
  340. image.UnlockBits(imageData);
  341. }
  342. return corners;
  343. }
  344. /// <summary>
  345. /// Process image looking for corners.
  346. /// </summary>
  347. ///
  348. /// <param name="image">Source image data to process.</param>
  349. ///
  350. /// <returns>Returns list of found corners (X-Y coordinates).</returns>
  351. ///
  352. /// <exception cref="UnsupportedImageFormatException">
  353. /// The source image has incorrect pixel format.
  354. /// </exception>
  355. ///
  356. public List<Point> ProcessImageMethod(Bitmap image)
  357. {
  358. // check image format
  359. if (
  360. (image.PixelFormat != PixelFormat.Format8bppIndexed) &&
  361. (image.PixelFormat != PixelFormat.Format24bppRgb) &&
  362. (image.PixelFormat != PixelFormat.Format32bppRgb) &&
  363. (image.PixelFormat != PixelFormat.Format32bppArgb)
  364. )
  365. {
  366. throw new UnsupportedImageFormatException("Unsupported pixel format of the source");
  367. }
  368. // lock source image
  369. BitmapData imageData = image.LockBits(
  370. new Rectangle(0, 0, image.Width, image.Height),
  371. ImageLockMode.ReadOnly, image.PixelFormat);
  372. List<Point> corners;// = new List<Point>();
  373. try
  374. {
  375. // process the image
  376. corners = ProcessImageMethod(new UnmanagedImage(imageData));
  377. }
  378. finally
  379. {
  380. // unlock image
  381. image.UnlockBits(imageData);
  382. }
  383. return corners;
  384. }
  385. /// <summary>
  386. /// Process image looking for corners.
  387. /// </summary>
  388. ///
  389. /// <param name="image">Source image data to process.</param>
  390. ///
  391. /// <returns>Returns list of found corners (X-Y coordinates).</returns>
  392. ///
  393. /// <exception cref="UnsupportedImageFormatException">
  394. /// The source image has incorrect pixel format.
  395. /// </exception>
  396. ///
  397. public List<Point> ProcessImageMethod(UnmanagedImage image)
  398. {
  399. // check image format
  400. if (
  401. (image.PixelFormat != PixelFormat.Format8bppIndexed) &&
  402. (image.PixelFormat != PixelFormat.Format24bppRgb) &&
  403. (image.PixelFormat != PixelFormat.Format32bppRgb) &&
  404. (image.PixelFormat != PixelFormat.Format32bppArgb)
  405. )
  406. {
  407. throw new UnsupportedImageFormatException("Unsupported pixel format of the source image.");
  408. }
  409. // make sure we have grayscale image
  410. UnmanagedImage grayImage = null;
  411. if (image.PixelFormat == PixelFormat.Format8bppIndexed)
  412. {
  413. grayImage = image;
  414. }
  415. else
  416. {
  417. // create temporary grayscale image
  418. grayImage = Grayscale.CommonAlgorithms.BT709.Apply(image);
  419. }
  420. // get source image size
  421. int width = grayImage.Width;
  422. int height = grayImage.Height;
  423. int stride = grayImage.Stride;
  424. int offset = stride - width;
  425. // 1. Calculate partial differences
  426. UnmanagedImage diffx = UnmanagedImage.Create(width, height, PixelFormat.Format8bppIndexed);
  427. UnmanagedImage diffy = UnmanagedImage.Create(width, height, PixelFormat.Format8bppIndexed);
  428. UnmanagedImage diffxy = UnmanagedImage.Create(width, height, PixelFormat.Format8bppIndexed);
  429. unsafe
  430. {
  431. // Compute dx and dy
  432. byte* src = (byte*)grayImage.ImageData.ToPointer();
  433. byte* dx = (byte*)diffx.ImageData.ToPointer();
  434. byte* dy = (byte*)diffy.ImageData.ToPointer();
  435. byte* dxy = (byte*)diffxy.ImageData.ToPointer();
  436. // for each line
  437. for (int y = 0; y < height; y++)
  438. {
  439. // for each pixel
  440. for (int x = 0; x < width; x++, src++, dx++, dy++)
  441. {
  442. // TODO: Place those verifications
  443. // outside the innermost loop
  444. if (x == 0 || x == width - 1 ||
  445. y == 0 || y == height - 1)
  446. {
  447. *dx = *dy = 0; continue;
  448. }
  449. int h = -(src[-stride - 1] + src[-1] + src[stride - 1]) +
  450. (src[-stride + 1] + src[+1] + src[stride + 1]);
  451. *dx = (byte)(h > 255 ? 255 : h < 0 ? 0 : h);
  452. int v = -(src[-stride - 1] + src[-stride] + src[-stride + 1]) +
  453. (src[+stride - 1] + src[+stride] + src[+stride + 1]);
  454. *dy = (byte)(v > 255 ? 255 : v < 0 ? 0 : v);
  455. }
  456. src += offset;
  457. dx += offset;
  458. dy += offset;
  459. }
  460. // Compute dxy
  461. dx = (byte*)diffx.ImageData.ToPointer();
  462. dxy = (byte*)diffxy.ImageData.ToPointer();
  463. // for each line
  464. for (int y = 0; y < height; y++)
  465. {
  466. // for each pixel
  467. for (int x = 0; x < width; x++, dx++, dxy++)
  468. {
  469. if (x == 0 || x == width - 1 ||
  470. y == 0 || y == height - 1)
  471. {
  472. *dxy = 0; continue;
  473. }
  474. int v = -(dx[-stride - 1] + dx[-stride] + dx[-stride + 1]) +
  475. (dx[+stride - 1] + dx[+stride] + dx[+stride + 1]);
  476. *dxy = (byte)(v > 255 ? 255 : v < 0 ? 0 : v);
  477. }
  478. dx += offset;
  479. dxy += offset;
  480. }
  481. }
  482. // 2. Smooth the diff images
  483. if (sigma > 0.0)
  484. {
  485. GaussianBlur blur = new GaussianBlur(sigma);
  486. blur.ApplyInPlace(diffx);
  487. blur.ApplyInPlace(diffy);
  488. blur.ApplyInPlace(diffxy);
  489. }
  490. // 3. Compute Harris Corner Response
  491. float[,] H = new float[height, width];
  492. unsafe
  493. {
  494. byte* ptrA = (byte*)diffx.ImageData.ToPointer();
  495. byte* ptrB = (byte*)diffy.ImageData.ToPointer();
  496. byte* ptrC = (byte*)diffxy.ImageData.ToPointer();
  497. float M, A, B, C;
  498. for (int y = 0; y < height; y++)
  499. {
  500. for (int x = 0; x < width; x++)
  501. {
  502. A = *(ptrA++);
  503. B = *(ptrB++);
  504. C = *(ptrC++);
  505. // Harris corner measure
  506. M = (A * B - C * C) - (k * ((A + B) * (A + B)));
  507. if (M > threshold)
  508. H[y, x] = M;
  509. else H[y, x] = 0;
  510. }
  511. ptrA += offset;
  512. ptrB += offset;
  513. ptrC += offset;
  514. }
  515. }
  516. // Free resources
  517. diffx.Dispose();
  518. diffy.Dispose();
  519. diffxy.Dispose();
  520. if (image.PixelFormat != PixelFormat.Format8bppIndexed)
  521. grayImage.Dispose();
  522. // 4. Suppress non-maximum points
  523. List<Point> cornersList = new List<Point>();
  524. // for each row
  525. for (int y = r, maxY = height - r; y < maxY; y++)
  526. {
  527. // for each pixel
  528. for (int x = r, maxX = width - r; x < maxX; x++)
  529. {
  530. float currentValue = H[y, x];
  531. // for each windows' row
  532. for (int i = -r; (currentValue != 0) && (i <= r); i++)
  533. {
  534. // for each windows' pixel
  535. for (int j = -r; j <= r; j++)
  536. {
  537. if (H[y + i, x + j] > currentValue)
  538. {
  539. currentValue = 0;
  540. break;
  541. }
  542. }
  543. }
  544. // check if this point is really interesting
  545. if (currentValue != 0)
  546. {
  547. cornersList.Add(new Point(x, y));
  548. }
  549. }
  550. }
  551. return cornersList;
  552. }
  553. }
  554. }