Java char recognition with OpenCV - java

i am trying to build an application that with a camera can recognize some numbers and letters from a board in front of the camera.
At moment i can detect faces, counters but i want to use ROI and Tess4j to recognize this live video.
Do you know any example of something like this?
My idea is that i have to analyze frame by frame and when i detect a char like '*' i make the full verification of the values
public class Demo {
public static void main(String[] args) throws Exception {
String classifierName = null;
if (args.length > 0) {
classifierName = args[0];
} else {
URL url = new URL("https://raw.github.com/Itseez/opencv/2.4.0/data/haarcascades/haarcascade_frontalface_alt.xml");
File file = Loader.extractResource(url, null, "classifier", ".xml");
file.deleteOnExit();
classifierName = file.getAbsolutePath();
}
// Preload the opencv_objdetect module to work around a known bug.
Loader.load(opencv_objdetect.class);
// We can "cast" Pointer objects by instantiating a new object of the desired class.
CvHaarClassifierCascade classifier = new CvHaarClassifierCascade(cvLoad(classifierName));
if (classifier.isNull()) {
System.err.println("Error loading classifier file \"" + classifierName + "\".");
System.exit(1);
}
// The available FrameGrabber classes include OpenCVFrameGrabber (opencv_videoio),
// DC1394FrameGrabber, FlyCaptureFrameGrabber, OpenKinectFrameGrabber, OpenKinect2FrameGrabber,
// RealSenseFrameGrabber, PS3EyeFrameGrabber, VideoInputFrameGrabber, and FFmpegFrameGrabber.
FrameGrabber grabber = FrameGrabber.createDefault(0);
grabber.start();
// CanvasFrame, FrameGrabber, and FrameRecorder use Frame objects to communicate image data.
// We need a FrameConverter to interface with other APIs (Android, Java 2D, or OpenCV).
OpenCVFrameConverter.ToIplImage converter = new OpenCVFrameConverter.ToIplImage();
// FAQ about IplImage and Mat objects from OpenCV:
// - For custom raw processing of data, createBuffer() returns an NIO direct
// buffer wrapped around the memory pointed by imageData, and under Android we can
// also use that Buffer with Bitmap.copyPixelsFromBuffer() and copyPixelsToBuffer().
// - To get a BufferedImage from an IplImage, or vice versa, we can chain calls to
// Java2DFrameConverter and OpenCVFrameConverter, one after the other.
// - Java2DFrameConverter also has static copy() methods that we can use to transfer
// data more directly between BufferedImage and IplImage or Mat via Frame objects.
IplImage grabbedImage = converter.convert(grabber.grab());
int width = grabbedImage.width();
int height = grabbedImage.height();
IplImage grayImage = IplImage.create(width, height, IPL_DEPTH_8U, 1);
IplImage rotatedImage = grabbedImage.clone();
// Objects allocated with a create*() or clone() factory method are automatically released
// by the garbage collector, but may still be explicitly released by calling release().
// You shall NOT call cvReleaseImage(), cvReleaseMemStorage(), etc. on objects allocated this way.
CvMemStorage storage = CvMemStorage.create();
// The OpenCVFrameRecorder class simply uses the CvVideoWriter of opencv_videoio,
// but FFmpegFrameRecorder also exists as a more versatile alternative.
FrameRecorder recorder = FrameRecorder.createDefault("output.avi", width, height);
recorder.start();
// CanvasFrame is a JFrame containing a Canvas component, which is hardware accelerated.
// It can also switch into full-screen mode when called with a screenNumber.
// We should also specify the relative monitor/camera response for proper gamma correction.
CanvasFrame frame = new CanvasFrame("Some Title", CanvasFrame.getDefaultGamma()/grabber.getGamma());
// Let's create some random 3D rotation...
CvMat randomR = CvMat.create(3, 3), randomAxis = CvMat.create(3, 1);
// We can easily and efficiently access the elements of matrices and images
// through an Indexer object with the set of get() and put() methods.
DoubleIndexer Ridx = randomR.createIndexer(), axisIdx = randomAxis.createIndexer();
axisIdx.put(0, (Math.random()-0.5)/4, (Math.random()-0.5)/4, (Math.random()-0.5)/4);
cvRodrigues2(randomAxis, randomR, null);
double f = (width + height)/2.0; Ridx.put(0, 2, Ridx.get(0, 2)*f);
Ridx.put(1, 2, Ridx.get(1, 2)*f);
Ridx.put(2, 0, Ridx.get(2, 0)/f); Ridx.put(2, 1, Ridx.get(2, 1)/f);
System.out.println(Ridx);
// We can allocate native arrays using constructors taking an integer as argument.
CvPoint hatPoints = new CvPoint(3);
while (frame.isVisible() && (grabbedImage = converter.convert(grabber.grab())) != null) {
cvClearMemStorage(storage);
// Let's try to detect some faces! but we need a grayscale image...
cvCvtColor(grabbedImage, grayImage, CV_BGR2GRAY);
CvSeq faces = cvHaarDetectObjects(grayImage, classifier, storage,
1.1, 3, CV_HAAR_FIND_BIGGEST_OBJECT | CV_HAAR_DO_ROUGH_SEARCH);
int total = faces.total();
for (int i = 0; i < total; i++) {
CvRect r = new CvRect(cvGetSeqElem(faces, i));
int x = r.x(), y = r.y(), w = r.width(), h = r.height();
cvRectangle(grabbedImage, cvPoint(x, y), cvPoint(x+w, y+h), CvScalar.RED, 1, CV_AA, 0);
// To access or pass as argument the elements of a native array, call position() before.
hatPoints.position(0).x(x-w/10) .y(y-h/10);
hatPoints.position(1).x(x+w*11/10).y(y-h/10);
hatPoints.position(2).x(x+w/2) .y(y-h/2);
cvFillConvexPoly(grabbedImage, hatPoints.position(0), 3, CvScalar.GREEN, CV_AA, 0);
}
// Let's find some contours! but first some thresholding...
cvThreshold(grayImage, grayImage, 64, 255, CV_THRESH_BINARY);
// To check if an output argument is null we may call either isNull() or equals(null).
CvSeq contour = new CvSeq(null);
cvFindContours(grayImage, storage, contour, Loader.sizeof(CvContour.class),
CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);
while (contour != null && !contour.isNull()) {
if (contour.elem_size() > 0) {
CvSeq points = cvApproxPoly(contour, Loader.sizeof(CvContour.class),
storage, CV_POLY_APPROX_DP, cvContourPerimeter(contour)*0.02, 0);
cvDrawContours(grabbedImage, points, CvScalar.BLUE, CvScalar.BLUE, -1, 1, CV_AA);
}
contour = contour.h_next();
}
//TESS4J - se ahou os contornos, analisar conteúdo
if(contour != null && !contour.isNull()) {
IplImageToBufferedImage(grayImage);
ITesseract instance = new Tesseract(); // JNA Interface Mapping
instance.setTessVariable("tessedit_char_whitelist", "0123456789");
try {
String result = instance.doOCR(IplImageToBufferedImage(grayImage));
System.out.println(result);
} catch (TesseractException e) {
System.err.println(e.getMessage());
}
}
cvWarpPerspective(grabbedImage, rotatedImage, randomR);
Frame rotatedFrame = converter.convert(rotatedImage);
frame.showImage(rotatedFrame);
recorder.record(rotatedFrame);
}
frame.dispose();
recorder.stop();
grabber.stop();
}
public static BufferedImage IplImageToBufferedImage(IplImage src) {
OpenCVFrameConverter.ToIplImage grabberConverter = new OpenCVFrameConverter.ToIplImage();
Java2DFrameConverter paintConverter = new Java2DFrameConverter();
Frame frame = grabberConverter.convert(src);
return paintConverter.getBufferedImage(frame,1);
}
}

