Get single pixel of TIFF image on Android - java

I ideally need to have offline access to information of the altitude of the terrain of Mexico on an Android app I'm developing. I downloaded a .bil file and converted it to a .tif file with QGIS, and the resulting file is almost 900 MB.
I don'I know if it would work, I'm still learning to develop Android apps, but I was planning to store it in the SD card and I was wondering if it could be possible to access to a single pixel without reading the whole image, because I know that's impossible.
Can anyone tell me if it is possible? And if it is, how to do it? Or any other way to get the information I need maybe converting the .bil file to other format or something like that.
Thanks for answering.

Tiff has images stored as rows of bytes starting at defined offsets. So you can easily retrieve single pixel and certainly no need to load the full image.
If you open any tif file in hex editor you will see that first 4 bytes mark tiff by code. And next 4 bytes give offset for metadata about tif image.
Use random access file to open image tif file, then seek the offset and you land into metadata space.From here you can pick offset of required pixel. Then go and get it..
We have tiff for this purpose only. That is to access individual pixels. If we needed full load of image then jpeg or BMP was enough.

check this link for full code:- full example to decode tiff image
package com.tif;
import android.os.*;import android.content.*;import android.app.*;import android.widget.*;import android.view.*;
import android.view.View.*;import android.graphics.*;import java.io.*;import java.util.*;import android.util.*;
import java.lang.*;import java.nio.*;import java.nio.channels.*;
public class Main extends Activity
{
private static final int CLEAR_CODE = 256;
private static final int EOI_CODE = 257;
long bytesCount=0L;
ScrollView sv;TextView tv;ImageView iv;
List intList;
long[] stripOffs,stripBytes;
byte[] bytes,ubytes,bmpBytes;
ByteBuffer bBuff;
BitBuffer bitBuff;
int entries,type,tag;
long count,value,ifd,stripAt,stripCount,stripBytesAt,rows,cols;
String txt="Null",path="",decompressed="";
String[] info= {"width","length","bitsPerSample","Compression","PhotometricInterpretation","FillOrder","StripOffsets","SamplesPerPixel","RowsPerStrip"
,"StripBytes","XResolution","YResolution","PlanarConfig","ResolutionUnit","extra","NextIFD"};
Bitmap bmp=null;
class DotsView extends View
{
int i = 0;Bitmap bmp;Canvas cnv;Rect bounds;Paint p;int width,height;
int alfa,red,green,blue;
public DotsView(Context context ,int width ,int height)
{
super(context);
this.width = width;
this.height = height;
bmp = Bitmap.createBitmap(width,height,Bitmap.Config.ARGB_8888);
cnv = new Canvas(bmp);
bounds = new Rect(0 , 0, width,height);
p = new Paint();
}
#Override
protected void onDraw(Canvas c)
{
for(int i=0;i<width;i++)
for(int j=0;j<height;j++)
{
for(int pix=0;pix<3;pix++)
{
if(pix==0)blue=bmpBytes[i+j+pix];
if(pix==1)green=bmpBytes[i+j+pix];
if(pix==2)red=bmpBytes[i+j+pix];
}
p.setColor( Color.argb(255, red,green,blue) );
cnv.drawPoint(i,j, p);
}
c.drawBitmap(bmp, null, bounds , null);
invalidate();
}
}
public int myShort(short sh)
{ int i;
ByteBuffer shortBuff=ByteBuffer.allocate(4);
shortBuff.order(ByteOrder.BIG_ENDIAN);shortBuff.putShort(sh);shortBuff.rewind();
shortBuff.order(ByteOrder.LITTLE_ENDIAN);sh=shortBuff.getShort();
if(sh<0)i=(int)(sh+32768); else i=(int)sh;
return i;
}
public long myInt(int i)
{ long l=0L;
ByteBuffer intBuff=ByteBuffer.allocate(4);
intBuff.order(ByteOrder.BIG_ENDIAN);intBuff.putInt(i);intBuff.rewind();
intBuff.order(ByteOrder.LITTLE_ENDIAN); i=intBuff.getInt();
if(i<0)l=(long)(i+2147483648L); else l=(long)i;
return l;
}
public String tagInfo(int tag)
{ int i=0;
switch(tag)
{case 256: i=0;break;case 257: i=1;break;case 258: i=2;break;case 259: i=3;break;case 262: i=4;break;case 266: i=5;break;
case 273: i=6;break;case 277: i=7;break;case 278: i=8;break;case 279: i=9;break;case 282: i=10;break;case 283: i=11;break;
case 284: i=12;break;case 296: i=13;break;case 1496: i=14;break;case 0: i=15;break;
}
return info[i];
}
public void extractTif()
{
String taginfo="";String strVal="";
FileInputStream fis;BufferedInputStream bis;DataInputStream dis;
path=Environment.getExternalStorageDirectory().getPath();
path=path+"/DCIM"+"/kpd.tif";
try {
fis=new FileInputStream(path);bis=new BufferedInputStream(fis);dis=new DataInputStream(bis);
dis.skip(4);ifd=myInt(dis.readInt());
txt="TIFF-IFD: "; txt=txt+ifd;
dis.skip(ifd-8); entries=myShort(dis.readShort());
txt=txt+"\nNo.OfEntries="+entries;
for(int i=0;i<=entries;i++)
{ tag=myShort( dis.readShort() );taginfo=tagInfo(tag);
type=myShort( dis.readShort() );count=myInt( dis.readInt() );value=myInt( dis.readInt() );
if(type==3)strVal="Value="; else strVal="Offset=";
if( strVal.equals("Offset=") )
{
if( taginfo.equals("StripOffsets") ){stripAt=value;stripCount=count;}
if( taginfo.equals("StripBytes") ){stripBytesAt=value;}
}
if( taginfo.equals("width") ){cols=value;}
if( taginfo.equals("length") ){rows=value;}
txt=txt+"\ntag="+tag+" "+tagInfo(tag)+",type="+type+",count="+count+strVal+value;
}
dis.close();bis.close();fis.close();
}catch(Exception e) {txt=txt+"\nerror="+e.toString();}
txt=txt+"\nNo.OfStrips="+stripCount+",array of strip locations at: "+stripAt+" and array of bytesPerStrip at "+stripBytesAt ;
extractBMP();
}
public void extractBMP()
{try{ File f=new File(path);RandomAccessFile raf=new RandomAccessFile(f,"r");
raf.seek(stripAt);stripOffs=new long[(int)stripCount];
txt=txt+"\nArray Of Image Offsets=";
for(int i=0;i<stripCount;i++){stripOffs[i]=myInt( raf.readInt() ); txt=txt+","+stripOffs[i]; }
raf.seek(stripBytesAt); stripBytes=new long[(int)stripCount];
txt=txt+"\nArray Of Strip Bytes =";
for(int i=0;i<stripCount;i++){stripBytes[i]=myInt(raf.readInt()); txt=txt+","+stripBytes[i];bytesCount+=stripBytes[i];}
txt=txt+stripBytes;
bBuff =ByteBuffer.allocate((int)(rows*cols*3));
for(int i=0;i<stripCount;i++)
{
bytes =new byte[(int)stripBytes[i]];
raf.seek(stripOffs[i]);
raf.read(bytes);
bBuff.put(lzwUncompress(bytes));
bytes=null;
}
txt=txt+"\nBuffered Image Bytes Size="+bBuff.position();
bBuff.rewind();
bmpBytes=new byte[bBuff.remaining()];
bmpBytes=bBuff.array();
txt=txt+"\nCount of bmpBytes="+bmpBytes.length;
bmp=BitmapFactory.decodeByteArray(bmpBytes,0,bmpBytes.length);
SystemClock.sleep(5000);
txt=txt+"Bitmap Object, bmp="+bmp;
if(bmp!=null){iv.setImageBitmap(bmp);sv.addView(iv);}
raf.close();
}catch(Exception e){txt=txt+"\nerror="+e.toString();}
}
public void lzw()
{
//String[] table=new String[4096];
byte b;char ch;String s;String pre="";short sh;
//List strTable=Arrays.asList(table);
//for(int i=0;i<255;i++)table[i]=Character.toString((char)i);
for(int i=0;i<100;i++)
{
b=bytes[i];
if(b<0)sh=(short)(128+b);
else sh=(short)b;
//ch=(char)b;
s=String.valueOf(sh);
//s=s+pre;
//if(strTable.contains(s)){pre=s;}
//else{ }
txt=txt+"Byte No."+i+"="+s+" ";
}
}
public void onCreate(Bundle bnd)
{
super.onCreate(bnd);
extractTif();
//sv=new ScrollView(this);
//tv=new TextView(this);
//iv=new ImageView(this);
//tv.setTextSize(7);
//sv.addView(tv);
//sv.addView(iv);
//tv.setText(txt);
//setContentView(sv);
Point res=new Point(); getWindowManager().getDefaultDisplay().getSize(res);
DotsView myView = new DotsView(this,res.x,res.y);
setContentView(myView);
}

Related

Rotate a BMP image in java without libraries java

This code works, it reads a file in byte type and after assigning the image it creates a copy in the directory where the other part is located with a different name, I must do the same, create a new file, I just have to make it rotate on the X and Y axes as the final 180 degree image without creating a library to do the job.
Can you help me with the code or madnar information
Thank you!
public class BMPRotations {
public static void main(String[] args) throws IOException {
int contador=0;
int datos_entrada[] = new int[921655];
try {
FileInputStream archivo_lectura = new FileInputStream("Ruta__picture.bmp");
boolean final_ar = false;
while(!final_ar) {
int byte_entrada = archivo_lectura.read();
if(byte_entrada!=-1)
datos_entrada[contador]=byte_entrada;
else
final_ar=true;
//Muestra todos los bytes
//System.out.println(datos_entrada[contador]);
contador++;
}
archivo_lectura.close();
}catch(IOException e) {
System.out.print("Error");
}
System.out.print("Bystes de la imagen: " + contador);
crea_fichero(datos_entrada);
}
static void crea_fichero(int datos_nuevo_fichero[]) {
try {
FileOutputStream fichero_nuevo = new FileOutputStream("Ruta_picture.bmp");
for(int i=0; i<datos_nuevo_fichero.length;i++) {
fichero_nuevo.write(datos_nuevo_fichero[i]);
}
fichero_nuevo.close();
}catch(IOException e) {
System.out.println("Error ");
}
}
Here is a reference image.
640X480 in 24-bit format
https://i.stack.imgur.com/pz4A4.png
This isn't a full answer but I hope it points you in right direction for what looks like homework.
What you have implemented so far is simply copying a file with hard-coded size 921655, and does not deal with an image - just any file. You could replace the entire program with:
File input = new File("Ruta__picture.bmp");
File output = new File("Ruta_picture.bmp");
Files.copy(input.toPath(), output.toPath(), StandardCopyOption.REPLACE_EXISTING);
To deal with images, look at javax.imageio.ImageIO class. This shows how to load any supported JDK image type and write it back:
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
BufferedImage image = ImageIO.read(input);
// TODO: TRANSFORM "image" here
BufferedImage modified = image;
ImageIO.write(modified , "bmp", output);
Note that ImageIO.write supports other types such as "jpg".

Why does the same image (edit: PNG) produce two slightly different byte arrays in Java?

I am writing a simple application and one of the features is the ability to serialize an object to an image so that the user can share a photo of their project with the build instructions embedded within it. Currently the other user can drag-and-drop the image into a JavaFX ListView and a listener will execute some code to decode and parse the embedded JSON.
This works fine if the image is stored locally, but I also want to allow users to drag and drop images from a browser without having to save them locally first. To test this I linked a working image in a basic HTML file so I could render it in Chrome.
Originally I was using the path to the file taken from the Dragboard, but when the image is coming from a browser I need (I think) to be able to accept the image directly, hence the overloaded decode() method below.
The problem I am having is that I am ending up with two slightly different byte arrays depending on whether the image comes from a browser or from somewhere in my main local storage. I don't know enough about images within Java (or in general) to understand why this is and haven't been able to find answers elsewhere. The browser sourced drag-drop produces a different enough byte array that I can't decode the message 100% properly, and therefore cannot deserialize it to an object. However, if I right click and save the browser based image, it loads correctly.
I have included a reproducible snippet and a test image below. My JDK is the most recent version of Amazon Corretto 8.
Reproducible snippet:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.image.Image;
import javafx.scene.image.PixelFormat;
import javafx.scene.image.PixelReader;
import javafx.scene.image.WritablePixelFormat;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.List;
public class MainApp extends Application {
private static final int OFFSET = 40;
private ListView<String> listView;
private GridPane gridPane;
#Override
public void start(Stage primaryStage) throws Exception {
listView = new ListView<>();
gridPane = new GridPane();
gridPane.addRow(0, listView);
setDragDropAction();
Scene scene = new Scene(gridPane, 250, 250);
primaryStage.setScene(scene);
primaryStage.sizeToScene();
primaryStage.show();
}
// the same drag-drop logic I am using in my project
private void setDragDropAction() {
gridPane.setOnDragOver(event -> {
if (event.getDragboard().hasFiles() || event.getDragboard().hasImage()) {
event.acceptTransferModes(TransferMode.ANY);
}
event.consume();
});
gridPane.setOnDragDropped(event -> {
List<File> files = event.getDragboard().getFiles();
try {
if (!files.isEmpty()) {
String decoded = decode(files.get(0));
System.out.println(decoded);
} else if (event.getDragboard().hasImage()) {
Image image = event.getDragboard().getImage();
String decoded = decode(image);
System.out.println(decoded);
}
} catch (IOException e) {
e.printStackTrace();
}
});
}
// decode using the file path
public String decode(File file) throws IOException {
byte[] byteImage = imageToByteArray(file);
return getStringFromBytes(byteImage);
}
// this results in a different byte array and a failed decode
public String decode(Image image) {
int width = (int) image.getWidth();
int height = (int) image.getHeight();
PixelReader reader = image.getPixelReader();
byte[] byteImage = new byte[width * height * 4];
WritablePixelFormat<ByteBuffer> format = PixelFormat.getByteBgraInstance();
reader.getPixels(0, 0, width, height, format, byteImage, 0, width * 4);
return getStringFromBytes(byteImage);
}
private String getStringFromBytes(byte[] byteImage) {
int offset = OFFSET;
byte[] byteLength = new byte[4];
System.arraycopy(byteImage, 1, byteLength, 0, (offset / 8) - 1);
int length = byteArrayToInt(byteLength);
byte[] result = new byte[length];
for (int b = 0; b < length; ++b) {
for (int i = 0; i < 8; ++i, ++offset) {
result[b] = (byte) ((result[b] << 1) | (byteImage[offset] & 1));
}
}
return new String(result);
}
private int byteArrayToInt(byte[] b) {
return b[3] & 0xFF
| (b[2] & 0xFF) << 8
| (b[1] & 0xFF) << 16
| (b[0] & 0xFF) << 24;
}
private byte[] imageToByteArray(File file) throws IOException {
BufferedImage image;
URL path = file.toURI().toURL();
image = ImageIO.read(path);
return ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
}
}
Another attempt at the decode method using SwingFXUtils.fromFXImage() which is also not working:
// Create a buffered image with the same imageType (6) as the type returned from ImageIO.read() with a local drag-drop
public String decode(Image image) throws IOException {
int width = (int) image.getWidth();
int height = (int) image.getHeight();
BufferedImage buffImageFinal = new BufferedImage(width, height, 6); // imageType = 6
BufferedImage buffImageTemp = SwingFXUtils.fromFXImage(image, null);
Graphics2D g = buffImageFinal.createGraphics();
g.drawImage(buffImageTemp, 0, 0, width, height, null);
g.dispose();
return getStringFromBytes(((DataBufferByte) buffImageFinal.getRaster().getDataBuffer()).getData());
}
Images showing the changes in the data:
Corner of image. Dissimilar pixels are highlighted in red.
Example image for testing:
The decoded testing message should print out ("This is a test message for my minimal reproducible example". Dragging the image from the browser does not work, saving the image locally and drag-dropping it does.
Based on a comment from the OP, event.getDragboard.getImage() loads an image with premultiplied alpha, while ImageIO.read() does not. One simple way to circumvent that is to get the url from the event (regardless of how the image is drag and dropped) and use ImageIO.read().
gridPane.setOnDragDropped(event -> {
if (event.getDragboard().hasUrl()) {
try {
URL path = new URL(event.getDragboard().getUrl());
String decoded = decode(path);
System.out.println(decoded);
} catch (IOException e) {
e.printStackTrace();
}
}
});
With the following modifications
public String decode(URL url) throws IOException {
byte[] byteImage = imageToByteArray(url);
return getStringFromBytes(byteImage);
}
private byte[] imageToByteArray(URL url) throws IOException {
BufferedImage image = ImageIO.read(url);
return ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
}
Note that loading from an url supports only a limited set of formats, as seen here.

Open CV Face Recognition not accurate

In my app I'm trying to do face recognition on a specific image using Open CV, here first I'm training one image and then after training that image if I run face recognition on that image it successfully recognizes that trained face. However, when I turn to another picture of the same person recognition does not work. It just works on the trained image, so my question is how do I rectify it?
Update:
What i want to do is that user should select image of a person from storage and then after training that selected image i want to fetch all images from storage which matches face of my trained image
Here is my activity class:
public class MainActivity extends AppCompatActivity {
private Mat rgba,gray;
private CascadeClassifier classifier;
private MatOfRect faces;
private ArrayList<Mat> images;
private ArrayList<String> imagesLabels;
private Storage local;
ImageView mimage;
Button prev,next;
ArrayList<Integer> imgs;
private int label[] = new int[1];
private double predict[] = new double[1];
Integer pos = 0;
private String[] uniqueLabels;
FaceRecognizer recognize;
private boolean trainfaces() {
if(images.isEmpty())
return false;
List<Mat> imagesMatrix = new ArrayList<>();
for (int i = 0; i < images.size(); i++)
imagesMatrix.add(images.get(i));
Set<String> uniqueLabelsSet = new HashSet<>(imagesLabels); // Get all unique labels
uniqueLabels = uniqueLabelsSet.toArray(new String[uniqueLabelsSet.size()]); // Convert to String array, so we can read the values from the indices
int[] classesNumbers = new int[uniqueLabels.length];
for (int i = 0; i < classesNumbers.length; i++)
classesNumbers[i] = i + 1; // Create incrementing list for each unique label starting at 1
int[] classes = new int[imagesLabels.size()];
for (int i = 0; i < imagesLabels.size(); i++) {
String label = imagesLabels.get(i);
for (int j = 0; j < uniqueLabels.length; j++) {
if (label.equals(uniqueLabels[j])) {
classes[i] = classesNumbers[j]; // Insert corresponding number
break;
}
}
}
Mat vectorClasses = new Mat(classes.length, 1, CvType.CV_32SC1); // CV_32S == int
vectorClasses.put(0, 0, classes); // Copy int array into a vector
recognize = LBPHFaceRecognizer.create(3,8,8,8,200);
recognize.train(imagesMatrix, vectorClasses);
if(SaveImage())
return true;
return false;
}
public void cropedImages(Mat mat) {
Rect rect_Crop=null;
for(Rect face: faces.toArray()) {
rect_Crop = new Rect(face.x, face.y, face.width, face.height);
}
Mat croped = new Mat(mat, rect_Crop);
images.add(croped);
}
public boolean SaveImage() {
File path = new File(Environment.getExternalStorageDirectory(), "TrainedData");
path.mkdirs();
String filename = "lbph_trained_data.xml";
File file = new File(path, filename);
recognize.save(file.toString());
if(file.exists())
return true;
return false;
}
private BaseLoaderCallback callbackLoader = new BaseLoaderCallback(this) {
#Override
public void onManagerConnected(int status) {
switch(status) {
case BaseLoaderCallback.SUCCESS:
faces = new MatOfRect();
//reset
images = new ArrayList<Mat>();
imagesLabels = new ArrayList<String>();
local.putListMat("images", images);
local.putListString("imagesLabels", imagesLabels);
images = local.getListMat("images");
imagesLabels = local.getListString("imagesLabels");
break;
default:
super.onManagerConnected(status);
break;
}
}
};
#Override
protected void onResume() {
super.onResume();
if(OpenCVLoader.initDebug()) {
Log.i("hmm", "System Library Loaded Successfully");
callbackLoader.onManagerConnected(BaseLoaderCallback.SUCCESS);
} else {
Log.i("hmm", "Unable To Load System Library");
OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION, this, callbackLoader);
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
prev = findViewById(R.id.btprev);
next = findViewById(R.id.btnext);
mimage = findViewById(R.id.mimage);
local = new Storage(this);
imgs = new ArrayList();
imgs.add(R.drawable.jonc);
imgs.add(R.drawable.jonc2);
imgs.add(R.drawable.randy1);
imgs.add(R.drawable.randy2);
imgs.add(R.drawable.imgone);
imgs.add(R.drawable.imagetwo);
mimage.setBackgroundResource(imgs.get(pos));
prev.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if(pos!=0){
pos--;
mimage.setBackgroundResource(imgs.get(pos));
}
}
});
next.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if(pos<5){
pos++;
mimage.setBackgroundResource(imgs.get(pos));
}
}
});
Button train = (Button)findViewById(R.id.btn_train);
train.setOnClickListener(new View.OnClickListener() {
#RequiresApi(api = Build.VERSION_CODES.KITKAT)
#Override
public void onClick(View view) {
rgba = new Mat();
gray = new Mat();
Mat mGrayTmp = new Mat();
Mat mRgbaTmp = new Mat();
classifier = FileUtils.loadXMLS(MainActivity.this);
Bitmap icon = BitmapFactory.decodeResource(getResources(),
imgs.get(pos));
Bitmap bmp32 = icon.copy(Bitmap.Config.ARGB_8888, true);
Utils.bitmapToMat(bmp32, mGrayTmp);
Utils.bitmapToMat(bmp32, mRgbaTmp);
Imgproc.cvtColor(mGrayTmp, mGrayTmp, Imgproc.COLOR_BGR2GRAY);
Imgproc.cvtColor(mRgbaTmp, mRgbaTmp, Imgproc.COLOR_BGRA2RGBA);
/*Core.transpose(mGrayTmp, mGrayTmp); // Rotate image
Core.flip(mGrayTmp, mGrayTmp, -1); // Flip along both*/
gray = mGrayTmp;
rgba = mRgbaTmp;
Imgproc.resize(gray, gray, new Size(200,200.0f/ ((float)gray.width()/ (float)gray.height())));
if(gray.total() == 0)
Toast.makeText(getApplicationContext(), "Can't Detect Faces", Toast.LENGTH_SHORT).show();
classifier.detectMultiScale(gray,faces,1.1,3,0|CASCADE_SCALE_IMAGE, new Size(30,30));
if(!faces.empty()) {
if(faces.toArray().length > 1)
Toast.makeText(getApplicationContext(), "Mutliple Faces Are not allowed", Toast.LENGTH_SHORT).show();
else {
if(gray.total() == 0) {
Log.i("hmm", "Empty gray image");
return;
}
cropedImages(gray);
imagesLabels.add("Baby");
Toast.makeText(getApplicationContext(), "Picture Set As Baby", Toast.LENGTH_LONG).show();
if (images != null && imagesLabels != null) {
local.putListMat("images", images);
local.putListString("imagesLabels", imagesLabels);
Log.i("hmm", "Images have been saved");
if(trainfaces()) {
images.clear();
imagesLabels.clear();
}
}
}
}else {
/* Bitmap bmp = null;
Mat tmp = new Mat(250, 250, CvType.CV_8U, new Scalar(4));
try {
//Imgproc.cvtColor(seedsImage, tmp, Imgproc.COLOR_RGB2BGRA);
Imgproc.cvtColor(gray, tmp, Imgproc.COLOR_GRAY2RGBA, 4);
bmp = Bitmap.createBitmap(tmp.cols(), tmp.rows(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(tmp, bmp);
} catch (CvException e) {
Log.d("Exception", e.getMessage());
}*/
/* mimage.setImageBitmap(bmp);*/
Toast.makeText(getApplicationContext(), "Unknown Face", Toast.LENGTH_SHORT).show();
}
}
});
Button recognize = (Button)findViewById(R.id.btn_recognize);
recognize.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if(loadData())
Log.i("hmm", "Trained data loaded successfully");
rgba = new Mat();
gray = new Mat();
faces = new MatOfRect();
Mat mGrayTmp = new Mat();
Mat mRgbaTmp = new Mat();
classifier = FileUtils.loadXMLS(MainActivity.this);
Bitmap icon = BitmapFactory.decodeResource(getResources(),
imgs.get(pos));
Bitmap bmp32 = icon.copy(Bitmap.Config.ARGB_8888, true);
Utils.bitmapToMat(bmp32, mGrayTmp);
Utils.bitmapToMat(bmp32, mRgbaTmp);
Imgproc.cvtColor(mGrayTmp, mGrayTmp, Imgproc.COLOR_BGR2GRAY);
Imgproc.cvtColor(mRgbaTmp, mRgbaTmp, Imgproc.COLOR_BGRA2RGBA);
/*Core.transpose(mGrayTmp, mGrayTmp); // Rotate image
Core.flip(mGrayTmp, mGrayTmp, -1); // Flip along both*/
gray = mGrayTmp;
rgba = mRgbaTmp;
Imgproc.resize(gray, gray, new Size(200,200.0f/ ((float)gray.width()/ (float)gray.height())));
if(gray.total() == 0)
Toast.makeText(getApplicationContext(), "Can't Detect Faces", Toast.LENGTH_SHORT).show();
classifier.detectMultiScale(gray,faces,1.1,3,0|CASCADE_SCALE_IMAGE, new Size(30,30));
if(!faces.empty()) {
if(faces.toArray().length > 1)
Toast.makeText(getApplicationContext(), "Mutliple Faces Are not allowed", Toast.LENGTH_SHORT).show();
else {
if(gray.total() == 0) {
Log.i("hmm", "Empty gray image");
return;
}
recognizeImage(gray);
}
}else {
Toast.makeText(getApplicationContext(), "Unknown Face", Toast.LENGTH_SHORT).show();
}
}
});
}
private void recognizeImage(Mat mat) {
Rect rect_Crop=null;
for(Rect face: faces.toArray()) {
rect_Crop = new Rect(face.x, face.y, face.width, face.height);
}
Mat croped = new Mat(mat, rect_Crop);
recognize.predict(croped, label, predict);
int indice = (int)predict[0];
Log.i("hmmcheck:",String.valueOf(label[0])+" : "+String.valueOf(indice));
if(label[0] != -1 && indice < 125)
Toast.makeText(getApplicationContext(), "Welcome "+uniqueLabels[label[0]-1]+"", Toast.LENGTH_SHORT).show();
else
Toast.makeText(getApplicationContext(), "You're not the right person", Toast.LENGTH_SHORT).show();
}
private boolean loadData() {
String filename = FileUtils.loadTrained();
if(filename.isEmpty())
return false;
else
{
recognize.read(filename);
return true;
}
}
}
My File Utils Class:
public class FileUtils {
private static String TAG = FileUtils.class.getSimpleName();
private static boolean loadFile(Context context, String cascadeName) {
InputStream inp = null;
OutputStream out = null;
boolean completed = false;
try {
inp = context.getResources().getAssets().open(cascadeName);
File outFile = new File(context.getCacheDir(), cascadeName);
out = new FileOutputStream(outFile);
byte[] buffer = new byte[4096];
int bytesread;
while((bytesread = inp.read(buffer)) != -1) {
out.write(buffer, 0, bytesread);
}
completed = true;
inp.close();
out.flush();
out.close();
} catch (IOException e) {
Log.i(TAG, "Unable to load cascade file" + e);
}
return completed;
}
public static CascadeClassifier loadXMLS(Activity activity) {
InputStream is = activity.getResources().openRawResource(R.raw.lbpcascade_frontalface);
File cascadeDir = activity.getDir("cascade", Context.MODE_PRIVATE);
File mCascadeFile = new File(cascadeDir, "lbpcascade_frontalface_improved.xml");
FileOutputStream os = null;
try {
os = new FileOutputStream(mCascadeFile);
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
is.close();
os.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return new CascadeClassifier(mCascadeFile.getAbsolutePath());
}
public static String loadTrained() {
File file = new File(Environment.getExternalStorageDirectory(), "TrainedData/lbph_trained_data.xml");
return file.toString();
}
}
These are the images i'm trying to compare here face of person is same still in recognition it's not matching!
Update
According to the new edit in the question, you need a way to identify new people on the fly whose photos might not have been available during the training phase of the model. These tasks are called few shot learning. This is similar to the requirements of the intelligence/police agencies to find their targets using CCTV camera footage. As usually there are not enough images of a specific target, during training, they use models such as FaceNet. I really suggest reading the paper, however, I explain a few of its highlights here:
Generally, the last layer of a classifier is a n*1 vector with n-1 of
the elements almost equal to zero, and one close to 1. The element close to 1, determines the prediction of the classifier about the input's label.
The authors figured out that if they train a
classifier network with a specific loss function on a huge dataset of faces, you can use the semi-final layer output as a representation of any face, irrespective of it being in the training set or not, the authors call this vector Face Embedding.
The previous result means that with a very well trained FaceNet model, you can summarise any face into a vector. The very interesting attribute of this approach is that the vectors of a specific person's face in different angles/positions/states have are proximate in the euclidian space (this property is enforced by the loss function that the authors chose).
In summary, you have a model that gets faces as input and returns vectors. The vectors close to each other are very likely to belong to the same person (For checking that you can use KNN or just simple euclidian distance).
One implementation of FaceNet can be found here. I suggest you try to run it on your computer to get to know what you are actually dealing with. After that, it might be best to do the following:
Transform the FaceNet model mentioned in the repository to its
tflite version (this blogpost might help)
For each photo submitted by the user, use Face API to extract the face(s)
Use the minified model in your app to get the face embeddings of the extracted face.
Process all the images in the gallery of the user, getting the vectors for the faces in the photos.
Then compare each vector found in step4 with each vector found in step3 to get the matches.
Original Answer
You came across one of the most prevalent challenges of machine learning: Overfitting. Face detection and recognition is a huge area of research on its own and almost all the reasonably accurate models are using some kind of deep learning. Note that even detecting a face accurately is not as easy as it seems, however, as you are doing it on android, you can use Face API for this task. (Other more advanced techniques such as MTCNN are too slow/difficult to deploy on a handset). It has been shown that just feeding the model with a face photo with a lot of background noise or multiple people inside does not work. So, you really cannot skip this step.
After getting a nice trimmed face of the candidate targets from the background, you need to overcome the challenge of recognising the detected faces. Again, all the competent models to the best of my knowledge, are using some sort of deep learning/convolutional neural networks. Using them on a mobile phone is a challenge, but thanks to Tensorflow Lite you can minify them and run them within your app. A project about face recognition on android phones that I had worked on is here that you can check.
Keep in mind that any good model should be trained on numerous instances of labelled data, however there are a plethora of models already trained on large datasets of faces or other image recognition tasks, to tweak them and use their existing knowledge, we can employ transfer learning, for a quick start on object detection and transfer learning that is closely related to your case check this blog post.
Overall, you have to get numerous instances of the faces that you want to detect plus numerous face pics of people that you don't care about, then you need to train a model based on the above-mentioned resources, and then you need to use TensorFlow lite to decrease its size and embed it within your app. For each frame then, you call android Face API and feed (the probably detected face) into the model and identify the person.
Depending on your level of tolerance for delay and the number of training set size and number of targets, you can get various results, however, %90+ accuracy is easily achievable if you have only a few target people.
If I understand correctly, you're training the classifier with a single image. In that case, this one specific image is everything the classifier will be able to ever recognise. You would need a noticeably bigger training set of pictures showing the same person, something like 5 or 10 different images at the very least.
1) Change threshold value while initializing LBPHrecognizer to -> LBPHFaceRecognizer(1, 8, 8, 8, 100)
2) train each face with atleast 2-3 pictures since these recognizers mainly work on comparison
3) Set accuracy threshold while recognizing. Do something like this:
//predicting result
// LoadData is a static class that contains trained recognizer
// _result is the gray frame image captured by the camera
LBPHFaceRecognizer.PredictionResult ER = LoadData.recog.Predict(_result);
int temp_result = ER.Label;
imageBox1.SizeMode = PictureBoxSizeMode.StretchImage;
imageBox1.Image = _result.Mat;
//Displaying predicted result on screen
// LBPH returns -1 if face is recognized
if ((temp_result != -1) && (ER.Distance < 55)){
//I get best accuracy at 55, you should try different values to determine best results
// Do something with detected image
}

