Crop Buffered image without losing dpi in java - image-processing

I am cropping the image by using the java buffered image. The problem is the cropped image is losing quality in terms of DPI. Say, for example, If the parent image has DPI of 200, the cropped image DPI is reduced to 96. Because of this, I am losing OCR accuracy.
Following is the code snippet I am using get the cropped Buffered Image.
BufferedImage cropImage( BufferedImage image, final int[] topLeft, int width, int height )
{
int x = Math.max( topLeft[CoOrdinate.X.getValue()], image.getMinX() );
int y = Math.max( topLeft[CoOrdinate.Y.getValue()], image.getMinY() );
int imageWidth = image.getWidth();
int imageHeight = image.getHeight();
width = ( x + width ) > imageWidth ? imageWidth - x : width;
height = ( y + height ) > imageHeight ? imageHeight - y : height;
return image.getSubimage( x, y, width, height );
}
Then I save it using ImageIO
File saveImage( String filePath, BufferedImage image )
{
String formatName = FilenameUtils.getExtension( filePath );
File croppedFile = new File( filePath );
try {
ImageIO.write( image, formatName,croppedFile );
return croppedFile;
} catch ( IOException e ) {
LOG.error( "IO Exception occured while saving a buffered image" );
throw new FileHandlingException( "IO Exception occured while saving a buffered image", e );
}
}
How can I crop the image without losing DPI quality? I saw many solutions for Image Resizing and I understand resizing will lose quality. But cropping, preserving the DPI should be straightforward right?
EDIT:
ImageMagick's crop does exactly what I need. but I will have to use command line from java. Not an option for me right now.

Related

Issue when drawing image on canvas on iPad

I'm working on an HTML5 project, that will run in a WKWebView on iPad.
I'm doing everything programatically.
My WKWebView frame takes the full screen, viewport is 1180x820,
so I set my canvas size and height to 1180x820 too.
Here is my original picture I'd like to display:
But when I'm displaying an 1920x1080 image on my canvas with the drawImage function,
the image does not fit on the screen (obsviously) but is really well displayed (not blurred).
_canvasContext.drawImage(imgMenu,0,0,1920,1080).
So I rescale the image when drawing it, still with the drawImage function.
_canvasContext.drawImage(imgMenu,0,0,1920/2,1080/2)
The image fits in the screen, but is blurred.
The downscaling is really really bad (it really could be better, this example is not the worst one).
I already tried the parameters
_canvasContext.imageSmoothingEnabled = true;
_canvasContext.webkitImageSmoothingEnabled = true;
_canvasContext.mozImageSmoothingEnabled = true;
_canvasContext.imageSmoothingQuality = "high";
It does not help.
Maybe I do something wrong, I don't understand what to do.
Screen resolution of my ipad is 2360x1640, so displaying a 1920x1080 picture should not be a problem.
If anyone could help me, that would save my life :)
Best regards,
Alex
This is one of those annoying and confusing things about canvas. What you need to do is size the canvas using devicePixelRatio. This will increase the actual size of the canvas to match the pixel density of the device. This could be 2, or 1.5 etc... a retina screen is often 2.
For drawing your image, the smart way is to support any image size and fit it into the canvas area (usually scaling down). With a tiny image, this code will scale up and lose resolution.
const IMAGE_URL = 'https://unsplash.it/1920/1080';
const canvas = document.createElement('canvas');
const _canvasContext = canvas.getContext('2d');
// you will probably want this, unless you want to support many screen sizes in which case you will actually want the window size:
/*
const width = 1180;
const height = 820;
*/
const width = window.innerWidth
const height = window.innerHeight
const ratio = window.devicePixelRatio;
// size the canvas to use pixel ratio
canvas.width = Math.round(width * ratio);
canvas.height = Math.round(height * ratio);
// downsize the canvas with css - making it
// retina compatible
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';
document.body.appendChild(canvas);
_canvasContext.fillStyle = 'gray'
_canvasContext.fillRect(0, 0, canvas.width, canvas.height);
function drawImage(url) {
const img = new Image()
img.addEventListener('load', () => {
// find a good scale value to fit the image
// on the canvas
const scale = Math.min(
canvas.width / img.width,
canvas.height / img.height
);
// calculate image size and padding
const scaledWidth = img.width * scale;
const scaledHeight = img.height * scale;
const padX = scaledWidth < canvas.width ? canvas.width - scaledWidth : 0;
const padY = scaledHeight < canvas.height ? canvas.height - scaledHeight : 0;
_canvasContext.drawImage(img, padX / 2, padY / 2, scaledWidth, scaledHeight);
})
img.src = url;
}
drawImage(IMAGE_URL);
body,html {
margin: 0;
padding: 0;
}
As mentioned in a comment in the code snippet. If your want your canvas to always use 1180x820... be sure to change the width and height variables:
const width = 1180;
const height = 820;
For the purposes of the snippet I used the window size. Which may be better for you if you wish to support other device sizes.