Related

OpenCV on Android: net.forward yields "215 Assertion failed"

Following this tutorial from openCV, and it should be straight forward. However, it crashes with an assertion fail on the net.forward, that I cannot resolve/find anywhere else.
Thought this problem seemed similar and tried to go through the fix/problem finding. However, restarting the discussion and trials showed it is likely not the same. I used initially 3.4.3, which did not support the same Mat type somehow. Updated to 3.4.7 now, and can confirm the blob size is okay (generated from image). Tried also various other prototxt and caffemodels, but doubt by now that the problem lies there (works if the files are okay, otherwise the net loading fails). The key code should be this:
// Load a network.
public void onCameraViewStarted(int width, int height) {
String proto = getPath("deploy.prototxt", this);
String weights = getPath("MobileNetSSD_deploy.caffemodel", this);
net = Dnn.readNetFromCaffe(proto, weights);
Log.i(TAG, "Network loaded successfully");
}
public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
// Get a new frame
Mat frame = inputFrame.rgba();
Imgproc.cvtColor(frame, frame, Imgproc.COLOR_RGBA2RGB);
// Forward image through network.
Mat blob = Dnn.blobFromImage(frame, 0.007843,
new Size(300, 300),
new Scalar(127.5, 127.5, 127.5));
net.setInput(blob);
Mat detections = net.forward(); //***215 ASSERTION FAILED occurs***
int cols = frame.cols();
int rows = frame.rows();
detections = detections.reshape(1, (int)detections.total() / 7);
for (int i = 0; i < detections.rows(); ++i) {
double confidence = detections.get(i, 2)[0];
if (confidence > 0.2) {
int classId = (int)detections.get(i, 1)[0];
int left = (int)(detections.get(i, 3)[0] * cols);
int top = (int)(detections.get(i, 4)[0] * rows);
int right = (int)(detections.get(i, 5)[0] * cols);
int bottom = (int)(detections.get(i, 6)[0] * rows);
// Draw rectangle around detected object.
Imgproc.rectangle(frame, new Point(left, top), new Point(right, bottom),
new Scalar(0, 255, 0));
String label = classNames[classId] + ": " + confidence;
int[] baseLine = new int[1];
Size labelSize = Imgproc.getTextSize(label, Core.FONT_HERSHEY_SIMPLEX, 0.5, 1, baseLine);
// Draw background for label.
Imgproc.rectangle(frame, new Point(left, top - labelSize.height),
new Point(left + labelSize.width, top + baseLine[0]),
new Scalar(255, 255, 255), Core.FILLED);
// Write class name and confidence.
Imgproc.putText(frame, label, new Point(left, top),
Core.FONT_HERSHEY_SIMPLEX, 0.5, new Scalar(0, 0, 0));
}
}
return frame;
}
public void onCameraViewStopped() {}
// Upload file to storage and return a path.
private static String getPath(String file, Context context) {
AssetManager assetManager = context.getAssets();
BufferedInputStream inputStream = null;
try {
// Read data from assets.
inputStream = new BufferedInputStream(assetManager.open(file));
byte[] data = new byte[inputStream.available()];
inputStream.read(data);
inputStream.close();
// Create copy file in storage.
File outFile = new File(context.getFilesDir(), file);
FileOutputStream os = new FileOutputStream(outFile);
os.write(data);
os.close();
// Return a path to file which may be read in common way.
return outFile.getAbsolutePath();
} catch (IOException ex) {
Log.i(TAG, "Failed to upload a file");
}
return "";
}
The full error message is
cv::Exception: OpenCV(3.4.7) /build/3_4_pack-android/opencv/modules/dnn/src/layers/batch_norm_layer.cpp:39: error: (-215:Assertion failed) blobs.size() >= 2 in function 'cv::dnn::BatchNormLayerImpl::BatchNormLayerImpl(const cv::dnn::experimental_dnn_34_v13::LayerParams&)'
I expect it to not crash. The frame should be okay (image loaded), the net is not empty, and the layers in the net seem fine too (checked since there are some differences using caffe in java). Any help is appreciated!
After some days of research in different directions, I found the problem: the frame format should be BGR, not RGB! That means
Imgproc.cvtColor(frame, frame, Imgproc.COLOR_RGBA2BGR);

