Tools.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639
  1. namespace VisualMath.Accord.Imaging
  2. {
  3. using System;
  4. using System.Drawing;
  5. using VisualMath.Accord.Math;
  6. using VisualMath.Accord.Math.Decompositions;
  7. using System.Drawing.Imaging;
  8. using AForge.Imaging;
  9. /// <summary>
  10. /// Static tool functions for imaging.
  11. /// </summary>
  12. ///
  13. public static class Tools
  14. {
  15. private const double SQRT2 = 1.4142135623730951;
  16. #region Algebra and geometry tools
  17. /// <summary>
  18. /// Creates an homography matrix matching points
  19. /// from a set of points to another.
  20. /// </summary>
  21. public static MatrixH Homography(PointH[] points1, PointH[] points2)
  22. {
  23. // Initial argument checkings
  24. if (points1.Length != points2.Length)
  25. throw new ArgumentException("The number of points should be equal.");
  26. if (points1.Length < 4)
  27. throw new ArgumentException("At least four points are required to fit an homography");
  28. int N = points1.Length;
  29. MatrixH T1, T2; // Normalize input points
  30. points1 = Tools.Normalize(points1, out T1);
  31. points2 = Tools.Normalize(points2, out T2);
  32. // Create the matrix A
  33. double[,] A = new double[3 * N, 9];
  34. for (int i = 0; i < N; i++)
  35. {
  36. PointH X = points1[i];
  37. double x = points2[i].X;
  38. double y = points2[i].Y;
  39. double w = points2[i].W;
  40. int r = 3 * i;
  41. A[r, 0] = 0;
  42. A[r, 1] = 0;
  43. A[r, 2] = 0;
  44. A[r, 3] = -w * X.X;
  45. A[r, 4] = -w * X.Y;
  46. A[r, 5] = -w * X.W;
  47. A[r, 6] = y * X.X;
  48. A[r, 7] = y * X.Y;
  49. A[r, 8] = y * X.W;
  50. r++;
  51. A[r, 0] = w * X.X;
  52. A[r, 1] = w * X.Y;
  53. A[r, 2] = w * X.W;
  54. A[r, 3] = 0;
  55. A[r, 4] = 0;
  56. A[r, 5] = 0;
  57. A[r, 6] = -x * X.X;
  58. A[r, 7] = -x * X.Y;
  59. A[r, 8] = -x * X.W;
  60. r++;
  61. A[r, 0] = -y * X.X;
  62. A[r, 1] = -y * X.Y;
  63. A[r, 2] = -y * X.W;
  64. A[r, 3] = x * X.X;
  65. A[r, 4] = x * X.Y;
  66. A[r, 5] = x * X.W;
  67. A[r, 6] = 0;
  68. A[r, 7] = 0;
  69. A[r, 8] = 0;
  70. }
  71. // Create the singular value decomposition
  72. SingularValueDecomposition svd = new SingularValueDecomposition(A, false, true);
  73. double[,] V = svd.RightSingularVectors;
  74. // Extract the homography matrix
  75. MatrixH H = new MatrixH((float)V[0, 8], (float)V[1, 8], (float)V[2, 8],
  76. (float)V[3, 8], (float)V[4, 8], (float)V[5, 8],
  77. (float)V[6, 8], (float)V[7, 8], (float)V[8, 8]);
  78. // Denormalize
  79. H = T2.Inverse().Multiply(H.Multiply(T1));
  80. return H;
  81. }
  82. /// <summary>
  83. /// Creates an homography matrix matching points
  84. /// from a set of points to another.
  85. /// </summary>
  86. public static MatrixH Homography(PointF[] points1, PointF[] points2)
  87. {
  88. // Initial argument checkings
  89. if (points1.Length != points2.Length)
  90. throw new ArgumentException("The number of points should be equal.");
  91. if (points1.Length < 4)
  92. throw new ArgumentException("At least four points are required to fit an homography");
  93. int N = points1.Length;
  94. MatrixH T1, T2; // Normalize input points
  95. points1 = Tools.Normalize(points1, out T1);
  96. points2 = Tools.Normalize(points2, out T2);
  97. // Create the matrix A
  98. double[,] A = new double[3 * N, 9];
  99. for (int i = 0; i < N; i++)
  100. {
  101. PointF X = points1[i];
  102. double x = points2[i].X;
  103. double y = points2[i].Y;
  104. int r = 3 * i;
  105. A[r, 0] = 0;
  106. A[r, 1] = 0;
  107. A[r, 2] = 0;
  108. A[r, 3] = -X.X;
  109. A[r, 4] = -X.Y;
  110. A[r, 5] = -1;
  111. A[r, 6] = y * X.X;
  112. A[r, 7] = y * X.Y;
  113. A[r, 8] = y;
  114. r++;
  115. A[r, 0] = X.X;
  116. A[r, 1] = X.Y;
  117. A[r, 2] = 1;
  118. A[r, 3] = 0;
  119. A[r, 4] = 0;
  120. A[r, 5] = 0;
  121. A[r, 6] = -x * X.X;
  122. A[r, 7] = -x * X.Y;
  123. A[r, 8] = -x;
  124. r++;
  125. A[r, 0] = -y * X.X;
  126. A[r, 1] = -y * X.Y;
  127. A[r, 2] = -y;
  128. A[r, 3] = x * X.X;
  129. A[r, 4] = x * X.Y;
  130. A[r, 5] = x;
  131. A[r, 6] = 0;
  132. A[r, 7] = 0;
  133. A[r, 8] = 0;
  134. }
  135. // Create the singular value decomposition
  136. SingularValueDecomposition svd = new SingularValueDecomposition(A, false, true);
  137. double[,] V = svd.RightSingularVectors;
  138. // Extract the homography matrix
  139. MatrixH H = new MatrixH((float)V[0, 8], (float)V[1, 8], (float)V[2, 8],
  140. (float)V[3, 8], (float)V[4, 8], (float)V[5, 8],
  141. (float)V[6, 8], (float)V[7, 8], (float)V[8, 8]);
  142. // Denormalize
  143. H = T2.Inverse().Multiply(H.Multiply(T1));
  144. return H;
  145. }
  146. /// <summary>
  147. /// Normalizes a set of homogeneous points so that the origin is located
  148. /// at the centroid and the mean distance to the origin is sqrt(2).
  149. /// </summary>
  150. public static PointH[] Normalize(this PointH[] points, out MatrixH T)
  151. {
  152. float n = points.Length;
  153. float xmean = 0, ymean = 0;
  154. for (int i = 0; i < points.Length; i++)
  155. {
  156. points[i].X = points[i].X / points[i].W;
  157. points[i].Y = points[i].Y / points[i].W;
  158. points[i].W = 1;
  159. xmean += points[i].X;
  160. ymean += points[i].Y;
  161. }
  162. xmean /= n; ymean /= n;
  163. float scale = 0;
  164. for (int i = 0; i < points.Length; i++)
  165. {
  166. float x = points[i].X - xmean;
  167. float y = points[i].Y - ymean;
  168. scale += (float)System.Math.Sqrt(x * x + y * y);
  169. }
  170. scale = (float)(SQRT2 * n / scale);
  171. T = new MatrixH
  172. (
  173. scale, 0, -scale * xmean,
  174. 0, scale, -scale * ymean,
  175. 0, 0, 1
  176. );
  177. return T.TransformPoints(points);
  178. }
  179. /// <summary>
  180. /// Normalizes a set of homogeneous points so that the origin is located
  181. /// at the centroid and the mean distance to the origin is sqrt(2).
  182. /// </summary>
  183. public static PointF[] Normalize(this PointF[] points, out MatrixH T)
  184. {
  185. float n = points.Length;
  186. float xmean = 0, ymean = 0;
  187. for (int i = 0; i < points.Length; i++)
  188. {
  189. points[i].X = points[i].X;
  190. points[i].Y = points[i].Y;
  191. xmean += points[i].X;
  192. ymean += points[i].Y;
  193. }
  194. xmean /= n; ymean /= n;
  195. float scale = 0;
  196. for (int i = 0; i < points.Length; i++)
  197. {
  198. float x = points[i].X - xmean;
  199. float y = points[i].Y - ymean;
  200. scale += (float)System.Math.Sqrt(x * x + y * y);
  201. }
  202. scale = (float)(SQRT2 * n / scale);
  203. T = new MatrixH
  204. (
  205. scale, 0, -scale * xmean,
  206. 0, scale, -scale * ymean,
  207. 0, 0, 1
  208. );
  209. return T.TransformPoints(points);
  210. }
  211. /// <summary>
  212. /// Detects if three points are colinear.
  213. /// </summary>
  214. public static bool Colinear(PointF pt1, PointF pt2, PointF pt3)
  215. {
  216. return System.Math.Abs(
  217. (pt1.Y - pt2.Y) * pt3.X +
  218. (pt2.X - pt1.X) * pt3.Y +
  219. (pt1.X * pt2.Y - pt1.Y * pt2.X)) < Special.SingleEpsilon;
  220. }
  221. /// <summary>
  222. /// Detects if three points are colinear.
  223. /// </summary>
  224. public static bool Colinear(PointH pt1, PointH pt2, PointH pt3)
  225. {
  226. return System.Math.Abs(
  227. (pt1.Y * pt2.W - pt1.W * pt2.Y) * pt3.X +
  228. (pt1.W * pt2.X - pt1.X * pt2.W) * pt3.Y +
  229. (pt1.X * pt2.Y - pt1.Y * pt2.X) * pt3.W) < Special.SingleEpsilon;
  230. }
  231. #endregion
  232. #region Image tools
  233. /// <summary>
  234. /// Computes the sum of the pixels in a given image.
  235. /// </summary>
  236. public static int Sum(this BitmapData image)
  237. {
  238. if (image.PixelFormat != PixelFormat.Format8bppIndexed)
  239. throw new UnsupportedImageFormatException("Only grayscale images are supported");
  240. int width = image.Width;
  241. int height = image.Height;
  242. int offset = image.Stride - image.Width;
  243. int sum = 0;
  244. unsafe
  245. {
  246. byte* src = (byte*)image.Scan0.ToPointer();
  247. for (int y = 0; y < height; y++)
  248. {
  249. for (int x = 0; x < width; x++, src++)
  250. sum += (*src);
  251. src += offset;
  252. }
  253. }
  254. return sum;
  255. }
  256. /// <summary>
  257. /// Computes the sum of the pixels in a given image.
  258. /// </summary>
  259. public static int Sum(this BitmapData image, Rectangle rectangle)
  260. {
  261. if (image.PixelFormat != PixelFormat.Format8bppIndexed)
  262. throw new UnsupportedImageFormatException("Only grayscale images are supported");
  263. int width = image.Width;
  264. int height = image.Height;
  265. int stride = image.Stride;
  266. int offset = image.Stride - image.Width;
  267. int rwidth = rectangle.Width;
  268. int rheight = rectangle.Height;
  269. int rx = rectangle.X;
  270. int ry = rectangle.Y;
  271. int sum = 0;
  272. unsafe
  273. {
  274. byte* src = (byte*)image.Scan0.ToPointer();
  275. for (int y = 0; y < rheight; y++)
  276. {
  277. byte* p = src + stride * (ry + y) + rx;
  278. for (int x = 0; x < rwidth; x++)
  279. sum += (*p++);
  280. }
  281. }
  282. return sum;
  283. }
  284. /// <summary>
  285. /// Computes the sum of the pixels in a given image.
  286. /// </summary>
  287. public static int Sum(this Bitmap image)
  288. {
  289. BitmapData data = image.LockBits(new Rectangle(0, 0, image.Width, image.Height),
  290. ImageLockMode.ReadOnly, image.PixelFormat);
  291. int sum = Sum(data);
  292. image.UnlockBits(data);
  293. return sum;
  294. }
  295. /// <summary>
  296. /// Computes the sum of the pixels in a given image.
  297. /// </summary>
  298. public static int Sum(this Bitmap image, Rectangle rectangle)
  299. {
  300. BitmapData data = image.LockBits(rectangle,
  301. ImageLockMode.ReadOnly, image.PixelFormat);
  302. int sum = Sum(data);
  303. image.UnlockBits(data);
  304. return sum;
  305. }
  306. /// <summary>
  307. /// Converts a given image into a array of double-precision
  308. /// floating-point numbers scaled between -1 and 1.
  309. /// </summary>
  310. public static double[] ToDoubleArray(this Bitmap image, int channel)
  311. {
  312. return ToDoubleArray(image, channel, -1, 1);
  313. }
  314. /// <summary>
  315. /// Converts a given image into a array of double-precision
  316. /// floating-point numbers scaled between the given range.
  317. /// </summary>
  318. public static double[] ToDoubleArray(this Bitmap image, int channel, double min, double max)
  319. {
  320. BitmapData data = image.LockBits(new Rectangle(0, 0, image.Width, image.Height),
  321. ImageLockMode.ReadOnly, image.PixelFormat);
  322. double[] array = ToDoubleArray(data, channel, min, max);
  323. image.UnlockBits(data);
  324. return array;
  325. }
  326. /// <summary>
  327. /// Converts a given image into a array of double-precision
  328. /// floating-point numbers scaled between -1 and 1.
  329. /// </summary>
  330. public static double[] ToDoubleArray(this BitmapData image, int channel)
  331. {
  332. return ToDoubleArray(image, channel, -1, 1);
  333. }
  334. /// <summary>
  335. /// Converts a given image into a array of double-precision
  336. /// floating-point numbers scaled between the given range.
  337. /// </summary>
  338. public static double[] ToDoubleArray(this BitmapData image, int channel, double min, double max)
  339. {
  340. int width = image.Width;
  341. int height = image.Height;
  342. int offset = image.Stride - image.Width;
  343. double[] data = new double[width * height];
  344. int dst = 0;
  345. unsafe
  346. {
  347. byte* src = (byte*)image.Scan0.ToPointer() + channel;
  348. for (int y = 0; y < height; y++)
  349. {
  350. for (int x = 0; x < width; x++, src++, dst++)
  351. {
  352. data[dst] = Accord.Math.Tools.Scale(0, 255, min, max, *src);
  353. }
  354. src += offset;
  355. }
  356. }
  357. return data;
  358. }
  359. /// <summary>
  360. /// Converts a given image into a array of double-precision
  361. /// floating-point numbers scaled between -1 and 1.
  362. /// </summary>
  363. public static double[][] ToDoubleArray(this Bitmap image)
  364. {
  365. return ToDoubleArray(image, -1, 1);
  366. }
  367. /// <summary>
  368. /// Converts a given image into a array of double-precision
  369. /// floating-point numbers scaled between the given range.
  370. /// </summary>
  371. public static double[][] ToDoubleArray(this Bitmap image, double min, double max)
  372. {
  373. BitmapData data = image.LockBits(new Rectangle(0, 0, image.Width, image.Height),
  374. ImageLockMode.ReadOnly, image.PixelFormat);
  375. double[][] array = ToDoubleArray(data, min, max);
  376. image.UnlockBits(data);
  377. return array;
  378. }
  379. /// <summary>
  380. /// Converts a given image into a array of double-precision
  381. /// floating-point numbers scaled between the given range.
  382. /// </summary>
  383. public static double[][] ToDoubleArray(this BitmapData image, double min, double max)
  384. {
  385. int width = image.Width;
  386. int height = image.Height;
  387. int pixelSize = System.Drawing.Image.GetPixelFormatSize(image.PixelFormat) / 8;
  388. int offset = image.Stride - image.Width * pixelSize;
  389. double[][] data = new double[width * height][];
  390. int dst = 0;
  391. unsafe
  392. {
  393. byte* src = (byte*)image.Scan0.ToPointer();
  394. for (int y = 0; y < height; y++)
  395. {
  396. for (int x = 0; x < width; x++, dst++)
  397. {
  398. double[] pixel = data[dst] = new double[pixelSize];
  399. for (int i = pixel.Length - 1; i >= 0; i--, src++)
  400. pixel[i] = Accord.Math.Tools.Scale(0, 255, min, max, *src);
  401. }
  402. src += offset;
  403. }
  404. }
  405. return data;
  406. }
  407. #endregion
  408. #region Conversions
  409. /// <summary>
  410. /// Converts an image given as a array of pixel values into
  411. /// a <see cref="System.Drawing.Bitmap"/>.
  412. /// </summary>
  413. /// <param name="pixels">An array containing the grayscale pixel
  414. /// values as <see cref="System.Double">doubles</see>.</param>
  415. /// <param name="width">The width of the resulting image.</param>
  416. /// <param name="height">The height of the resulting image.</param>
  417. /// <param name="min">The minimum value representing a color value of 0.</param>
  418. /// <param name="max">The maximum value representing a color value of 255. </param>
  419. /// <returns>A <see cref="System.Drawing.Bitmap"/> of given width and height
  420. /// containing the given pixel values.</returns>
  421. public static Bitmap ToBitmap(this double[] pixels, int width, int height, double min, double max)
  422. {
  423. Bitmap bitmap = AForge.Imaging.Image.CreateGrayscaleImage(width, height);
  424. BitmapData data = bitmap.LockBits(new Rectangle(0, 0, width, height),
  425. ImageLockMode.WriteOnly, bitmap.PixelFormat);
  426. int offset = data.Stride - width;
  427. int src = 0;
  428. unsafe
  429. {
  430. byte* dst = (byte*)data.Scan0.ToPointer();
  431. for (int y = 0; y < height; y++)
  432. {
  433. for (int x = 0; x < width; x++, src++, dst++)
  434. {
  435. *dst = (byte)Accord.Math.Tools.Scale(min, max, 0, 255, pixels[src]);
  436. }
  437. dst += offset;
  438. }
  439. }
  440. bitmap.UnlockBits(data);
  441. return bitmap;
  442. }
  443. /// <summary>
  444. /// Converts an image given as a array of pixel values into
  445. /// a <see cref="System.Drawing.Bitmap"/>.
  446. /// </summary>
  447. /// <param name="pixels">An jagged array containing the pixel values
  448. /// as double arrays. Each element of the arrays will be converted to
  449. /// a R, G, B, A value. The bits per pixel of the resulting image
  450. /// will be set according to the size of these arrays.</param>
  451. /// <param name="width">The width of the resulting image.</param>
  452. /// <param name="height">The height of the resulting image.</param>
  453. /// <param name="min">The minimum value representing a color value of 0.</param>
  454. /// <param name="max">The maximum value representing a color value of 255. </param>
  455. /// <returns>A <see cref="System.Drawing.Bitmap"/> of given width and height
  456. /// containing the given pixel values.</returns>
  457. public static Bitmap ToBitmap(this double[][] pixels, int width, int height, double min, double max)
  458. {
  459. PixelFormat format;
  460. int channels = pixels[0].Length;
  461. switch (channels)
  462. {
  463. case 1:
  464. format = PixelFormat.Format8bppIndexed;
  465. break;
  466. case 3:
  467. format = PixelFormat.Format24bppRgb;
  468. break;
  469. case 4:
  470. format = PixelFormat.Format32bppArgb;
  471. break;
  472. default:
  473. throw new ArgumentException("pixels");
  474. }
  475. Bitmap bitmap = new Bitmap(width, height, format);
  476. BitmapData data = bitmap.LockBits(new Rectangle(0, 0, width, height),
  477. ImageLockMode.WriteOnly, format);
  478. int pixelSize = System.Drawing.Image.GetPixelFormatSize(format) / 8;
  479. int offset = data.Stride - width * pixelSize;
  480. int src = 0;
  481. unsafe
  482. {
  483. byte* dst = (byte*)data.Scan0.ToPointer();
  484. for (int y = 0; y < height; y++)
  485. {
  486. for (int x = 0; x < width; x++, src++)
  487. {
  488. for (int c = channels - 1; c >= 0; c--, dst++)
  489. {
  490. *dst = (byte)Accord.Math.Tools.Scale(min, max, 0, 255, pixels[src][c]);
  491. }
  492. }
  493. dst += offset;
  494. }
  495. }
  496. bitmap.UnlockBits(data);
  497. return bitmap;
  498. }
  499. /// <summary>
  500. /// Converts an image given as a array of pixel values into
  501. /// a <see cref="System.Drawing.Bitmap"/>.
  502. /// </summary>
  503. /// <param name="pixels">An jagged array containing the pixel values
  504. /// as double arrays. Each element of the arrays will be converted to
  505. /// a R, G, B, A value. The bits per pixel of the resulting image
  506. /// will be set according to the size of these arrays.</param>
  507. /// <param name="width">The width of the resulting image.</param>
  508. /// <param name="height">The height of the resulting image.</param>
  509. /// <returns>A <see cref="System.Drawing.Bitmap"/> of given width and height
  510. /// containing the given pixel values.</returns>
  511. public static Bitmap ToBitmap(this double[][] pixels, int width, int height)
  512. {
  513. return ToBitmap(pixels, width, height, -1, 1);
  514. }
  515. #endregion
  516. }
  517. }