How can I randomize Image size

Thank you to the people who previously helped me, I have managed to work a lot on my generative business cards assignment.
I want to randomly resize 9 images in processing but can't seem to find a good example on the internet on how to do it. The size of the images is 850x550 which is also the background size.
Does anyone know a good and easy to follow tutorial? or could give me an example?
The Processing's documentation on the image() method covers this.
I still wrote you some skeleton code to demonstrate:
PImage img;
int w, h;
float scaleModifier = 1;
void setup() {
size(800, 600);
img = loadImage("bean.jpeg");
w = img.width;
h = img.height;
}
void draw() {
background(0);
image(img, 0, 0, w, h); // here is the important line
}
// Every click will resize the image
void mouseClicked() {
scaleModifier += 0.1;
if (scaleModifier > 1) {
scaleModifier = 0.1;
}
w = (int)(img.width * scaleModifier);
h = (int)(img.height * scaleModifier);
}
What's important to know is the following:
image() has 2 signatures:
image(img, a, b)
image(img, a, b, c, d)
Where the following applies:
img => the PImage for your image
a => x coordinate where to draw the image
b => y coordinate where to draw the image
c => the image's width (if it's different from the image's width, this implies a resize)
d => the image's height (also implies a resize if it's different from the "real" height)
Have fun!
say you have stored an image in a PImage object, image
you can generate two random integers for the img_width and img_height of the image and then resize() the image using resize() method
int img_width = foor(random(min_value, max_value));
int img_height = floor(random(min_value, max_value));
image.resize(img_width, img_height); //this simple code resizes the image to any dimension
or if you want to keep the same aspect ratio, then you can use this approach
//first set either of width or height to a random value
int img_width = floor(random(min_value, max_value));
//then proportionally calculate the other dimension of the image
float ratio = (float) image.width/image.height;
int img_height = floor(img_width/ratio);
image.resize(img_width, img_height);
You can check this out YouTube playlist for some tutorials of image processing.

Extract Color from image using OpenCV