OpenCV detecting drilled holes

I'm working on a project where I have to detect drilled holes on a surface. (the top two holes and there for orientation purposes only)
After detecting the holes the pattern will judge the placement of the holes and give results. I have created an overlay grid layout and placed it over the camera2api preview so the user can align the holes and scan (The real testing will not be of a picture from the LCD as shown in the screenshot)
Currently, I'm cropping the image based on the grid and resizing it to 1920x2560 to have a consistent frame for pattern judgement, which makes a single grid of roughly about 300px. I am unable to detect the blobs can someone suggest what sort of filtering I should choose for this work and if there is a better approach for doing this rather than using a grid layout as the placement of the holes in regard to the orientation holes matter for final results (both x and y axis)
Here is my code:
Mat srcMat = resizeAndCropMatToGrid(mats[0]);
if (srcMat == null) {
exception = new Exception("Cropping Failed");
errorMessage = "Unable to crop image based on grid";
return null;
}
matProgressTask = srcMat;
Mat processedMat = new Mat();
Imgproc.cvtColor(srcMat, processedMat, Imgproc.COLOR_BGR2GRAY);
Imgproc.GaussianBlur(processedMat, processedMat, new org.opencv.core.Size(5, 5), 5);
Imgproc.threshold(processedMat, processedMat, 115, 255, Imgproc.THRESH_BINARY);
matProgressTask = processedMat;
FeatureDetector featureDetector = FeatureDetector.create(FeatureDetector.SIMPLEBLOB);
featureDetector.read(Environment.getExternalStorageDirectory() + "/Android/blob.xml");
MatOfKeyPoint matOfKeyPoint = new MatOfKeyPoint();
featureDetector.detect(processedMat, matOfKeyPoint);
KeyPoint[] keyPointsArray = matOfKeyPoint.toArray();
Log.e("keypoints", "" + Arrays.toString(keyPointsArray));
if (keyPointsArray.length < 1) {
exception = new Exception("Blobs Missing");
errorMessage = "Error: Unable to filter blobs";
} else {
try {
MatOfKeyPoint matOfKeyPointFilteredBlobs = new MatOfKeyPoint(keyPointsArray);
Features2d.drawKeypoints(srcMat, matOfKeyPointFilteredBlobs, srcMat, new Scalar(255, 0, 0), Features2d.DRAW_OVER_OUTIMG);
} catch (Exception e) {
e.printStackTrace();
exception = e;
errorMessage = "Error: Unable to draw Blobs";
return null;
}
matProgressTask = srcMat;
onProgressUpdate();
patterData = pinpointBlobsToGetData(keyPointsArray);
if (patterData == null) {
exception = new Exception("Unable to establish pattern");
errorMessage = "Error: Key points array is null";
}
}
And here is the blobby file configuration that I'm using:
<?xml version="1.0"?>
<opencv_storage>
<format>3</format>
<thresholdStep>10.</thresholdStep>
<minThreshold>50.</minThreshold>
<maxThreshold>120.</maxThreshold>
<minRepeatability>2</minRepeatability>
<minDistBetweenBlobs>20.</minDistBetweenBlobs>
<filterByColor>1</filterByColor>
<blobColor>0</blobColor>
<filterByArea>1</filterByArea>
<minArea>2300.</minArea>
<maxArea>4500.</maxArea>
<filterByCircularity>1</filterByCircularity>
<minCircularity>0.2</minCircularity>
<maxCircularity>1.0</maxCircularity>
<filterByInertia>1</filterByInertia>
<minInertiaRatio>0.2</minInertiaRatio>
<maxInertiaRatio>1.0</maxInertiaRatio>
<filterByConvexity>1</filterByConvexity>
<minConvexity>0.2</minConvexity>
<maxConvexity>1.0</maxConvexity>
</opencv_storage>
I am using Python.
For the second image you provided I successfully detected the holes...
...using this code...
import cv2
import numpy as np
img = cv2.imread("C:\\Users\\Link\\Desktop\\2.jpg")
# cv2.imshow("original", img)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# cv2.imshow("gray", gray)
blur = cv2.medianBlur(gray, 31)
# cv2.imshow("blur", blur)
ret, thresh = cv2.threshold(blur, 127, 255, cv2.THRESH_OTSU)
# cv2.imshow("thresh", thresh)
canny = cv2.Canny(thresh, 75, 200)
# cv2.imshow('canny', canny)
im2, contours, hierarchy = cv2.findContours(canny, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
contour_list = []
for contour in contours:
approx = cv2.approxPolyDP(contour, 0.01 * cv2.arcLength(contour, True), True)
area = cv2.contourArea(contour)
if 5000 < area < 15000:
contour_list.append(contour)
msg = "Total holes: {}".format(len(approx)//2)
cv2.putText(img, msg, (20, 40), cv2.FONT_HERSHEY_PLAIN, 2, (0, 0, 255), 2, cv2.LINE_AA)
cv2.drawContours(img, contour_list, -1, (0, 255, 0), 2)
cv2.imshow('Objects Detected', img)
cv2.imwrite("detected_holes.png", img)
cv2.waitKey(0)
Now, the first is a bit different. The same code will not work in detecting the right amount of holes. The program keep detecting also what is clearly not a hole (crack in left bottom angle..) with missing some main holes.
Here is an example of what I am talking about:
Not only the counter in that case is wrong but also, the main problem, is that the hole at right bottom can't be detected.
So, I have managed to figure it by passing the mat directly to FeatureDetector class without any prior processing...
Mat srcMat = mats[0];
if (srcMat == null) {
exception = new Exception("Cropping Failed");
errorMessage = "Unable to crop image based on grid";
return null;
}
matProgressTask = srcMat;
FeatureDetector featureDetector = FeatureDetector.create(FeatureDetector.SIMPLEBLOB);
featureDetector.read(Environment.getExternalStorageDirectory() + "/Android/blob.xml");
Log.e("LoadingBlob", "wqfqfwq");
MatOfKeyPoint matOfKeyPoint = new MatOfKeyPoint();
featureDetector.detect(srcMat, matOfKeyPoint);
KeyPoint[] keyPointsArray = matOfKeyPoint.toArray();
Log.e("keypoints", "" + Arrays.toString(keyPointsArray));
if (keyPointsArray.length < 1) {
exception = new Exception("Blobs Missing");
errorMessage = "Error: Unable to filter blobs";
} else {
try {
MatOfKeyPoint matOfKeyPointFilteredBlobs = new MatOfKeyPoint(keyPointsArray);
Features2d.drawKeypoints(srcMat, matOfKeyPointFilteredBlobs, srcMat, new Scalar(0, 255, 0), Features2d.DRAW_OVER_OUTIMG);
} catch (Exception e) {
e.printStackTrace();
exception = e;
errorMessage = "Error: Unable to draw Blobs";
return null;
}
matProgressTask = srcMat;
onProgressUpdate();
patterData = pinpointBlobsToGetData(keyPointsArray);
if (patterData == null) {
exception = new Exception("Unable to establish pattern");
errorMessage = "Error: Key points array is null";
}
}
And my feature detector parameters file is:
<?xml version="1.0"?>
<opencv_storage>
<format>3</format>
<thresholdStep>10.</thresholdStep>
<minThreshold>50.</minThreshold>
<maxThreshold>120.</maxThreshold>
<minRepeatability>2</minRepeatability>
<minDistBetweenBlobs>20.</minDistBetweenBlobs>
<filterByColor>0</filterByColor>
<blobColor>0</blobColor>
<filterByArea>1</filterByArea>
<minArea>3000.</minArea>
<maxArea>10000.</maxArea>
<filterByCircularity>1</filterByCircularity>
<minCircularity>0.3</minCircularity>
<maxCircularity>1.0</maxCircularity>
<filterByInertia>1</filterByInertia>
<minInertiaRatio>0.3</minInertiaRatio>
<maxInertiaRatio>1.0</maxInertiaRatio>
<filterByConvexity>1</filterByConvexity>
<minConvexity>0.3</minConvexity>
<maxConvexity>1.0</maxConvexity>
</opencv_storage>
The result images:

Drawing the shape of the detected object?

I successfully detected the upperbody of the person in a picture. But all I can do now is draw a rectangle around the upperbody. How can I trace the upperbody?, i.e. draw a line (that looks like the upperbody) around the detected upperbody. I'm working with OpenCV.
Here's some code from the detection system.
if(new File("E:\\OpenCV\\opencv\\data\\haarcascades\\haarcascade_mcs_upperbody.xml\\").isFile())
{ System.out.println("file there"); }
cascadeClassifier = new CascadeClassifier("E:\\OpenCV\\opencv\\data\\haarcascades\\haarcascade_mcs_upperbody.xml");
inputPic = Highgui.imread(picSrcDir + picName);
MatOfInt intw = new MatOfInt(1);
MatOfDouble dble = new MatOfDouble(1.05);
rect = new MatOfRect();
cascadeClassifier.detectMultiScale(inputPic, rect, intw, dble);
Scalar color = new Scalar(0, 0, 255);
System.out.println("Number Of Hits: " + rect.toArray().length);
Rect[] rectArr = rect.toArray();
System.out.println(rectArr.length);
int i=0;
for(Rect recta : rectArr){
System.out.println(rectArr[i]); i++;
Core.rectangle(inputPic, new Point(recta.x, recta.y), new Point(recta.x+recta.width, recta.y+recta.height), color);
}
Highgui.imwrite(picName, inputPic);
After detecting the people upperbody rect:
Remove the rect background, keeping just the person upperbody.
Binarize the image.
Apply morphological boundary algorithm to trace the upperbody.
Example:
OpenCV provides these algorithms. However, the example above was developed using Marvin. The source code is presented below:
public class TraceShape {
public TraceShape(){
// Load Plug-in
MarvinImagePlugin boundary = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.morphological.boundary");
// Load image
MarvinImage image = MarvinImageIO.loadImage("./res/person.jpg");
// Binarize
MarvinImage binImage = MarvinColorModelConverter.rgbToBinary(image, 245);
MarvinImageIO.saveImage(binImage, "./res/person_bin.png");
// Boundary
boundary.process(binImage.clone(), binImage);
MarvinImageIO.saveImage(binImage, "./res/person_boundary.png");
}
public static void main(String[] args) {
new TraceShape();
}
}

Merging BufferedImages leaves black border

For a class project, we were required to take a program that implements the Sobel algorithm and run it across multiple threads as an exercise to become familiar with threading. I have the program running correctly as far as threads go (as far as I can tell), however whenever I implement it using more than 1 thread, I see a black border running down the dividing area where the buffer doesn't seem to be writing to the destination BufferedImage.
Here is some code of the method responsible for threading and writing the file:
public void processImage(int threads)
{
try{
File imgFile = new File(bmpFile);
image = ImageIO.read(imgFile);
w = image.getWidth();
wThr = (image.getWidth()/threads);
h = image.getHeight();
inData = new int[wThr*h];
BufferedImage[] pieces = new BufferedImage[threads];
//instantiate array used for storing pieces of image
for (int i=0; i<threads; i++){
pieces[i] = new BufferedImage(wThr, h, BufferedImage.TYPE_BYTE_GRAY);
}
wThr = pieces[0].getWidth();
h = pieces[0].getHeight();
//instantiate target image
combined = new BufferedImage(w, h, BufferedImage.TYPE_BYTE_GRAY);
//split into threads, each one taking a division of the image and running algorithm
Thread[] threadList = new Thread[threads];
for (int i = 0; i < threadList.length; i++) {
image.getRaster().getPixels((i*wThr), 0, wThr, h, inData);
threadList[i] = new Pr1();
threadList[i].start();
try{
threadList[i].join();
}catch (InterruptedException ie) {}
//Write images to pieces and draw pieces individually onto target image
pieces[i].getRaster().setPixels(0, 0, wThr, h, outData);
Graphics2D g = combined.createGraphics();
g.drawImage(pieces[i], i*(wThr), 0, null);
g.dispose();
}
outFile = new File("1.bmp");
ImageIO.write(combined, "BMP", outFile);
}
catch(IOException e)
{
// Handle the exception here!
}
}
This is the image when processed with 2 threads: http://i.imgur.com/n5gasAW.png?1
Anyone have any insight on how to repair this? I've altered the parameters of the BufferedImages and arrays every way I can think and still no luck.

How to get image height and width using java?

Is there any other way besides using ImageIO.read to get image height and width?
Because I encounter an issue that locks up the thread.
at com.sun.medialib.codec.jpeg.Decoder.njpeg_decode(Native Method)
at com.sun.medialib.codec.jpeg.Decoder.decode(Decoder.java:87)
at com.sun.media.imageioimpl.plugins.jpeg.CLibJPEGImageReader.decode(CLibJPEGImageReader.java:73)
- locked <0xd96fb668> (a com.sun.media.imageioimpl.plugins.jpeg.CLibJPEGImageReader)
at com.sun.media.imageioimpl.plugins.clib.CLibImageReader.getImage(CLibImageReader.java:320)
- locked <0xd96fb668> (a com.sun.media.imageioimpl.plugins.jpeg.CLibJPEGImageReader)
at com.sun.media.imageioimpl.plugins.clib.CLibImageReader.read(CLibImageReader.java:384)
- locked <0xd96fb668> (a com.sun.media.imageioimpl.plugins.jpeg.CLibJPEGImageReader)
at javax.imageio.ImageIO.read(ImageIO.java:1400)
at javax.imageio.ImageIO.read(ImageIO.java:1322)
This error only occurs on a Sun app server and therefore I suspect that it is a Sun bug.
Here is something very simple and handy.
BufferedImage bimg = ImageIO.read(new File(filename));
int width = bimg.getWidth();
int height = bimg.getHeight();
This is a rewrite of the great post by #Kay, which throws IOException and provides an early exit:
/**
* Gets image dimensions for given file
* #param imgFile image file
* #return dimensions of image
* #throws IOException if the file is not a known image
*/
public static Dimension getImageDimension(File imgFile) throws IOException {
int pos = imgFile.getName().lastIndexOf(".");
if (pos == -1)
throw new IOException("No extension for file: " + imgFile.getAbsolutePath());
String suffix = imgFile.getName().substring(pos + 1);
Iterator<ImageReader> iter = ImageIO.getImageReadersBySuffix(suffix);
while(iter.hasNext()) {
ImageReader reader = iter.next();
try {
ImageInputStream stream = new FileImageInputStream(imgFile);
reader.setInput(stream);
int width = reader.getWidth(reader.getMinIndex());
int height = reader.getHeight(reader.getMinIndex());
return new Dimension(width, height);
} catch (IOException e) {
log.warn("Error reading: " + imgFile.getAbsolutePath(), e);
} finally {
reader.dispose();
}
}
throw new IOException("Not a known image file: " + imgFile.getAbsolutePath());
}
I guess my rep is not high enough for my input to be considered worthy as a reply.
I tried to test performance using some of the various approaches listed. It's hard to make a rigorous test as many factors affect the result. I prepared two folders, one with 330 jpg files and another one with 330 png files. The average file size was 4Mb in both cases. Then I called getDimension for each file. Each implementation of getDimension method and each image type was tested separately (separate run). Here is the execution times that I got (first number for jpg, second number for png):
1(Apurv) - 101454ms, 84611ms
2(joinJpegs) - 471ms, N/A
3(Andrew Taylor) - 707ms, 68ms
4(Karussell, ImageIcon) - 106655ms, 100898ms
5(user350756) - 2649ms, 68ms
It's obvious that some methods load the whole file in order to get dimensions while others get by just reading some header information from the image. I think these numbers may be useful when application performance is critical.
Thank you everyone for the contribution to this thread - very helpful.
I have found another way to read an image size (more generic).
You can use ImageIO class in cooperation with ImageReaders.
Here is the sample code:
private Dimension getImageDim(final String path) {
Dimension result = null;
String suffix = this.getFileSuffix(path);
Iterator<ImageReader> iter = ImageIO.getImageReadersBySuffix(suffix);
if (iter.hasNext()) {
ImageReader reader = iter.next();
try {
ImageInputStream stream = new FileImageInputStream(new File(path));
reader.setInput(stream);
int width = reader.getWidth(reader.getMinIndex());
int height = reader.getHeight(reader.getMinIndex());
result = new Dimension(width, height);
} catch (IOException e) {
log(e.getMessage());
} finally {
reader.dispose();
}
} else {
log("No reader found for given format: " + suffix));
}
return result;
}
Note that getFileSuffix is method that returns extension of path without "." so e.g.: png, jpg etc.
Example implementation is:
private String getFileSuffix(final String path) {
String result = null;
if (path != null) {
result = "";
if (path.lastIndexOf('.') != -1) {
result = path.substring(path.lastIndexOf('.'));
if (result.startsWith(".")) {
result = result.substring(1);
}
}
}
return result;
}
This solution is very quick as only image size is read from the file and not the whole image. I tested it and there is no comparison to ImageIO.read performance. I hope someone will find this useful.
You can load jpeg binary data as a file and parse the jpeg headers yourself. The one you are looking for is the 0xFFC0 or Start of Frame header:
Start of frame marker (FFC0)
* the first two bytes, the length, after the marker indicate the number of bytes, including the two length bytes, that this header contains
* P -- one byte: sample precision in bits (usually 8, for baseline JPEG)
* Y -- two bytes
* X -- two bytes
* Nf -- one byte: the number of components in the image
o 3 for color baseline JPEG images
o 1 for grayscale baseline JPEG images
* Nf times:
o Component ID -- one byte
o H and V sampling factors -- one byte: H is first four bits and V is second four bits
o Quantization table number-- one byte
The H and V sampling factors dictate the final size of the component they are associated with. For instance, the color space defaults to YCbCr and the H and V sampling factors for each component, Y, Cb, and Cr, default to 2, 1, and 1, respectively (2 for both H and V of the Y component, etc.) in the Jpeg-6a library by the Independent Jpeg Group. While this does mean that the Y component will be twice the size of the other two components--giving it a higher resolution, the lower resolution components are quartered in size during compression in order to achieve this difference. Thus, the Cb and Cr components must be quadrupled in size during decompression.
For more info about the headers check out wikipedia's jpeg entry or I got the above info here.
I used a method similar to the code below which I got from this post at the sun forums:
import java.awt.Dimension;
import java.io.*;
public class JPEGDim {
public static Dimension getJPEGDimension(File f) throws IOException {
FileInputStream fis = new FileInputStream(f);
// check for SOI marker
if (fis.read() != 255 || fis.read() != 216)
throw new RuntimeException("SOI (Start Of Image) marker 0xff 0xd8 missing");
Dimension d = null;
while (fis.read() == 255) {
int marker = fis.read();
int len = fis.read() << 8 | fis.read();
if (marker == 192) {
fis.skip(1);
int height = fis.read() << 8 | fis.read();
int width = fis.read() << 8 | fis.read();
d = new Dimension(width, height);
break;
}
fis.skip(len - 2);
}
fis.close();
return d;
}
public static void main(String[] args) throws IOException {
System.out.println(getJPEGDimension(new File(args[0])));
}
}
Simple way:
BufferedImage readImage = null;
try {
readImage = ImageIO.read(new File(your path);
int h = readImage.getHeight();
int w = readImage.getWidth();
} catch (Exception e) {
readImage = null;
}
Having struggled with ImageIO a lot in the past years, I think Andrew Taylor's solution is by far the best compromise (fast: not using ImageIO#read, and versatile). Thanks man!!
But I was a little frustrated to be compelled to use a local file (File/String), especially in cases where you want to check image sizes coming from, say, a multipart/form-data request where you usually retrieve InputPart/InputStream's. So I quickly made a variant that accepts File, InputStream and RandomAccessFile, based on the ability of ImageIO#createImageInputStream to do so.
Of course, such a method with Object input, may only remain private and you shall create as many polymorphic methods as needed, calling this one. You can also accept Path with Path#toFile() and URL with URL#openStream() prior to passing to this method:
private static Dimension getImageDimensions(Object input) throws IOException {
try (ImageInputStream stream = ImageIO.createImageInputStream(input)) { // accepts File, InputStream, RandomAccessFile
if(stream != null) {
IIORegistry iioRegistry = IIORegistry.getDefaultInstance();
Iterator<ImageReaderSpi> iter = iioRegistry.getServiceProviders(ImageReaderSpi.class, true);
while (iter.hasNext()) {
ImageReaderSpi readerSpi = iter.next();
if (readerSpi.canDecodeInput(stream)) {
ImageReader reader = readerSpi.createReaderInstance();
try {
reader.setInput(stream);
int width = reader.getWidth(reader.getMinIndex());
int height = reader.getHeight(reader.getMinIndex());
return new Dimension(width, height);
} finally {
reader.dispose();
}
}
}
throw new IllegalArgumentException("Can't find decoder for this image");
} else {
throw new IllegalArgumentException("Can't open stream for this image");
}
}
}
You could use the Toolkit, no need for ImageIO
Image image = Toolkit.getDefaultToolkit().getImage(file.getAbsolutePath());
int width = image.getWidth(null);
int height = image.getHeight(null);
If you don't want to handle the loading of the image do
ImageIcon imageIcon = new ImageIcon(file.getAbsolutePath());
int height = imageIcon.getIconHeight();
int width = imageIcon.getIconWidth();
Problem with ImageIO.read is that it is really slow. All you need to do is to read image header to get the size. ImageIO.getImageReader is perfect candidate.
Here is the Groovy example, but the same thing applies to Java
def stream = ImageIO.createImageInputStream(newByteArrayInputStream(inputStream))
def formatReader = ImageIO.getImageWritersByFormatName(format).next()
def reader = ImageIO.getImageReader(formatReader)
reader.setInput(stream, true)
println "width:reader.getWidth(0) -> height: reader.getHeight(0)"
The performance was the same as using SimpleImageInfo java library.
https://github.com/cbeust/personal/blob/master/src/main/java/com/beust/SimpleImageInfo.java
You can get width and height of image with BufferedImage object using java.
public void setWidthAndHeightImage(FileUploadEvent event) {
byte[] imageTest = event.getFile().getContents();
baiStream = new ByteArrayInputStream(imageTest);
BufferedImage bi = ImageIO.read(baiStream);
//get width and height of image
int imageWidth = bi.getWidth();
int imageHeight = bi.getHeight();
}
To get a Buffered Image with ImageIO.read is a very heavy method, as it's creating a complete uncompressed copy of the image in memory. For png's you may also use pngj and the code:
if (png)
PngReader pngr = new PngReader(file);
width = pngr.imgInfo.cols;
height = pngr.imgInfo.rows;
pngr.close();
}
public static Optional<Dimension> getImageDimensions(Path imageFile) {
Optional<String> suffixOpt = getExtension(imageFile);
Iterator<ImageReader> iter = ImageIO.getImageReadersBySuffix(suffixOpt.orElse(""));
while (iter.hasNext()) {
ImageReader reader = iter.next();
try (ImageInputStream stream = new FileImageInputStream(imageFile.toFile())) {
reader.setInput(stream);
return Optional.of(new Dimension(reader.getWidth(reader.getMinIndex()),
reader.getHeight(reader.getMinIndex())));
} catch (IOException e) {
log.warn("Error reading: " + imageFile, e); //or however you want to handle the exception
} finally {
reader.dispose();
}
}
return Optional.empty();
}
public static Optional<String> getExtension(Path file) {
int pos = file.getFileName().toString().lastIndexOf(".");
if (pos == -1) {
return Optional.empty();
}
return Optional.of(file.getFileName().toString().substring(pos + 1));
}
Revised the method by #Andrew Taylor to use Optionals.
Also uses the Java's NIO Path to make the transition to Path.getExt easier in Java 21 (the second method can be removed and getExtension(imageFile) can be replaced with imageFile.getExtension()).
Also uses the try-with-resources design from Java.
One could instead use an external library in place of the second method if that's preferable.
Using a Spliterator could be another way, though in the end the code became more verbose as little is gained by converting from an Iterator.
So unfortunately, after trying all the answers from above, I did not get them to work after tireless times of trying. So I decided to do the real hack myself and I go this to work for me. I trust it would work perfectly for you too.
I am using this simple method to get the width of an image generated by the app and yet to be upload later for verification :
Pls. take note : you would have to enable permissions in manifest for access storage.
/I made it static and put in my Global class so I can reference or access it from just one source and if there is any modification, it would all have to be done at just one place. Just maintaining a DRY concept in java. (anyway) :)/
public static int getImageWidthOrHeight(String imgFilePath) {
Log.d("img path : "+imgFilePath);
// Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imgFilePath, o);
int width_tmp = o.outWidth, height_tmp = o.outHeight;
Log.d("Image width : ", Integer.toString(width_tmp) );
//you can decide to rather return height_tmp to get the height.
return width_tmp;
}
To get size of emf file without EMF Image Reader you can use code:
Dimension getImageDimForEmf(final String path) throws IOException {
ImageInputStream inputStream = new FileImageInputStream(new File(path));
inputStream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
// Skip magic number and file size
inputStream.skipBytes(6*4);
int left = inputStream.readInt();
int top = inputStream.readInt();
int right = inputStream.readInt();
int bottom = inputStream.readInt();
// Skip other headers
inputStream.skipBytes(30);
int deviceSizeInPixelX = inputStream.readInt();
int deviceSizeInPixelY = inputStream.readInt();
int deviceSizeInMlmX = inputStream.readInt();
int deviceSizeInMlmY = inputStream.readInt();
int widthInPixel = (int) Math.round(0.5 + ((right - left + 1.0) * deviceSizeInPixelX / deviceSizeInMlmX) / 100.0);
int heightInPixel = (int) Math.round(0.5 + ((bottom-top + 1.0) * deviceSizeInPixelY / deviceSizeInMlmY) / 100.0);
inputStream.close();
return new Dimension(widthInPixel, heightInPixel);
}

Categories

Resources