Checking if an image is blank in Java

I'm currently working on a dynamic Animation Loader for Characters in a game and for that I'm in need of detecting if the current frame is completely blank in order to stop loading more sprites.
This is what I'm currently using to find out if the current image is blank:
public static boolean isBlankImage(BufferedImage b) {
byte[] pixels1 = getPixels(b);
byte[] pixels2 = getPixels(getBlankImage(b.getWidth(), b.getHeight()));
return Arrays.equals(pixels1, pixels2);
}
private static BufferedImage getBlankImage(int width, int height) {
return new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
}
private static byte[] getPixels(BufferedImage b) {
byte[] pixels = ((DataBufferByte) b.getRaster().getDataBuffer()).getData();
return pixels;
}
However, as soon as I run it, I get this annoying error:
Exception in thread "Thread-0" java.lang.ClassCastException:
java.awt.image.DataBufferInt cannot be cast to java.awt.image.DataBufferByte
I've tried switching the casting type but all I get in return is:
Exception in thread "Thread-0" java.lang.ClassCastException:
java.awt.image.DataBufferByte cannot be cast to java.awt.image.DataBufferInt
I've searched all over the place for an answer to no avail, so here's my question: Is there a better functional way to check if an image is fully transparent ?
Any help will be greatly appreciated.
The method must be return a byte array, you are trying convert a DataBuffer in a DataBufferByte.
I have changed the name getPixels to getByteArray. Since it is not the same.
Try this:
private static byte[] getByteArray(BufferedImage img) {
byte[] imageInByte = null;
String format = "jpeg"; //Needs a image TYPE
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(img, format, baos);
baos.flush();
imageInByte = baos.toByteArray();
baos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return imageInByte;
}
The blank image's DataBuffer is indeed an instance of DataBufferInt while your original image has a buffer of type DataBufferByte.
You should create your empty image based on the type of the image to compare with:
private static BufferedImage getBlankImage(int width, int height, int type) {
return new BufferedImage(width, height, type);
}
and call it this way:
getBlankImage(b.getWidth(), b.getHeight(), b.getType())
Notice that in terms of performance and memory usage it may be better to create the empty image only once (or once for each image type which may occur).
Probably the image type and size are constant and written wherever the actual images are created.
Now you have a proper empty image and may test its equality as in Is there a simple way to compare BufferedImage instances?:
public static boolean compareImages(BufferedImage imgA, BufferedImage imgB) {
int width = imgA.getWidth();
int height = imgA.getHeight();
// Loop over every pixel.
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
// Compare the pixels for equality.
if (imgA.getRGB(x, y) != imgB.getRGB(x, y)) {
return false;
}
}
}
return true;
}

