java提供获取屏幕数据的接口,我们可以使用这个来实现截屏。
BufferedImage image = robot.createScreenCapture(screenRect);
这个接口返回的是BufferedImage对象。如果只是截屏,我们直接调用ImageIO的保存接口就可以。若需要录制屏幕,就需要结合javacv来保存视频文件。
javacv处理的图像类是Frame。工具库里面提供了BufferedImage和Frame的转换类,例如Java2DFrameConverter、Java2DFrameUtils。但是,我们直接转换后,会发现显示和保存的图片颜色是错误的。
Java2DFrameUtils.toFrame(BufferedImage src)
具体折腾过程就不说明了。我是最后分析摄像头获取的Frame格式,然后转换成BufferedImage。发现这时是没有问题的。所以,我干脆把截屏获取的BufferedImage也转换成跟摄像头一样的格式算了。结果,图片显示正常。
摄像头获取的Frame转换成BufferedImage的type是TYPE_3BYTE_BGR,而截屏获取的BufferedImage的type是TYPE_INT_RGB。初步推测,javacv的转换逻辑存在问题,导致颜色错误。对于究竟是哪个错误,我没有再去分析。
问题还没有结束!!!以上转换后获取的Frame,亮度特别大,跟实际看到的效果不一样。这个问题明显就是Gamma。所以按照Gamma优化一下就行。
最后把我整个实现类贴出来,具体功能都有注释,自己研究就可以。这个实现类我是按照其它Grabber接口定义的。因为拍照和录像跟截屏和录屏的区别就只有这个Grabber,所以我都是直接替换这个Grabber就弄好截屏和录屏功能。
package org.ufo.rtmp;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import org.bytedeco.javacv.CanvasFrame;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameConverter;
/**
* 录制屏幕的Grabber,接口参考其它Grabber
*/
public class ScreenFrameGrabber {
// 采集帧率,兼容其它Grabber接口用
private double frameRate;
// 采集器
private Robot robot;
// 采集区域
private Rectangle screenRect;
// 屏幕大小
private Dimension screenSize;
// 图像转换缓存
private BufferedImage copyImage;
// 图像转换工具(主要是调整Gamma)
private Java2DFrameConverter converter;
// 图像的Gamma值(1.0表示不进行校正,现在显示器使用的是2.2)
// 这个Gamma校正,在CanvasFrame里面也有使用,可以查看源码理解
// 当gamma值小于1时(一般选择1/2.2),图像的整体亮度值得到提升,同时低灰度处的对比度增加,高灰度处的对比度降低,更利于分辩低灰度值时的图像细节
// 当gamma值大于1时(一般选择2.2),图像的整体亮度值得到减小,同时低灰度处的对比度降低,高灰度处的对比度增加,更利于分辩高灰度值时的图像细节
private double gamma = 1.0;
public void start() {
// 检查屏幕设备
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice[] gs = ge.getScreenDevices();
GraphicsDevice display = null;
for (int i = 0; i < gs.length; i++) {
System.out.println("屏幕设备:" + gs[i].getType() + ", " + gs[i].getIDstring());
if (gs[i].getType() == GraphicsDevice.TYPE_RASTER_SCREEN) {
display = gs[i];
}
}
if (display == null) {
System.out.println("没有可用屏幕");
return;
}
// 创建采集工具
try {
robot = new Robot(display);
} catch (Exception e) {
e.printStackTrace();
return;
}
// 显示器的Gamma值
double g = CanvasFrame.getGamma(display);
gamma = g == 0.0 ? 1.0 : g;
// 获取当前屏幕大小
screenSize = Toolkit.getDefaultToolkit().getScreenSize();
System.out.println("屏幕大小:" + screenSize.width + "*" + screenSize.height);
// 默认选择左边640*480的区域
screenRect = new Rectangle(0, 0, 640, 480);
// 默认帧率
frameRate = 25.0;
}
public Frame grab() {
BufferedImage image = robot.createScreenCapture(screenRect);
// 创建转换缓存,且图像类别使用TYPE_3BYTE_BGR。这是摄像头图像使用的格式
// 默认获取的图像类别是TYPE_INT_RGB,这个直接转换后颜色会错误!
// 另外,若对Frame使用OpenCV的图像处理接口(opencv_imgproc),一定要转换成这个类别,否则会报错!
if (copyImage == null ||
copyImage.getWidth() != image.getWidth() ||
copyImage.getHeight() != image.getHeight()) {
copyImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
}
// 将原图绘制到缓存上(实现拷贝和类别转换)
Graphics g = copyImage.getGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
// 创建转换工具
if (converter == null) {
converter = new Java2DFrameConverter();
}
// 由于获取的图片没有进行Gamma处理,亮度会特别高,需要进行校正
return converter.getFrame(copyImage, gamma);
}
public BufferedImage grabBufferedImage() {
return robot.createScreenCapture(screenRect);
}
public void close() {
}
public boolean hasVideo() {
return robot != null;
}
public int getImageWidth() {
return screenRect.width;
}
public void setImageWidth(int imageWidth) {
screenRect.width = imageWidth;
}
public int getImageHeight() {
return screenRect.height;
}
public void setImageHeight(int imageHeight) {
screenRect.height = imageHeight;
}
public int getImageX() {
return screenRect.x;
}
public void setImageX(int imageX) {
screenRect.x = imageX;
}
public int getImageY() {
return screenRect.y;
}
public void setImageY(int imageY) {
screenRect.y = imageY;
}
public double getFrameRate() {
return frameRate;
}
public void setFrameRate(double frameRate) {
this.frameRate = frameRate;
}
public double getGamma() {
return gamma;
}
public void setGamma(double gamma) {
this.gamma = gamma;
}
public Robot getRobot() {
return robot;
}
public Rectangle getScreenRect() {
return screenRect;
}
public Dimension getScreenSize() {
return screenSize;
}
}