namespace VisualMath.Accord.Imaging { using System; using System.Drawing; using VisualMath.Accord.Math; using VisualMath.Accord.Math.Decompositions; using System.Drawing.Imaging; using AForge.Imaging; /// /// Static tool functions for imaging. /// /// public static class Tools { private const double SQRT2 = 1.4142135623730951; #region Algebra and geometry tools /// /// Creates an homography matrix matching points /// from a set of points to another. /// public static MatrixH Homography(PointH[] points1, PointH[] points2) { // Initial argument checkings if (points1.Length != points2.Length) throw new ArgumentException("The number of points should be equal."); if (points1.Length < 4) throw new ArgumentException("At least four points are required to fit an homography"); int N = points1.Length; MatrixH T1, T2; // Normalize input points points1 = Tools.Normalize(points1, out T1); points2 = Tools.Normalize(points2, out T2); // Create the matrix A double[,] A = new double[3 * N, 9]; for (int i = 0; i < N; i++) { PointH X = points1[i]; double x = points2[i].X; double y = points2[i].Y; double w = points2[i].W; int r = 3 * i; A[r, 0] = 0; A[r, 1] = 0; A[r, 2] = 0; A[r, 3] = -w * X.X; A[r, 4] = -w * X.Y; A[r, 5] = -w * X.W; A[r, 6] = y * X.X; A[r, 7] = y * X.Y; A[r, 8] = y * X.W; r++; A[r, 0] = w * X.X; A[r, 1] = w * X.Y; A[r, 2] = w * X.W; A[r, 3] = 0; A[r, 4] = 0; A[r, 5] = 0; A[r, 6] = -x * X.X; A[r, 7] = -x * X.Y; A[r, 8] = -x * X.W; r++; A[r, 0] = -y * X.X; A[r, 1] = -y * X.Y; A[r, 2] = -y * X.W; A[r, 3] = x * X.X; A[r, 4] = x * X.Y; A[r, 5] = x * X.W; A[r, 6] = 0; A[r, 7] = 0; A[r, 8] = 0; } // Create the singular value decomposition SingularValueDecomposition svd = new SingularValueDecomposition(A, false, true); double[,] V = svd.RightSingularVectors; // Extract the homography matrix MatrixH H = new MatrixH((float)V[0, 8], (float)V[1, 8], (float)V[2, 8], (float)V[3, 8], (float)V[4, 8], (float)V[5, 8], (float)V[6, 8], (float)V[7, 8], (float)V[8, 8]); // Denormalize H = T2.Inverse().Multiply(H.Multiply(T1)); return H; } /// /// Creates an homography matrix matching points /// from a set of points to another. /// public static MatrixH Homography(PointF[] points1, PointF[] points2) { // Initial argument checkings if (points1.Length != points2.Length) throw new ArgumentException("The number of points should be equal."); if (points1.Length < 4) throw new ArgumentException("At least four points are required to fit an homography"); int N = points1.Length; MatrixH T1, T2; // Normalize input points points1 = Tools.Normalize(points1, out T1); points2 = Tools.Normalize(points2, out T2); // Create the matrix A double[,] A = new double[3 * N, 9]; for (int i = 0; i < N; i++) { PointF X = points1[i]; double x = points2[i].X; double y = points2[i].Y; int r = 3 * i; A[r, 0] = 0; A[r, 1] = 0; A[r, 2] = 0; A[r, 3] = -X.X; A[r, 4] = -X.Y; A[r, 5] = -1; A[r, 6] = y * X.X; A[r, 7] = y * X.Y; A[r, 8] = y; r++; A[r, 0] = X.X; A[r, 1] = X.Y; A[r, 2] = 1; A[r, 3] = 0; A[r, 4] = 0; A[r, 5] = 0; A[r, 6] = -x * X.X; A[r, 7] = -x * X.Y; A[r, 8] = -x; r++; A[r, 0] = -y * X.X; A[r, 1] = -y * X.Y; A[r, 2] = -y; A[r, 3] = x * X.X; A[r, 4] = x * X.Y; A[r, 5] = x; A[r, 6] = 0; A[r, 7] = 0; A[r, 8] = 0; } // Create the singular value decomposition SingularValueDecomposition svd = new SingularValueDecomposition(A, false, true); double[,] V = svd.RightSingularVectors; // Extract the homography matrix MatrixH H = new MatrixH((float)V[0, 8], (float)V[1, 8], (float)V[2, 8], (float)V[3, 8], (float)V[4, 8], (float)V[5, 8], (float)V[6, 8], (float)V[7, 8], (float)V[8, 8]); // Denormalize H = T2.Inverse().Multiply(H.Multiply(T1)); return H; } /// /// Normalizes a set of homogeneous points so that the origin is located /// at the centroid and the mean distance to the origin is sqrt(2). /// public static PointH[] Normalize(this PointH[] points, out MatrixH T) { float n = points.Length; float xmean = 0, ymean = 0; for (int i = 0; i < points.Length; i++) { points[i].X = points[i].X / points[i].W; points[i].Y = points[i].Y / points[i].W; points[i].W = 1; xmean += points[i].X; ymean += points[i].Y; } xmean /= n; ymean /= n; float scale = 0; for (int i = 0; i < points.Length; i++) { float x = points[i].X - xmean; float y = points[i].Y - ymean; scale += (float)System.Math.Sqrt(x * x + y * y); } scale = (float)(SQRT2 * n / scale); T = new MatrixH ( scale, 0, -scale * xmean, 0, scale, -scale * ymean, 0, 0, 1 ); return T.TransformPoints(points); } /// /// Normalizes a set of homogeneous points so that the origin is located /// at the centroid and the mean distance to the origin is sqrt(2). /// public static PointF[] Normalize(this PointF[] points, out MatrixH T) { float n = points.Length; float xmean = 0, ymean = 0; for (int i = 0; i < points.Length; i++) { points[i].X = points[i].X; points[i].Y = points[i].Y; xmean += points[i].X; ymean += points[i].Y; } xmean /= n; ymean /= n; float scale = 0; for (int i = 0; i < points.Length; i++) { float x = points[i].X - xmean; float y = points[i].Y - ymean; scale += (float)System.Math.Sqrt(x * x + y * y); } scale = (float)(SQRT2 * n / scale); T = new MatrixH ( scale, 0, -scale * xmean, 0, scale, -scale * ymean, 0, 0, 1 ); return T.TransformPoints(points); } /// /// Detects if three points are colinear. /// public static bool Colinear(PointF pt1, PointF pt2, PointF pt3) { return System.Math.Abs( (pt1.Y - pt2.Y) * pt3.X + (pt2.X - pt1.X) * pt3.Y + (pt1.X * pt2.Y - pt1.Y * pt2.X)) < Special.SingleEpsilon; } /// /// Detects if three points are colinear. /// public static bool Colinear(PointH pt1, PointH pt2, PointH pt3) { return System.Math.Abs( (pt1.Y * pt2.W - pt1.W * pt2.Y) * pt3.X + (pt1.W * pt2.X - pt1.X * pt2.W) * pt3.Y + (pt1.X * pt2.Y - pt1.Y * pt2.X) * pt3.W) < Special.SingleEpsilon; } #endregion #region Image tools /// /// Computes the sum of the pixels in a given image. /// public static int Sum(this BitmapData image) { if (image.PixelFormat != PixelFormat.Format8bppIndexed) throw new UnsupportedImageFormatException("Only grayscale images are supported"); int width = image.Width; int height = image.Height; int offset = image.Stride - image.Width; int sum = 0; unsafe { byte* src = (byte*)image.Scan0.ToPointer(); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++, src++) sum += (*src); src += offset; } } return sum; } /// /// Computes the sum of the pixels in a given image. /// public static int Sum(this BitmapData image, Rectangle rectangle) { if (image.PixelFormat != PixelFormat.Format8bppIndexed) throw new UnsupportedImageFormatException("Only grayscale images are supported"); int width = image.Width; int height = image.Height; int stride = image.Stride; int offset = image.Stride - image.Width; int rwidth = rectangle.Width; int rheight = rectangle.Height; int rx = rectangle.X; int ry = rectangle.Y; int sum = 0; unsafe { byte* src = (byte*)image.Scan0.ToPointer(); for (int y = 0; y < rheight; y++) { byte* p = src + stride * (ry + y) + rx; for (int x = 0; x < rwidth; x++) sum += (*p++); } } return sum; } /// /// Computes the sum of the pixels in a given image. /// public static int Sum(this Bitmap image) { BitmapData data = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, image.PixelFormat); int sum = Sum(data); image.UnlockBits(data); return sum; } /// /// Computes the sum of the pixels in a given image. /// public static int Sum(this Bitmap image, Rectangle rectangle) { BitmapData data = image.LockBits(rectangle, ImageLockMode.ReadOnly, image.PixelFormat); int sum = Sum(data); image.UnlockBits(data); return sum; } /// /// Converts a given image into a array of double-precision /// floating-point numbers scaled between -1 and 1. /// public static double[] ToDoubleArray(this Bitmap image, int channel) { return ToDoubleArray(image, channel, -1, 1); } /// /// Converts a given image into a array of double-precision /// floating-point numbers scaled between the given range. /// public static double[] ToDoubleArray(this Bitmap image, int channel, double min, double max) { BitmapData data = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, image.PixelFormat); double[] array = ToDoubleArray(data, channel, min, max); image.UnlockBits(data); return array; } /// /// Converts a given image into a array of double-precision /// floating-point numbers scaled between -1 and 1. /// public static double[] ToDoubleArray(this BitmapData image, int channel) { return ToDoubleArray(image, channel, -1, 1); } /// /// Converts a given image into a array of double-precision /// floating-point numbers scaled between the given range. /// public static double[] ToDoubleArray(this BitmapData image, int channel, double min, double max) { int width = image.Width; int height = image.Height; int offset = image.Stride - image.Width; double[] data = new double[width * height]; int dst = 0; unsafe { byte* src = (byte*)image.Scan0.ToPointer() + channel; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++, src++, dst++) { data[dst] = Accord.Math.Tools.Scale(0, 255, min, max, *src); } src += offset; } } return data; } /// /// Converts a given image into a array of double-precision /// floating-point numbers scaled between -1 and 1. /// public static double[][] ToDoubleArray(this Bitmap image) { return ToDoubleArray(image, -1, 1); } /// /// Converts a given image into a array of double-precision /// floating-point numbers scaled between the given range. /// public static double[][] ToDoubleArray(this Bitmap image, double min, double max) { BitmapData data = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, image.PixelFormat); double[][] array = ToDoubleArray(data, min, max); image.UnlockBits(data); return array; } /// /// Converts a given image into a array of double-precision /// floating-point numbers scaled between the given range. /// public static double[][] ToDoubleArray(this BitmapData image, double min, double max) { int width = image.Width; int height = image.Height; int pixelSize = System.Drawing.Image.GetPixelFormatSize(image.PixelFormat) / 8; int offset = image.Stride - image.Width * pixelSize; double[][] data = new double[width * height][]; int dst = 0; unsafe { byte* src = (byte*)image.Scan0.ToPointer(); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++, dst++) { double[] pixel = data[dst] = new double[pixelSize]; for (int i = pixel.Length - 1; i >= 0; i--, src++) pixel[i] = Accord.Math.Tools.Scale(0, 255, min, max, *src); } src += offset; } } return data; } #endregion #region Conversions /// /// Converts an image given as a array of pixel values into /// a . /// /// An array containing the grayscale pixel /// values as doubles. /// The width of the resulting image. /// The height of the resulting image. /// The minimum value representing a color value of 0. /// The maximum value representing a color value of 255. /// A of given width and height /// containing the given pixel values. public static Bitmap ToBitmap(this double[] pixels, int width, int height, double min, double max) { Bitmap bitmap = AForge.Imaging.Image.CreateGrayscaleImage(width, height); BitmapData data = bitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, bitmap.PixelFormat); int offset = data.Stride - width; int src = 0; unsafe { byte* dst = (byte*)data.Scan0.ToPointer(); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++, src++, dst++) { *dst = (byte)Accord.Math.Tools.Scale(min, max, 0, 255, pixels[src]); } dst += offset; } } bitmap.UnlockBits(data); return bitmap; } /// /// Converts an image given as a array of pixel values into /// a . /// /// An jagged array containing the pixel values /// as double arrays. Each element of the arrays will be converted to /// a R, G, B, A value. The bits per pixel of the resulting image /// will be set according to the size of these arrays. /// The width of the resulting image. /// The height of the resulting image. /// The minimum value representing a color value of 0. /// The maximum value representing a color value of 255. /// A of given width and height /// containing the given pixel values. public static Bitmap ToBitmap(this double[][] pixels, int width, int height, double min, double max) { PixelFormat format; int channels = pixels[0].Length; switch (channels) { case 1: format = PixelFormat.Format8bppIndexed; break; case 3: format = PixelFormat.Format24bppRgb; break; case 4: format = PixelFormat.Format32bppArgb; break; default: throw new ArgumentException("pixels"); } Bitmap bitmap = new Bitmap(width, height, format); BitmapData data = bitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, format); int pixelSize = System.Drawing.Image.GetPixelFormatSize(format) / 8; int offset = data.Stride - width * pixelSize; int src = 0; unsafe { byte* dst = (byte*)data.Scan0.ToPointer(); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++, src++) { for (int c = channels - 1; c >= 0; c--, dst++) { *dst = (byte)Accord.Math.Tools.Scale(min, max, 0, 255, pixels[src][c]); } } dst += offset; } } bitmap.UnlockBits(data); return bitmap; } /// /// Converts an image given as a array of pixel values into /// a . /// /// An jagged array containing the pixel values /// as double arrays. Each element of the arrays will be converted to /// a R, G, B, A value. The bits per pixel of the resulting image /// will be set according to the size of these arrays. /// The width of the resulting image. /// The height of the resulting image. /// A of given width and height /// containing the given pixel values. public static Bitmap ToBitmap(this double[][] pixels, int width, int height) { return ToBitmap(pixels, width, height, -1, 1); } #endregion } }