Is there a equivalent of Android's BitmapFactory.Options isDecodeBounds for TIFF in Java/JAI?

I am trying to improve the performance of our system (a Java app running in Tomcat) and now the bottleneck is in one operation, we need to read and return dimension of tiff images, so we use JAI's ImageDecoder and use
ImageDecoder decoder = ImageCodec.createImageDecoder("TIFF", input, param);
RenderedImage r = decoder.decodeAsRenderedImage();
int width = r.getWidth();
int height = r.getHeight();
From sampling data, a lot of time is spent in createImageDecoder. My assumption (without going to source code of ImageCodec) is it's probably trying to decode the input stream.
Coming from Android land, I am hoping there is a similar solution to just decode bounds like setting BitmapFactory.Options.inJustDecodeBounds = true but so far no luck in finding any other library like that. (I am aware that tiff support on Android is missing in AOSP, but that's topic for another day.)
Anyone know a library that does this? Or is there a way to achieve similar goal using JAI/ImageIO?
It looks like the tiff file format groups this information together in a header, so you could just read the data from the file yourself:
private static Dimension getTiffDimensions(InputStream tiffFile) throws IOException {
ReadableByteChannel channel = Channels.newChannel(tiffFile);
ByteBuffer buffer = ByteBuffer.allocate(12);
forceRead(channel, buffer, 8);
byte endian = buffer.get();
if(endian != buffer.get() || (endian != 'I' && endian != 'M')) {
throw new IOException("Not a tiff file.");
}
buffer.order(endian == 'I' ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
if(buffer.getShort() != 42) {
throw new IOException("Not a tiff file.");
}
// Jump to the first image directory. Note that we've already read 8 bytes.
tiffFile.skip(buffer.getInt() - 8);
int width = -1;
int height = -1;
// The first two bytes of the IFD are the number of fields.
forceRead(channel, buffer, 2);
for(int fieldCount = buffer.getShort(); fieldCount > 0 && (width < 0 || height < 0); --fieldCount) {
forceRead(channel, buffer, 12);
switch(buffer.getShort()) {
case 0x0100: // Image width
width = readField(buffer);
break;
case 0x0101: // Image "length", i.e. height
height = readField(buffer);
break;
}
}
return new Dimension(width, height);
}
private static void forceRead(ReadableByteChannel channel, ByteBuffer buffer, int n) throws IOException {
buffer.position(0);
buffer.limit(n);
while(buffer.hasRemaining()) {
channel.read(buffer);
}
buffer.flip();
}
private static int readField(ByteBuffer buffer) {
int type = buffer.getShort();
int count = buffer.getInt();
if(count != 1) {
throw new RuntimeException("Expected a count of 1 for the given field.");
}
switch(type) {
case 3: // word
return buffer.getShort();
case 4: // int
return buffer.getInt();
default: // char (not used here)
return buffer.get() & 0xFF;
}
}
I've tested this with a few different tiff files (run length encoded black & white, color with transparency) and it seems to work fine. Depending on the layout of your tiff file it may have to read a lot of the stream before it finds the size (one of the files I tested, saved by Apple's Preview, had this data at the end of the file).

Categories