如何解决加工 |程序滞后 贾法玛
我是 Processing 的新手,我需要制作一个程序,该程序捕获主监视器,在第二个屏幕上显示平均颜色,并使用函数获得的另一种颜色(感知主色)制作螺旋线。
问题是程序太慢(滞后,1FPS)。我觉得是因为每次截图的时候要做的事情太多,但我不知道如何让它更快。
也可能有许多其他问题,但主要的就是那个。
非常感谢!
代码如下:
import java.awt.Robot;
import java.awt.AWTException;
import java.awt.Rectangle;
import java.awt.color.ColorSpace;
PImage screenshot;
float a = 0;
int blockSize = 20;
int avg_c;
int per_c;
void setup() {
fullScreen(2); // 1920x1080
nostroke();
frame.removeNotify();
}
void draw() {
screenshot();
avg_c = extractColorFromImage(screenshot);
per_c = extractAverageColorFromImage(screenshot);
background(avg_c); // Average color
spiral();
}
void screenshot() {
try{
Robot robot_Screenshot = new Robot();
screenshot = new PImage(robot_Screenshot.createScreenCapture
(new Rectangle(0,displayWidth,displayHeight)));
}
catch (AWTException e){ }
frame.setLocation(displayWidth/2,0);
}
void spiral() {
fill (per_c);
for (int i = blockSize; i < width; i += blockSize*2)
{
ellipse(i,height/2+sin(a+i)*100,blockSize+cos(a+i)*5,blockSize+cos(a+i)*5);
a += 0.001;
}
}
color extractColorFromImage(PImage screenshot) { // Get average color
screenshot.loadPixels();
int r = 0,g = 0,b = 0;
for (int i = 0; i < screenshot.pixels.length; i++) {
color c = screenshot.pixels[i];
r += c>>16&0xFF;
g += c>>8&0xFF;
b += c&0xFF;
}
r /= screenshot.pixels.length; g /= screenshot.pixels.length; b /= screenshot.pixels.length;
return color(r,g,b);
}
color extractAverageColorFromImage(PImage screenshot) { // Get lab average color (perceptual)
float[] average = new float[3];
CIELab lab = new CIELab();
int numPixels = screenshot.pixels.length;
for (int i = 0; i < numPixels; i++) {
color rgb = screenshot.pixels[i];
float[] labValues = lab.fromrGB(new float[]{red(rgb),green(rgb),blue(rgb)});
average[0] += labValues[0];
average[1] += labValues[1];
average[2] += labValues[2];
}
average[0] /= numPixels;
average[1] /= numPixels;
average[2] /= numPixels;
float[] rgb = lab.toRGB(average);
return color(rgb[0] * 255,rgb[1] * 255,rgb[2] * 255);
}
public class CIELab extends ColorSpace {
@Override
public float[] fromCIEXYZ(float[] colorvalue) {
double l = f(colorvalue[1]);
double L = 116.0 * l - 16.0;
double a = 500.0 * (f(colorvalue[0]) - l);
double b = 200.0 * (l - f(colorvalue[2]));
return new float[] {(float) L,(float) a,(float) b};
}
@Override
public float[] fromrGB(float[] rgbvalue) {
float[] xyz = CIEXYZ.fromrGB(rgbvalue);
return fromCIEXYZ(xyz);
}
@Override
public float getMaxValue(int component) {
return 128f;
}
@Override
public float getMinValue(int component) {
return (component == 0)? 0f: -128f;
}
@Override
public String getName(int idx) {
return String.valueOf("Lab".charat(idx));
}
@Override
public float[] toCIEXYZ(float[] colorvalue) {
double i = (colorvalue[0] + 16.0) * (1.0 / 116.0);
double X = fInv(i + colorvalue[1] * (1.0 / 500.0));
double Y = fInv(i);
double Z = fInv(i - colorvalue[2] * (1.0 / 200.0));
return new float[] {(float) X,(float) Y,(float) Z};
}
@Override
public float[] toRGB(float[] colorvalue) {
float[] xyz = toCIEXYZ(colorvalue);
return CIEXYZ.toRGB(xyz);
}
CIELab() {
super(ColorSpace.TYPE_Lab,3);
}
private double f(double x) {
if (x > 216.0 / 24389.0) {
return Math.cbrt(x);
} else {
return (841.0 / 108.0) * x + N;
}
}
private double fInv(double x) {
if (x > 6.0 / 29.0) {
return x*x*x;
} else {
return (108.0 / 841.0) * (x - N);
}
}
private final ColorSpace CIEXYZ =
ColorSpace.getInstance(ColorSpace.CS_CIEXYZ);
private final double N = 4.0 / 29.0;
}
解决方法
是的,在我的机器上大约 1 FPS:
优化代码真的很难,所以我没有阅读所有内容寻找改进的地方,而是从测试您失去如此多处理能力的地方开始。答案就在这一行:
per_c = extractAverageColorFromImage(screenshot);
extractAverageColorFromImage
方法写得很好,但它低估了它必须做的工作量。屏幕的大小和这个屏幕的像素数之间存在二次关系,所以屏幕越大情况越糟。并且这种方法一直在处理屏幕截图的每个像素,每个屏幕截图多次。
对于平均颜色来说,这需要做很多工作。现在,如果有一种方法可以削减一些角落……也许是更小的屏幕,或者更小的屏幕截图……哦!有!让我们调整屏幕截图的大小。毕竟,我们不需要深入研究诸如单个像素的平均值之类的细节。在 screenshot
方法中,添加以下行:
void screenshot() {
try {
Robot robot_Screenshot = new Robot();
screenshot = new PImage(robot_Screenshot.createScreenCapture(new Rectangle(0,displayWidth,displayHeight)));
// ADD THE NEXT LINE
screenshot.resize(width/4,height/4);
}
catch (AWTException e) {
}
frame.setLocation(displayWidth/2,0);
}
我将工作量除以 4,但我鼓励您调整这个数字,直到您能以最快的速度获得满意的结果。这只是一个概念证明:
如您所见,调整屏幕截图的大小并将其缩小 4 倍可使我的速度提高 10 倍。这不是奇迹,但要好得多,我看不出最终结果有什么不同 - 但关于那部分,你必须使用自己的判断,因为你知道你的项目是关于什么的.希望能帮到你!
玩得开心!
,很遗憾,我无法提供像 laancelot (+1) 这样的详细答案,但希望我可以提供一些提示:
- 调整图像大小绝对是一个好的方向。请记住,您还可以跳过多个像素,而不是增加每个像素。 (如果您正确处理像素索引,您可以在不调用 resize 的情况下获得类似的调整大小效果,尽管这不会为您节省大量 CPU 时间)
- 不要在一秒钟内多次创建新的
Robot
实例。在设置中创建一次并重新使用它。 (这更像是一个好习惯) - 使用 CPU 分析器(例如 VisualVM 中的分析器)查看到底什么地方慢,并首先优化最慢的东西。
第 1 点 示例:
for (int i = 0; i < numPixels; i+= 100)
第 2 点 示例:
Robot robot_Screenshot;
...
void setup() {
fullScreen(2); // 1920x1080
noStroke();
frame.removeNotify();
try{
robot_Screenshot = new Robot();
}catch(AWTException e){
println("error setting up screenshot Robot instance");
e.printStackTrace();
}
}
...
void screenshot() {
screenshot = new PImage(robot_Screenshot.createScreenCapture
(new Rectangle(0,displayHeight)));
frame.setLocation(displayWidth/2,0);
}
请注意,最慢的位实际上是 AWT 的 fromRGB 和 Math.cbrt()
我建议寻找另一种更简单的替代 RGB -> XYZ -> L*a*b*
转换方法(主要是函数、更少的类、具有 AWT 或其他依赖项)并且希望更快。
还有很多事情可以做,甚至超出了已经提到的内容。
迭代和线程
截取屏幕截图后,立即迭代缓冲图像的每 1/N 个像素(可能每 4 或 8 个)。然后,在此迭代过程中,计算每个像素的 LAB 值(因为您可以直接使用每个像素通道),同时增加每个 RGB 通道的运行总数。
这使我们免于对相同像素进行两次迭代并避免不必要的转换(BufferedImage
→ PImage
;然后从 PImage
像素组合然后分解像素通道)。
同样,我们避免了 Processing 昂贵的 resize()
调用(如另一个答案中所建议的),这不是我们想要在每一帧都调用的东西(即使它确实加快了程序速度,但它不是一种有效的方法)。
现在,在迭代更改之上,我们可以将迭代包装在 Callable
中,以便轻松地跨多个系统线程并发运行工作负载(毕竟,像素迭代是令人尴尬的并行) ;下面的示例使用 2 个线程执行此操作,每个线程截屏并处理显示器像素的一半。
优化RGB→XYZ→LAB转换
我们不太关心向后转换,因为它只对每帧一个值进行
您似乎自己实现了 XYZ→LAB,并且正在使用 java.awt.color
中的 RGB→XYZ 转换器。
正如已经确定的那样,正向转换 XYZ→LAB 使用 cbrt()
作为瓶颈。我还想象 RGB→XYZ 实现对 Math.Pow(x,2.4)
进行了 3 次调用——每个像素 3 个非整数指数大大增加了计算量。解决方案是更快的数学...
贾法玛
Jafama 是一个内置的 java.math
替代品——只需导入库并将任何 Math.__()
调用替换为 FastMath.__()
以获得免费加速(您可以更进一步将 Jafama 的 E-15 精度与准确度更低但速度更快的基于 LUT 的专用类进行交换)。
所以至少,将 Math.cbrt()
换成 FastMath.cbrt()
。然后考虑自己实现 RGB→XYZ (example),再次使用 Jafama 代替 java.math
。
您甚至可能会发现,对于这样的项目,仅转换为 XYZ 就足以克服 RGB 众所周知的弱点(从而避免 XYZ→LAB 转换)。
缓存 LAB 计算
除非大多数像素每一帧都在变化,那么考虑缓存每个像素的LAB值,只有当像素在当前前一帧之间发生变化时才重新计算它。这里的权衡是根据每个像素的先前值检查每个像素的开销,以及肯定检查将节省多少计算。鉴于 LAB 计算要贵得多,所以在这里非常值得。下面的示例使用了这种技术。
屏幕截图
无论程序的其余部分优化得多么好,一个相当大的瓶颈是 AWT 机器人的 createScreenCapture()
。在足够大的显示器上,它很难超过 30FPS。我无法提供任何确切的建议,但值得查看 Java 中的其他屏幕捕获方法。
通过迭代更改和线程重新编写代码
此代码实现了上面讨论的内容,减去了对 LAB 计算的任何更改。
float a = 0;
int blockSize = 20;
int avg_c;
int per_c;
java.util.concurrent.ExecutorService threadPool = java.util.concurrent.Executors.newFixedThreadPool(4);
List<java.util.concurrent.Callable<Boolean>> taskList;
float[] averageLAB;
int totalR = 0,totalG = 0,totalB = 0;
CIELab lab = new CIELab();
final int pixelStride = 8; // look at every 8th pixel
void setup() {
size(800,800,FX2D);
noStroke();
frame.removeNotify();
taskList = new ArrayList<java.util.concurrent.Callable<Boolean>>();
Compute thread1 = new Compute(0,width,height/2);
Compute thread2 = new Compute(0,height/2,height/2);
taskList.add(thread1);
taskList.add(thread2);
}
void draw() {
totalR = 0; // re init
totalG = 0; // re init
totalB = 0; // re init
averageLAB = new float[3]; // re init
final int numPixels = (width*height)/pixelStride;
try {
threadPool.invokeAll(taskList); // run threads now and block until completion of all
}
catch (Exception e) {
e.printStackTrace();
}
// calculate average LAB
averageLAB[0]/=numPixels;
averageLAB[1]/=numPixels;
averageLAB[2]/=numPixels;
final float[] rgb = lab.toRGB(averageLAB);
per_c = color(rgb[0] * 255,rgb[1] * 255,rgb[2] * 255);
// calculate average RGB
totalR/=numPixels;
totalG/=numPixels;
totalB/=numPixels;
avg_c = color(totalR,totalG,totalB);
background(avg_c); // Average color
spiral();
fill(255,0);
text(frameRate,10,20);
}
class Compute implements java.util.concurrent.Callable<Boolean> {
private final Rectangle screenRegion;
private Robot robot_Screenshot;
private final int[] previousRGB;
private float[][] previousLAB;
Compute(int x,int y,int w,int h) {
screenRegion = new Rectangle(x,y,w,h);
previousRGB = new int[w*h];
previousLAB = new float[w*h][3];
try {
robot_Screenshot = new Robot();
}
catch (AWTException e1) {
e1.printStackTrace();
}
}
@Override
public Boolean call() {
BufferedImage rawScreenshot = robot_Screenshot.createScreenCapture(screenRegion);
int[] ssPixels = new int[rawScreenshot.getWidth()*rawScreenshot.getHeight()]; // screenshot pixels
rawScreenshot.getRGB(0,rawScreenshot.getWidth(),rawScreenshot.getHeight(),ssPixels,rawScreenshot.getWidth()); // copy buffer to int[] array
for (int pixel = 0; pixel < ssPixels.length; pixel+=pixelStride) {
// get invididual colour channels
final int pixelColor = ssPixels[pixel];
final int R = pixelColor >> 16 & 0xFF;
final int G = pixelColor >> 8 & 0xFF;
final int B = pixelColor & 0xFF;
if (pixelColor != previousRGB[pixel]) { // if pixel has changed recalculate LAB value
float[] labValues = lab.fromRGB(new float[]{R/255f,G/255f,B/255f}); // note that I've fixed this; beforehand you were missing the /255,so it was always white.
previousLAB[pixel] = labValues;
}
averageLAB[0] += previousLAB[pixel][0];
averageLAB[1] += previousLAB[pixel][1];
averageLAB[2] += previousLAB[pixel][2];
totalR+=R;
totalG+=G;
totalB+=B;
previousRGB[pixel] = pixelColor; // cache last result
}
return true;
}
}
800x800 像素;像素步幅 = 4;相当静态的屏幕背景
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。