TIFF How to place images in an orderly manner - c#

Friends! I have a set of small images that I need to lay out as a table in a Tiff file.
How the files should look like in the final file:
I use the LibTIFF library for this.
Tell me how this can be implemented? I implement my solution in C #, but the language does not matter, since rewriting the solution is not a problem.
var Row = 10;
var Column = 10;
var PIXEL_WIDTH = 8810;
var PIXEL_HEIGHT = 11810;
//Each small image has a resolution of 881x1181
using (Tiff tiff = Tiff.Open("big.tif", "w"))
{
tiff.SetField(TiffTag.IMAGEWIDTH, PIXEL_WIDTH);
tiff.SetField(TiffTag.IMAGELENGTH, PIXEL_HEIGHT);
tiff.SetField(TiffTag.COMPRESSION, Compression.LZW);
tiff.SetField(TiffTag.PHOTOMETRIC, Photometric.RGB);
tiff.SetField(TiffTag.ORIENTATION, Orientation.TOPLEFT);
tiff.SetField(TiffTag.ROWSPERSTRIP, PIXEL_HEIGHT);
tiff.SetField(TiffTag.XRESOLUTION, 120);
tiff.SetField(TiffTag.YRESOLUTION, 120);
tiff.SetField(TiffTag.BITSPERSAMPLE, 8);
tiff.SetField(TiffTag.SAMPLESPERPIXEL, 3);
tiff.SetField(TiffTag.PLANARCONFIG, PlanarConfig.CONTIG);
int tileC = 0;
for (int row = 0; row < Row; row++)
{
for (int col = 0; col < Column; col++)
{
Bitmap bitmap = new Bitmap($"{row}x{col}.png");
byte[] raster = getImageRasterBytes(bitmap, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
tiff.WriteEncodedStrip(tileC++, raster, raster.Length);
}
}
tiff.WriteDirectory();
}
Thanks in advance!

First let's put aside the TIFF part of your question. The major problem is that you need to figure out how to organize the pixel data in memory before you can save the final image to any image type.
I will provide my own simple example to illustrate how that pixel data should be organized.
Let's say we want to combine 9 images in a 3x3 table.
Each image will be 3x3 pixels and 8-bit mono (1 channel).
That makes this example nice and simple with 9 bytes per image, and each image having a stride of 3 bytes per row.
The combined image will end up being 9x9 pixels, 81 bytes total.
These images are named A, B, C ... I
A0 is byte 0 of the pixel data for A, A1 is byte 1, and so on...
These images are going to be organized in a 3x3 table like this:
ABC
DEF
GHI
Then the final data layout would need to look like this:
byte[] pixelData = [
A0,A1,A2,B0,B1,B2,C0,C1,C2,
A3,A4,A5,B3,B4,B5,C3,C4,C5,
A6,A7,A8,B6,B7,B8,C6,C7,C8,
D0,D1,D2,E0,E1,E2,F0,F1,F2,
D3,D4,D5,E3,E4,E5,F3,F4,F5,
D6,D7,D8,E6,E7,E8,F6,F7,F8,
G0,G1,G2,H0,H1,H2,I0,I1,I2,
G3,G4,G5,H3,H4,H5,I3,I4,I5,
G6,G7,G8,H6,H7,H8,I6,I7,I8
];
The pixel array above could then be written to any image file you want. Including TIFF.
Notice in the array above:
As you iterate through that array from index 0 to 80, you will be jumping back and forth between the three images that are on the same row until you reach the next row entirely, where the next 3 images from that row are visited in the same pattern.
To achieve a memory layout like this, you can use several approaches.
TIFF files have support for breaking up a large image into equal-sized tiles. This could be used to achieve what you are asking for by writing each image to its own tile using the libTIFF library. There is a limitation that each TIFF tile must have dimensions that are multiples of 16.
The Graphics class in System.Drawing can be used to make one large blank image and then you can draw each sub-image into the large image at any desired position. (This is the easiest way to get what you want, but it can be slow.)
Doing it manually with a loop:
// For the example I have given above, in pseudo C# code
int composite_stride = image_stride * 3; // 3 is the number of images per row
int composite_size = composite_stride * image_height * 3 // 3 is the number of images per column
byte[] composite_pixels = new byte[composite_size];
// Loop over each image you want to combine
// We need some way to know which row/column the image is from, let that be assigned to table_row and table_col
// We are also assuming all images have the same width and height
foreach (image in table)
{
int comp_x = table_col * image.width;
int comp_y = table_row * image.height;
for (int y=0; y<image.height; y++)
{
// Calculate the array index that the current row starts at
int comp_row_start = comp_y * composite_stride;
for (int x=0; x<image.width; x++)
{
// Calculate the array index in the composite image to write to, and the source image index to copy from
int comp_index = comp_row_start + ((comp_x + x) * image.bytes_per_pixel);
int img_index = (y * image.stride) + (x * image.bytes_per_pixel);
composite_pixels[pixel_index] = image.pixels[img_index];
}
}
}

Related

Loading and displaying a 16 (12) bit grayscale png into a PictureBox

I'm using a framework for some camera hardware called IDS Peak and we are receiving 16 bit grayscale images back from the framework, the framework itself can write the files to disk as PNGs and that's all good and well, but how do I display them in a PictureBox in Winforms?
Windows Bitmap does not support 16 bit grayscale so the following code throws a 'Parameter is not valid.' System.ArgumentException
var image = new Bitmap(width, height, stride, System.Drawing.Imaging.PixelFormat.Format16bppGrayScale, iplImg.Data());
iplImg.Data() here is an IntPtr to the bespoke Image format of the framework.
Considering Windows Bitmap does not support the format, and I can write the files using the framework to PNGs, how can I do one of the following:
Convert to a different object type other than Bitmap to display directly in Winforms without reading from the files.
Load the 16-bit grayscale PNG files into the PictureBox control (or any other control type, it doesn't have to be a PictureBox).
(1) is preferable as it doesn't require file IO but if (2) is the only possibility that's completely fine as I need to both save and display them anyway but (1) only requires a write operation and not a secondary read.
The files before writing to disc are actually monochrome with 12 bits per pixel, packed.
While it is possible to display 16-bit images, for example by hosting a wpf control in winforms, you probably want to apply a windowing function to reduce the image to 8 bit before display.
So lets use unsafe code and pointers for speed:
var bitmapData = myBitmap.LockBits(
new Rectangle(0, 0, myBitmap.Width, myBitmap.Height),
ImageLockMode.ReadWrite,
myBitmap.PixelFormat);
try
{
var ptr= (byte*)bitmapData.Scan0;
var stride = bitmapData.Stride;
var width = bitmapData.Width;
var height= bitmapData.Height;
// Conversion Code
}
finally
{
myBitmap.UnlockBits(bitmapData);
}
or using wpf image classes, that generally have better 16-bit support:
var myBitmap= new WriteableBitmap(new BitmapImage(new Uri("myBitmap.jpg", UriKind.Relative)));
writeableBitmap.Lock();
try{
var ptr = (byte*)myBitmap.BackBuffer;
...
}
finally
{
myBitmap.Unlock();
}
To loop over all the pixels you would use a double loop:
for (int y = 0; y < height; y++)
{
var row = (ushort*)(ptr+ y * stride);
for (int x = 0; x < width; x++)
{
var pixelValue = row[x];
// Scaling code
}
}
And to scale the value you could use a linear scaling between the min and max values to the 0-255 range of a byte
var slope = (byte.MaxValue + 1f) / (maxUshortValyue - minUshortValue);
var scaled = (int)(((pixelValue + 0.5f - minUshortValue) * slope)) ;
scaled = scaled > byte.MaxValue ? byte.MaxValue: scaled;
scaled = scaled < 0 ? 0: scaled;
var byteValue = (byte)scaled;
The maxUshortValyue / minUshortValue would either be computed from the max/min value of the image, or configured by the user. You would also need to create a target image in order to write down the result into a target 8-bit grayscale bitmap to be displayed, or write down the same value for each color channel in a color image.

Get most similar image [duplicate]

This question already has answers here:
How can I measure the similarity between two images? [closed]
(17 answers)
Closed 5 years ago.
I have one Bitmap A and one array of Bitmap, in the array there is a Bitmap that looks the same as Bitmap A. I'm using the code below but it sometimes doesnt work, it iterates the entire array without finding it, it seems there are some minor differences, is there a way to change the function to return true if its 90% similar or pick the most similar image in the array? The array has only 6 images.
for(int i = 0; i < list.Count;i++)
{
if(ImageCompareString(image,list[i])
{
answerIndex = i;
break;
}
}
private static bool ImageCompareString(Bitmap firstImage, Bitmap secondImage)
{
MemoryStream ms = new MemoryStream();
firstImage.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
String firstBitmap = Convert.ToBase64String(ms.ToArray());
ms.Position = 0;
secondImage.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
String secondBitmap = Convert.ToBase64String(ms.ToArray());
if (firstBitmap.Equals(secondBitmap))
{
return true;
}
else
{
return false;
}
}
Of course there is such way... But you have to code it yourself.
First you shoud not compare the base64 data... You'll loose direct pixel value access and increase the size of the data to compare by more then 150% (Originaly 200% but corrected thanks to PeterDuniho's comment) in C# due to UTF16.
Second I assume that all pictures have the same fixed size. Before comparing, reduce the image size to something really small, but keep the width/height aspect. This will speed up the comparsion and also eliminates noise.
Third Iterate both pictures and compare their grayscaled pixel values. I Assume that you have resized the picture to 16x16. Since we're comparing their grayscale-values the value of one pixel is between 0 and 255. So the maximum distance between both pictures will be 16 * 16 * 256 = 65536. If both pictures are black, the distance between the pictures will be zero (100% similarity). If one picture is black and the other is white the distance will be 65535 (0% similarity).
To compare the images iterate the picture-pixels and subtract the grayscale-pixel-value-from-picture-a from the grayscale-pixel-value-of-picture-b at the point x,y and add the absolute difference value to the counter. This counter will be the total distance between both pictures.
Lets assume this counter has a value of 1000 after the comparison loop, you get the percentage-similarity by 1000 / 65535 ~ 1.5% difference (or 98.5% similarity) between both pictures.
pseudo-compare-code
long counter = 0;
long total = image.Width * image.Height * (Color.White - Color.Black);
for(int x = 0; x < image.Width; x++)
{
for(int y = 0; y < image.Height; y++)
{
var p1 = image.GetPixel(x, y);
var p2 = otherImage.GetPixel(x, y);
var g1 = ((p1.R + p1.G + p1.B) / 3);
var g2 = ((p2.R + p2.G + p2.B) / 3);
var distance = Math.Abs(g1 - g2);
counter += distance;
}
}
var similarity = 100 - ((counter / total) * 100);
This is an more or less easy approach, but you have to test this with you scenario/images. Instead of comparing grayscale-values you could also compare rgb-values. Look for distance definitions like the euclidean distance... Start and keep reading :)
EDIT
This is just a really basic approach that should explain how you can start comparing images. It does not take into account that there might be different image formats (jpeg, png, gif), color formats (indexed, 16bit, 24bit, 32bit) or images with different resolutions.

Adding and averaging Tiff images using Emgu.CV to create an averaged Tiff image

I am taking three images and saving them as Tiff images in order to preserve the data of the image for analysis. In my program I load these three images as Emgu.CV.Image<Rgb,ushort>. I need to add these three images together and return a final tiff image that is the average of the three seperate images. What would be the best way to go about doing this?
Using the underlying method you can access the individual pixel values of images in emgucv and then find the average.
For color images EmguCV stores Red, Green and Blue Data in layer 0,1,2 of the image respectively
for (int i = 0; i < img1.Width; i++)
{
for (int j = 0; j < img1.Height; j++)
{
img4.Data[i,j,0] = (img1.Data[i,j,0] + img2.Data[i,j,0] +img3.Data[i,j,0])/3;
img4.Data[i,j,1] = (img1.Data[i,j,1] + img2.Data[i,j,1]+ img3.Data[i,j,1])/3;
img4.Data[i,j,2] = (img1.Data[i,j,2] + img2.Data[i,j,2]+ img3.Data[i,j,2])/3;
}
}
Note: The above code assumes that all the images are of equal size(height and width),if its not the case you should iterate for the image with the biggest size, and set the unavailable images's(or smaller image's) corresponding pixels to zero before adding .
You should also use some thing as Math.Floor or similar to normalize each pixel values as pixel values can't be in decimals.

