I have 2 pictures, containing part of each other:
now I want to combine them together, but not repeating the common part, to have something like that:
What is the best way to do it?
#edit: That images are only examples of what I want to do with any two images, that contain part of each other, and that part is at the bottom of the first image, and at the top of the second.
here's a solution for your question :
I defined a function which takes 2 Bitmaps and returns the Combined Bitmap
, in this function , I store first 2 rows of the second bitmap in an array of byte so I can compare them with the first bitmap in the next step , then I start looking for the matched rows in the first bitmap whenever I fined the matched row in that , I store the y position
, now I combine those bitmaps according to the y I've already found !
Here's the Demo Project : Download
Here's the Solution File : Download
here's the results for :
Example 1
Example 2
and here's the function :
private Bitmap CombineImages(Bitmap bmp_1, Bitmap bmp_2)
{
if (bmp_1.Width == bmp_2.Width)
{
int bmp_1_Height, bmpWidth, bmp_2_Height;
bmpWidth = bmp_1.Width;
bmp_1_Height = bmp_1.Height;
bmp_2_Height = bmp_2.Height;
Color c;
bool notFound = false;
int firstMatchedRow = 0;
byte[,] bmp2_first2rows = new byte[3 * bmpWidth, 2];
for (int b = 0; b < 2; b++)
{
for (int a = 0; a < bmpWidth; a++)
{
c = bmp_2.GetPixel(a, b);
bmp2_first2rows[a * 3, b] = c.R;
bmp2_first2rows[a * 3 + 1, b] = c.G;
bmp2_first2rows[a * 3 + 2, b] = c.B;
}
}
for (int y = 0; y < bmp_1_Height - 1; y++)
{
for (int j = 0; j < 2; j++)
{
for (int x = 0; x < bmpWidth; x++)
{
c = bmp_1.GetPixel(x, y + j);
if ((bmp2_first2rows[x * 3, j] == c.R) &&
(bmp2_first2rows[x * 3 + 1, j] == c.G) &&
(bmp2_first2rows[x * 3 + 2, j] == c.B))
{
}
else
{
notFound = true;
break;
}
}
if (notFound)
{
break;
}
}
if (!notFound)
{
firstMatchedRow = y;
break;
}
else
{
notFound = false;
}
}
if (firstMatchedRow > 0)
{
Bitmap bmp = new Bitmap(bmpWidth, firstMatchedRow + bmp_2_Height);
Graphics g = Graphics.FromImage(bmp);
Rectangle RectDst = new Rectangle(0, 0, bmpWidth, firstMatchedRow);
Rectangle RectSrc;
g.DrawImage(bmp_1, RectDst, RectDst, GraphicsUnit.Pixel);
RectDst = new Rectangle(0, firstMatchedRow, bmpWidth, bmp_2_Height);
RectSrc = new Rectangle(0, 0, bmpWidth, bmp_2_Height);
g.DrawImage(bmp_2, RectDst, RectSrc, GraphicsUnit.Pixel);
return bmp;
}
else
{
return null;
}
}
else
{
return null;
}
}
And Finally I should mention that , these Fantastic Tutorials have Helped me a lot in Image Processing :
Image Processing for Dummies with C# and GDI+
Image Processing using C#
Hope it Helps :)
Related
I need to apply mask (3x3) to image manually by iterating through it and calculating new values for each pixel. My code seems to work, but there is an issue - new value might not be between 0 and 255, it can be higher or lower. Such values are not acceptable. And now I don't know how to correctly solve it.
Apparently normalization to 0-255 will work, but here I need to normalize whole set of values which are known after loop is finished. This means I need to iterate through entire image twice.
You can also rate my code, maybe something can be improved, and maybe it actually doesn't work correctly despite compiling and creating processed images.
public Image<Rgba32> ApplyMask3x3(int[,] mask, Image<Rgba32> image)
{
Image<Rgba32> result = new Image<Rgba32>(image.Width, image.Height);
for(int row = 0; row < image.Height; row++)
{
for(int col = 0; col < image.Width; col++)
{
byte R = 0, G = 0, B = 0;
//temporary - forcefully throw new values to 0-255
int valR = ApplyMaskR3x3(col, row, image, mask); //code below
int valG = ApplyMaskG3x3(col, row, image, mask); //identical code as R but uses G attribute
int valB = ApplyMaskB3x3(col, row, image, mask); //identical code as R but uses B attribute
if (valR > 255) valR = 255;
if (valR < 0) valR = 0;
if (valG > 255) valG = 255;
if (valG < 0) valG = 0;
if (valB > 255) valB = 255;
if (valB < 0) valB = 0;
R = Convert.ToByte(valR);
G = Convert.ToByte(valG);
B = Convert.ToByte(valB); ;
result[col, row] = new Rgba32(R,G,B);
}
}
return result;
}
private int ApplyMaskR3x3(int col, int row, Image<Rgba32> image, int[,] mask)
{
int a, b, c;
int d, e, f;
int g, h, i;
if (col == 0 || row == 0)
a = 0;
else
a = image[col - 1, row - 1].R * mask[0, 0];
if (row == 0)
b = 0;
else
b = image[col, row - 1].R * mask[0, 1];
if (row == 0 || col == image.Width - 1)
c = 0;
else
c = image[col + 1, row - 1].R * mask[0, 2];
if (col == 0)
d = 0;
else
d = image[col - 1, row].R * mask[1, 0];
e = image[col, row].R * mask[1, 1];
if (col == image.Width - 1)
f = 0;
else
f = image[col + 1, row].R * mask[1, 2];
if (col == 0 || row == image.Height - 1)
g = 0;
else
g = image[col - 1, row + 1].R * mask[2, 0];
if (row == image.Height - 1)
h = 0;
else
h = image[col, row + 1].R * mask[2, 1];
if (col == image.Width - 1 || row == image.Height - 1)
i = 0;
else
i = image[col + 1, row + 1].R * mask[2, 2];
return a + b + c + d + e + f + g + h + i;
}
You should be able to make use of the Filter processor built in to ImageSharp
From your code sample you should be able to do this
public Image<Rgba32> ApplyMask3x3(int[,] mask, Image<Rgba32> image)
{
var matrix = new ColorMatrix(
mask[0,0],
mask[0,1],
mask[0,2],
mask[1,0],
mask[1,1],
mask[1,2],
mask[2,0],
mask[2,1],
mask[2,2]
);
image.Mutate(x=>x.Filter(matrix));
return image;
}
replace .Mutate() with .Clone() if you actually need a 2nd image and don't want to mutate in place.
If you Clone you need to make sure that both source and output images are disposed of correctly otherwise you can end up causing memory issues.
I am currently trying to port the code from https://github.com/AsuharietYgvar/AppleNeuralHash2ONNX/blob/master/nnhash.py to C# using Micrisoft.ML.OnnxRuntime. For Image loading I use SixLabors.ImageSharp.
So far loading the onnx session file works but I think I have trouble preprocessing the input image in a correct way.
The original code does like so:
image = Image.open(sys.argv[3]).convert('RGB')
image = image.resize([360, 360])
arr = np.array(image).astype(np.float32) / 255.0
arr = arr * 2.0 - 1.0
arr = arr.transpose(2, 0, 1).reshape([1, 3, 360, 360])
And now I have trouble finding out how to transpose the image data array. My code so far is like this:
img.Mutate(x =>
{
x.Resize(new ResizeOptions
{
Size = new SixLabors.ImageSharp.Size(360, 360),
Mode = SixLabors.ImageSharp.Processing.ResizeMode.Stretch
});
});
Tensor<float> arr = new DenseTensor<float>(new int[] { 1, 3, 360, 360 });
for (int y = 0; y < img.Height; y++)
{
Span<Rgb24> pixelSpan = img.GetPixelRowSpan(y);
for (int x = 0; x < img.Width; x++)
{
arr[0, 0, y, x] = (pixelSpan[x].R / 255f * 2) - 1;
arr[0, 1, y, x] = (pixelSpan[x].G / 255f * 2) - 1;
arr[0, 2, y, x] = (pixelSpan[x].B / 255f * 2) - 1;
}
}
but it seems like setting the order/layout of the Tensor is wrong. The problem is, I cant find any transform function in the Microsoft.ML.OnnxRuntime library to resemble the last line (arr.transpose(2, 0, 1).reshape([1, 3, 360, 360])) in the original code.
So how could I correctly build the input image Tensor for Microsoft.ML.OnnxRuntime?
The transpose is not required as you are already creating the dense tensor in the correct shape. In the python code, we are taking an array that is [360, 360, 3] and transposing that into a array of [1, 3, 360, 360]. While your dotnet code is taking the RGB values from the row, and placing the values directly into the tensor.
This is the code that we have been using to do this is below:
public virtual Tensor<float> ConvertImageToTensor(ref List<Image<Rgb48>> images, int[] inputDimension)
{
inputDimension[0] = images.Count;
Tensor<float> input = new DenseTensor<float>(inputDimension);
for (var i = 0; i < images.Count; i++)
{
var image = images[i];
for (var y = 0; y < image.Height; y++)
{
var pixelSpan = image.GetPixelRowSpan(y);
for (var x = 0; x < image.Width; x++)
{
input[i, 0, y, x] = pixelSpan[x].R;
input[i, 1, y, x] = pixelSpan[x].G;
input[i, 2, y, x] = pixelSpan[x].B;
}
}
}
return input;
}
I have a function which will find a smaller image within a larger image and return me it's position. I want to set a tolerance threshold so that even if a similar (but not exactly the same) is present, it returns it's position as well.
If possible I would also want it to work if the smaller image is rotated.
I tried finding edges in the image using OpenCV, but the edges in the haystack and those in the needle image are never the exact same and it never matches.
public Point? Find(Bitmap haystack, Bitmap needle)
{
if (null == haystack || null == needle)
{
return null;
}
if (haystack.Width < needle.Width || haystack.Height < needle.Height)
{
return null;
}
var haystackArray = GetPixelArray(haystack);
var needleArray = GetPixelArray(needle);
foreach (var firstLineMatchPoint in FindMatch(haystackArray.Take(haystack.Height - needle.Height), needleArray[0]))
{
if (IsNeedlePresentAtLocation(haystackArray, needleArray, firstLineMatchPoint, 1))
{
return firstLineMatchPoint;
}
}
return null;
}
private int[][] GetPixelArray(Bitmap bitmap)
{
var result = new int[bitmap.Height][];
var bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly,
PixelFormat.Format32bppArgb);
for (int y = 0; y < bitmap.Height; ++y)
{
result[y] = new int[bitmap.Width];
Marshal.Copy(bitmapData.Scan0 + y*bitmapData.Stride, result[y], 0, result[y].Length);
}
bitmap.UnlockBits(bitmapData);
return result;
}
private IEnumerable<Point> FindMatch(IEnumerable<int[]> haystackLines, int[] needleLine)
{
var y = 0;
foreach (var haystackLine in haystackLines)
{
for (int x = 0, n = haystackLine.Length - needleLine.Length; x < n; ++x)
{
if (ContainSameElements(haystackLine, x, needleLine, 0, needleLine.Length))
{
yield return new Point(x, y);
}
}
y += 1;
}
}
private bool ContainSameElements(int[] first, int firstStart, int[] second, int secondStart, int length)
{
for (int i = 0; i < length; ++i)
{
if (first[i + firstStart] != second[i + secondStart])
{
return false;
}
}
return true;
}
private bool IsNeedlePresentAtLocation(int[][] haystack, int[][] needle, Point point, int alreadyVerified)
{
//we already know that "alreadyVerified" lines already match, so skip them
for (int y = alreadyVerified; y < needle.Length; ++y)
{
if ( ! ContainSameElements(haystack[y + point.Y], point.X, needle[y], 0, needle.Length))
{
return false;
}
}
return true;
}
How can I achieve this ?
For the first: You have to define your metric for comparing two pixels
I can imagine a distance in the cube defined by RGB, or a distance in the cylinder of HSV (HSV should be more accurate)
Example:
static double GetMetric(Pixel a, Pixel b)
{
double dR = a.R - b.R;
double dG = a.G - b.G;
double dB = a.B - b.B;
return Math.Sqrt(dR * dR + dG * dG + dB * dB);
}
Then just create a window search algorithm. Create window in the haste (same size as needle). Then try every possible position of window and calculate window distance as the sum of pixel distances.
You do not recalculate whole window. While moving window to the right, just recalculate and subtract left column (the one which was removed) and calculate and add right (new) column.
Then you need to remember minimal distance and its location and just compare it.
The result is the closest window to the needle. (Depends on used metric).
A simplified example:
static void Find(Bitmap Haste, Bitmap Needle)
{
//Simplified for Haste.Height = Needle.Height
//But Haste.Width > Needle.Width
int minX = 0;
int minY = 0;
double minMetric = double.MaxValue;
//Setup first window
double actualMetric = 0;
for (int i = 0; i < Needle.Width; i++)
{
for (int j = 0; j < Needle.Height; j++)
{
actualMetric += GetMatric(Needle.GetPixel(i, j), Haste.GetPixel(i, j));
}
}
minMetric = actualMetric;
//Move window to the right
for (int i = 0; i < Haste.Width - Needle.Width; i++)
{
for (int j = 0; j < Needle.Height; j++)
{
//Subtract left column pixel
actualMetric -= GetMatric(Needle.GetPixel(i, j), Haste.GetPixel(i, j));
//Add right column pixel
actualMetric += GetMatric(Needle.GetPixel(i + Needle.Width, j), Haste.GetPixel(i + Needle.Width, j));
}
//Compare
if(actualMetric < minMetric)
{
minX = i;
minY = 0; // Because Y is fixed while simplification Haste and Needle Height
}
}
}
I have a Problem to Convert an RGB Image to a 2D double array that each element is between 0 to 1 in C#. I used a function to read each pixel of an Image then average and scale them to convert it into a 2d array. but this code is too slow. for a 36 Mega Pixel image it takes about 30 seconds in comparison with MATLAB that it takes just 0.5 second. since it is vital for my project to increase the speed of processing I don't know to do it
public static double[,] Image2Matrix(Bitmap Image)
{
int nR = Image.Size.Height;
int nC = Image.Size.Width;
double S=0 ;
Color C;
double[,] M = new double[nR, nC];
for (int i = 0; i < nR; i++)
{
for (int j = 0; j < nC; j++)
{
C= Image.GetPixel(j, i);
M[i, j] = (Convert.ToDouble(C.R) + Convert.ToDouble(C.B)+Convert.ToDouble(C.G)) / (3*255);
}
}
return M;
}
With a bit of LockBits and unsafe code it's possible to speed things up about 100 times.
Solution mostly cribbed from here.
public static double[,] Image2Matrix(Bitmap image)
{
if (image == null)
throw new ArgumentNullException("image");
var data = image.LockBits(
new Rectangle(0, 0, image.Width, image.Height),
ImageLockMode.ReadOnly, image.PixelFormat);
double[,] matrix = new double[image.Height, image.Width];
try
{
unsafe
{
byte* ptr = (byte*)data.Scan0;
for (int i = 0; i < data.Height; i++)
{
for (int j = 0; j < data.Width; j++)
{
matrix[i, j] = (ptr[0] + ptr[1] + ptr[2]) / (3d * 255);
ptr += 3;
}
ptr += data.Stride - data.Width * 3;
}
return matrix;
}
}
finally
{
image.UnlockBits(data);
}
}
A quick benchmark:
spender's method took : 59ms
sadegh's method took : 5162ms
arrays are identical : True
spender's method took : 66ms
sadegh's method took : 5133ms
arrays are identical : True
spender's method took : 68ms
sadegh's method took : 5168ms
arrays are identical : True
By parallelizing the summing, this goes even faster on my machine. YMMV.
public static double[,] Image2Matrix2(Bitmap image)
{
if (image == null)
throw new ArgumentNullException("image");
var data = image.LockBits(
new Rectangle(0, 0, image.Width, image.Height),
ImageLockMode.ReadOnly, image.PixelFormat);
double[,] matrix = new double[image.Height, image.Width];
try
{
unsafe
{
byte* scan0 = (byte*)data.Scan0;
Parallel.For(0, data.Height, i => {
for (int j = 0; j < data.Width; j++)
{
byte* ptr = scan0 + (i * data.Stride + j * 3);
matrix[i, j] = (ptr[0] + ptr[1] + ptr[2]) / (3d * 255);
}
});
return matrix;
}
}
finally
{
image.UnlockBits(data);
}
}
spender's method took : 22ms
sadegh's method took : 5284ms
first time working with C# here. I am reading a few images files, do some calculations, and output and array of double. I need to be able to save this double array (or these, since I will have multiples arrays) to a greyscale image. I have been looking around on the internet, I couldn't find much. i have done it on Python and Mathlab, but C# doesn't seems to be as friendly to me. here is what I have done so far (for the double image creation).
static Image MakeImage(double[,] data)
{
Image img = new Bitmap(data.GetUpperBound(1), data.GetUpperBound(0));
//Bitmap bitmap = new Bitmap(data.GetUpperBound(1), data.GetUpperBound(0));
for (int i = 0; i < data.GetUpperBound(1); i++)
{
for (int k = 0; k < data.GetUpperBound(0); k++)
{
//bitmap.SetPixel(k, i, Color.FromArgb((int)data[i, k],(int) data[i, k],(int) data[i, k]));
}
}
return img;
}
}
}
This code actually doesnt do much. It create my blank image template. color doesnt take double as input. I have no Idea how to create an image from data... I am stuck =)
thank you in advance.
If you can accept using an unsafe block this is pretty fast:
private Image CreateImage(double[,] data)
{
double min = data.Min();
double max = data.Max();
double range = max - min;
byte v;
Bitmap bm = new Bitmap(data.GetLength(0), data.GetLength(1));
BitmapData bd = bm.LockBits(new Rectangle(0, 0, bm.Width, bm.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
// This is much faster than calling Bitmap.SetPixel() for each pixel.
unsafe
{
byte* ptr = (byte*)bd.Scan0;
for (int j = 0; j < bd.Height; j++)
{
for (int i = 0; i < bd.Width; i++)
{
v = (byte)(255 * (data[i, bd.Height - 1 - j] - min) / range);
ptr[0] = v;
ptr[1] = v;
ptr[2] = v;
ptr[3] = (byte)255;
ptr += 4;
}
ptr += (bd.Stride - (bd.Width * 4));
}
}
bm.UnlockBits(bd);
return bm;
}