Predefined: My A4 sheet will always be of white color.
I need to detect A4 sheet from image. I am able to detect rectangles, now the problem is I am getting multiple rectangles from my image. So I extracted the images from the detected rectangle points.
Now I want to match image color with white color.
Using below method to extract image from contours detected :
- (cv::Mat) getPaperAreaFromImage: (std::vector<cv::Point>) square, cv::Mat image
{
// declare used vars
int paperWidth = 210; // in mm, because scale factor is taken into account
int paperHeight = 297; // in mm, because scale factor is taken into account
cv::Point2f imageVertices[4];
float distanceP1P2;
float distanceP1P3;
BOOL isLandscape = true;
int scaleFactor;
cv::Mat paperImage;
cv::Mat paperImageCorrected;
cv::Point2f paperVertices[4];
// sort square corners for further operations
square = sortSquarePointsClockwise( square );
// rearrange to get proper order for getPerspectiveTransform()
imageVertices[0] = square[0];
imageVertices[1] = square[1];
imageVertices[2] = square[3];
imageVertices[3] = square[2];
// get distance between corner points for further operations
distanceP1P2 = distanceBetweenPoints( imageVertices[0], imageVertices[1] );
distanceP1P3 = distanceBetweenPoints( imageVertices[0], imageVertices[2] );
// calc paper, paperVertices; take orientation into account
if ( distanceP1P2 > distanceP1P3 ) {
scaleFactor = ceil( lroundf(distanceP1P2/paperHeight) ); // we always want to scale the image down to maintain the best quality possible
paperImage = cv::Mat( paperWidth*scaleFactor, paperHeight*scaleFactor, CV_8UC3 );
paperVertices[0] = cv::Point( 0, 0 );
paperVertices[1] = cv::Point( paperHeight*scaleFactor, 0 );
paperVertices[2] = cv::Point( 0, paperWidth*scaleFactor );
paperVertices[3] = cv::Point( paperHeight*scaleFactor, paperWidth*scaleFactor );
}
else {
isLandscape = false;
scaleFactor = ceil( lroundf(distanceP1P3/paperHeight) ); // we always want to scale the image down to maintain the best quality possible
paperImage = cv::Mat( paperHeight*scaleFactor, paperWidth*scaleFactor, CV_8UC3 );
paperVertices[0] = cv::Point( 0, 0 );
paperVertices[1] = cv::Point( paperWidth*scaleFactor, 0 );
paperVertices[2] = cv::Point( 0, paperHeight*scaleFactor );
paperVertices[3] = cv::Point( paperWidth*scaleFactor, paperHeight*scaleFactor );
}
cv::Mat warpMatrix = getPerspectiveTransform( imageVertices, paperVertices );
cv::warpPerspective(image, paperImage, warpMatrix, paperImage.size(), cv::INTER_LINEAR, cv::BORDER_CONSTANT );
if (true) {
cv::Rect rect = boundingRect(cv::Mat(square));
cv::rectangle(image, rect.tl(), rect.br(), cv::Scalar(0,255,0), 5, 8, 0);
UIImage *object = [self UIImageFromCVMat:paperImage];
}
// we want portrait output
if ( isLandscape ) {
cv::transpose(paperImage, paperImageCorrected);
cv::flip(paperImageCorrected, paperImageCorrected, 1);
return paperImageCorrected;
}
return paperImage;
}
EDITED: I used below method to get the color from image. But now my problem after converting my original image to cv::mat, when I am cropping there is already transparent grey color over my image. So always I am getting the same color.
Is there any direct way to get original color from cv::mat image?
- (UIColor *)averageColor: (UIImage *) image {
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
unsigned char rgba[4];
CGContextRef context = CGBitmapContextCreate(rgba, 1, 1, 8, 4, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGContextDrawImage(context, CGRectMake(0, 0, 1, 1), image.CGImage);
CGColorSpaceRelease(colorSpace);
CGContextRelease(context);
if(rgba[3] > 0) {
CGFloat alpha = ((CGFloat)rgba[3])/255.0;
CGFloat multiplier = alpha/255.0;
return [UIColor colorWithRed:((CGFloat)rgba[0])*multiplier
green:((CGFloat)rgba[1])*multiplier
blue:((CGFloat)rgba[2])*multiplier
alpha:alpha];
}
else {
return [UIColor colorWithRed:((CGFloat)rgba[0])/255.0
green:((CGFloat)rgba[1])/255.0
blue:((CGFloat)rgba[2])/255.0
alpha:((CGFloat)rgba[3])/255.0];
}
}
EDIT 2 :
Input Image
Getting this output
Need to detect only A4 sheet of white color.
I just resolved it using Google Vision api.
My objective was to calculate the cracks for builder purpose from image so in my case User will be using A4 sheet as reference on the image where crack is, and I will capture the A4 sheet and calculate the size taken by each pixel. Then build will tap on two points in the crack, and I will calculate the distance.
In google vision I used document text detection api and printed my app name on A4 sheet fully covered vertically or horizontally. And google vision api detect that text and gives me the coordinate.

Image auto cropping when rotate in OpenCV.js

I'm using OpenCV.js to rotate image to the left and right, but it was cropped when I rotate.
This is my code:
let src = cv.imread('img');
let dst = new cv.Mat();
let dsize = new cv.Size(src.rows, src.cols);
let center = new cv.Point(src.cols/2, src.rows/2);
let M = cv.getRotationMatrix2D(center, 90, 1);
cv.warpAffine(src, dst, M, dsize, cv.INTER_LINEAR, cv.BORDER_CONSTANT, new cv.Scalar());
cv.imshow('canvasOutput', dst);
src.delete(); dst.delete(); M.delete();
Here is an example:
This is my source image:
This is what I want:
But it returned like this:
What should I do to fix this problem?
P/s: I don't know how to use different languages except javascript.
A bit late but given the scarcity of opencv.js material I'll post the answer:
The function cv.warpAffine crops the image because it only does a mathematical transformation as documented on OpenCV and other sources, if you wish to do rotations to any angle you'll need to calculate the padding in order to compensate that.
If you wish to only rotate in multiples of 90 degrees you could use cv.rotate as follows:
cv.rotate(src, dst, cv.ROTATE_90_CLOCKWISE);
Where src is the matrix with your source image, dst is the destination matrix which could be defined empty as follows let dst = new cv.Mat(); and cv.ROTATE_90_CLOCKWISE is the rotate flag indicating the angle of rotation, there are three different options:
cv.ROTATE_90_CLOCKWISE
cv.ROTATE_180
cv.ROTATE_90_COUNTERCLOCKWISE
You can find which OpenCV functions are implemented on OpenCV.js on the repository's opencv_js.congif.py file if the function is indicated as whitelisted then is working on opencv.js even if it is not included in the opencv.js tutorial.
The info about how to use each function can be found in the general documentation, the order of the parameters is generally the indicated on the C++ indications (don't be distracted by the oscure C++ vector types sintax) and the name of the flags (like rotate flag) is usually indicated on the python indications.
I was also experiencing this issue so had a look into #fernando-garcia's answer, however I couldn't see that rotate had been implemented in opencv.js so it seems that the fix in the post #dan-mašek's links is the best solution for this, however the functions required are slightly different.
This is the solution I came up with (note, I haven't tested this exact code and there is probably a more elegant/efficient way of writing this, but it gives the general idea. Also this will only work with images rotated by multiples of 90°):
const canvas = document.getElementById('canvas');
const image = cv.imread(canvas);
let output = new cv.Mat();
const size = new cv.Size();
size.width = image.cols;
size.height = image.rows;
// To add transparent borders
const scalar = new cv.Scalar(0, 0, 0, 0);
let center;
let padding;
let height = size.height;
let width = size.width;
if (height > width) {
center = new cv.Point(height / 2, height / 2);
padding = (height - width) / 2;
// Pad out the left and right before rotating to make the width the same as the height
cv.copyMakeBorder(image, output, 0, 0, padding, padding, cv.BORDER_CONSTANT, scalar);
size.width = height;
} else {
center = new cv.Point(width / 2, width / 2);
padding = (width - height) / 2;
// Pad out the top and bottom before rotating to make the height the same as the width
cv.copyMakeBorder(image, output, padding, padding, 0, 0, cv.BORDER_CONSTANT, scalar);
size.height = width;
}
// Do the rotation
const rotationMatrix = cv.getRotationMatrix2D(center, 90, 1);
cv.warpAffine(
output,
output,
rotationMatrix,
size,
cv.INTER_LINEAR,
cv.BORDER_CONSTANT,
new cv.Scalar()
);
let rectangle;
if (height > width) {
rectangle = new cv.Rect(0, padding, height, width);
} else {
/* These arguments might not be in the right order as my solution only needed height
* > width so I've just assumed this is the order they'll need to be for width >=
* height.
*/
rectangle = new cv.Rect(padding, 0, height, width);
}
// Crop the image back to its original dimensions
output = output.roi(rectangle);
cv.imshow(canvas, output);

Save frames of background subtraction capture

I am doing a background subtraction capture demo recently but I met with difficulties. I have already get the pixel of silhouette extraction and I intend to draw it into a buffer through createGraphics(). I set the new background is 100% transparent so that I could only get the foreground extraction. Then I use saveFrame() function in order to get png file of each frame. However, it doesn't work as I expected. I intend to get a series of png of the silhouette extraction
with 100% transparent background but now I only get the general png of frames from the camera feed. Is there anyone could help me to see what's the problem with this code? Thanks a lot in advance. Any help will be appreciated.
import processing.video.*;
Capture video;
PGraphics pg;
PImage backgroundImage;
float threshold = 30;
void setup() {
size(320, 240);
video = new Capture(this, width, height);
video.start();
backgroundImage = createImage(video.width, video.height, RGB);
pg = createGraphics(320, 240);
}
void captureEvent(Capture video) {
video.read();
}
void draw() {
pg.beginDraw();
loadPixels();
video.loadPixels();
backgroundImage.loadPixels();
image(video, 0, 0);
for (int x = 0; x < video.width; x++) {
for (int y = 0; y < video.height; y++) {
int loc = x + y * video.width;
color fgColor = video.pixels[loc];
color bgColor = backgroundImage.pixels[loc];
float r1 = red(fgColor); float g1 = green(fgColor); float b1 = blue(fgColor);
float r2 = red(bgColor); float g2 = green(bgColor); float b2 = blue(bgColor);
float diff = dist(r1, g1, b1, r2, g2, b2);
if (diff > threshold) {
pixels[loc] = fgColor;
} else {
pixels[loc] = color(0, 0);
}
}}
pg.updatePixels();
pg.endDraw();
saveFrame("line-######.png");
}
void mousePressed() {
backgroundImage.copy(video, 0, 0, video.width, video.height, 0, 0, video.width, video.height);
backgroundImage.updatePixels();
}
Re:
Then I use saveFrame() function in order to get png file of each frame. However, it doesn't work as I expected. I intend to get a series of png of the silhouette extraction with 100% transparent background but now I only get the general png of frames from the camera feed.
This won't work, because saveFrame() saves the canvas, and the canvas doesn't support transparency. For example, from the reference:
It is not possible to use the transparency alpha parameter with background colors on the main drawing surface. It can only be used along with a PGraphics object and createGraphics(). https://processing.org/reference/background_.html
If you want to dump a frame with transparency you need to use .save() to dump it directly from a PImage / PGraphics.
https://processing.org/reference/PImage_save_.html
If you need to clear your PImage / PGraphics and reuse it each frame, either use pg.clear() or pg.background(0,0,0,0) (set all pixels to transparent black).

Resources