How To Read File Into A Two Dimensional Array in c#

I have a file (c:\kk.bmp). I want to read this file into a two dimensional array[Width,Height] of
byte and a two dimensional array[Width,Height] of Int32, such as
byte[,] byte_array = File.ReadAllBytes(filename_mpeg4); // Not correct
I want to read the file "filename_mpeg4" into two dimensions in an array of byte and array of Int32 in C#.
BMSs and MPEG4s are quite different things. A Bitmap has two dimensions. Mpeg4 or other video files store a list of two dimensional frames using complex compression algorithms. Therefore you have two space plus one time dimension. Your array would have to be three dimensional. It is not clear from your question whether you are trying to read an image (c:\kk.bmp) or a video (filename_mpeg4).
In both cases you need to decode the file. Don't try to do that yourself, it's very complicated (especially for videos).
Reading a bitmap is easy:
Bitmap myBitmap = new Bitmap(#"C:\kk.bmp");
This works for JPG, GIF, TIF and PNG as well. The decoders are integrated in the Windows OS.
You can draw on the image and do other things without converting it to some array.
// Example: Drawing a line on a bitmap
using (Graphics g = Graphics.FromImage( myBitmap )) {
g.DrawLine(Pens.Red, new Point(0, 0), new Point(100, 50));
}
If you still need to extract the pixel data into an array, this SO answer might help you: C# Getting the pixel data efficiently from System.Drawing.Bitmap
Videos are a completely different story. There is no easy answer to your question. This CodeProject might help you: http://www.codeproject.com/Articles/9676/Extracting-still-pictures-from-movie-files-with-C.
You can't do this with the current filestructure: you need to specify the length of at least one dimension. Something like:
int height = File.Read();
byte[] mp4 = File.ReadAllBytes(filename_mpeg4);
int width = mp4.length/height;
byte[,] byte_array = new byte[height,mp4.length/height];
int k = 0;
for(int i = 0; i < height; i++) {
for(int j = 0; j < width; j++) {
byte_array[i,j] = mp4[k++];
}
}

GDAL C# ReadAsArray

I was using GDAL API to read raster files... I found in some places that python version has ReadAsArray, I assume this takes the data of the raster file as two dimensional array, is there any similar option for C#, or at least can you show me how to do it? thanks a lot!
There is no equivalent of ReadAsArray function available in C# bindings to GDAL. The ReadAsArray is available because GDAL Python bindings are supposed to be usable with array protocol defined by NumPy so this function exists for this specific purpose.
However, you can use ReadRaster method of Band class to read pixels into 1-dimension array and then iterate over such 1-dimension array as it was 2-dimension array.
Let's assume you read pixels of a band with width x height dimensions:
byte[] bits = new byte[width * height];
band.ReadRaster(0, 0, width, height, bits, width, height, 0, 0);
Now, you can calculate index of a pixel according to this formula: column + row * width
for (int col = 0; col < width; col++)
{
for (int row = 0; row < height; row++)
{
// equivalent to bits[col][row] if bits is 2-dimension array
byte pixel = bits[col + row * width];
}
}
The ReadAsArray(0,0, xsize, ysize) function in Python version is equivalent to the ReadRaster(0,0, ds.RasterXSize, ds.RasterYSize, dstArray, ds.RasterXSize, ds.RasterYSize, 0) in C# version.

Categories

Resources