基于图像的直方图作为矢量图形

Histogram based on image as vector graphic

我想将基于图像的直方图转换为矢量图形。

这可能是一个开始:

function preload() {
  img = loadImage("https://upload.wikimedia.org/wikipedia/commons/thumb/3/36/Cirrus_sky_panorama.jpg/1200px-Cirrus_sky_panorama.jpg");
}

function setup() {
  createCanvas(400, 400);
  background(255);
  img.resize(0, 200);
  var maxRange = 256
  colorMode(HSL, maxRange);
  image(img, 0, 0);
  var histogram = new Array(maxRange);
  for (i = 0; i <= maxRange; i++) {
    histogram[i] = 0
  }

  loadPixels();

  for (var x = 0; x < img.width; x += 5) {
    for (var y = 0; y < img.height; y += 5) {
      var loc = (x + y * img.width) * 4;
      var h = pixels[loc];
      var s = pixels[loc + 1];
      var l = pixels[loc + 2];
      var a = pixels[loc + 3];
      b = int(l);
      histogram[b]++
    }
  }
  image(img, 0, 0);
  stroke(300, 100, 80)
  push()
  translate(10, 0)
  for (x = 0; x <= maxRange; x++) {
    index = histogram[x];

    y1 = int(map(index, 0, max(histogram), height, height - 300));
    y2 = height
    xPos = map(x, 0, maxRange, 0, width - 20)
    line(xPos, y1, xPos, y2);
  }
  pop()
}
<script src="https://cdn.jsdelivr.net/npm/p5@1.4.1/lib/p5.js"></script>

但我需要可下载的矢量图形文件作为结果,这些文件是闭合的形状,它们之间没有任何间隙。它应该看起来像这样:

<svg viewBox="0 0 399.84 200"><polygon points="399.84 200 399.84 192.01 361.91 192.01 361.91 182.87 356.24 182.87 356.24 183.81 350.58 183.81 350.58 184.74 344.91 184.74 344.91 188.19 339.87 188.19 339.87 189.89 334.6 189.89 334.6 185.29 328.93 185.29 328.93 171.11 323.26 171.11 323.26 172.55 317.59 172.55 317.59 173.99 311.92 173.99 311.92 179.42 306.88 179.42 306.88 182.03 301.21 182.03 301.21 183.01 295.54 183.01 295.54 179.04 289.87 179.04 289.87 175.67 284.21 175.67 284.21 182.03 278.54 182.03 278.54 176 273.5 176 273.5 172.42 267.83 172.42 267.83 179.42 262.79 179.42 262.79 182.03 257.12 182.03 257.12 183.01 251.45 183.01 251.45 178.63 245.78 178.63 245.78 175.21 240.11 175.21 240.11 182.03 234.86 182.03 234.86 150.42 229.2 150.42 229.2 155.98 223.53 155.98 223.53 158.06 217.86 158.06 217.86 167.44 212.19 167.44 212.19 162.58 206.52 162.58 206.52 155.98 200.85 155.98 200.85 158.06 195.18 158.06 195.18 167.44 189.51 167.44 189.51 177.46 183.84 177.46 183.84 166.93 178.17 166.93 178.17 153.69 172.5 153.69 172.5 155.87 166.82 155.87 166.82 158.05 161.78 158.05 161.78 155.63 156.11 155.63 156.11 160.65 150.84 160.65 150.84 146.59 145.17 146.59 145.17 109.63 139.49 109.63 139.49 113.67 133.82 113.67 133.82 61.48 128.15 61.48 128.15 80.59 123.11 80.59 123.11 93.23 117.44 93.23 117.44 97.97 111.76 97.97 111.76 78.07 106.09 78.07 106.09 61.66 100.42 61.66 100.42 93.23 94.75 93.23 94.75 98.51 89.7 98.51 89.7 85.4 84.03 85.4 84.03 111.03 78.99 111.03 78.99 120.57 73.32 120.57 73.32 124.14 67.65 124.14 67.65 23.48 61.97 23.48 61.97 0 56.3 0 56.3 120.57 50.63 120.57 50.63 167.01 45.38 167.01 45.38 170.83 39.71 170.83 39.71 172.26 34.03 172.26 34.03 178.7 28.36 178.7 28.36 175.36 22.69 175.36 22.69 170.83 17.02 170.83 17.02 172.26 11.34 172.26 11.34 178.7 5.67 178.7 5.67 103.85 0 103.85 0 200 399.84 200"/></svg>

有人知道如何编程吗?它不一定需要基于 p5.js,但会很酷。

缩小差距

要得到无间隙直方图,需要满足以下条件:

numberOfBars * barWidth === totalWidth 

现在您正在使用 p5 line() 函数绘制柱状图。您没有明确设置条形的宽度,因此它使用默认值 1px 宽。

我们知道您代码中的 numberOfBars 总是 maxRange256.

现在直方图的总宽度为 width - 20,其中 widthcreateCanvas(400, 400) 设置为 400。所以 totalWidth380.

256 * 1 !== 380

如果 380 像素中有 256 像素的条形 space 那么就会有间隙!

我们需要改变 barWidth and/or totalWidth 来平衡等式。

例如,您可以将 canvas 大小更改为 276256 + 您的 20 像素边距),然后间隙就会消失!

createCanvas(276, 400);

然而,这不是一个合适的解决方案,因为现在您的图像被裁剪并且您的像素数据是错误的。但其实……之前就已经错了!

采样像素

当您在 p5.js 中调用全局 loadPixels() 函数时,您正在为整个 canvas 加载所有 像素。这包括图像之外的白色区域。

for (var x = 0; x < img.width; x += 5) {
    for (var y = 0; y < img.height; y += 5) {
        var loc = (x + y * img.width) * 4;

它是一个一维数组,因此您在此处限制 xy 值的方法并未为您提供正确的位置。您的 loc 变量需要使用整个 canvas 的宽度,而不仅仅是图像的宽度,因为像素数组包括整个 canvas.

var loc = (x + y * width) * 4;

或者,您可以使用 img.loadPixels()img.pixels.

查看 图像的像素
img.loadPixels();

for (var x = 0; x < img.width; x += 5) {
  for (var y = 0; y < img.height; y += 5) {
    var loc = (x + y * img.width) * 4;
    var h = img.pixels[loc];
    var s = img.pixels[loc + 1];
    var l = img.pixels[loc + 2];
    var a = img.pixels[loc + 3];
    b = int(l);
    histogram[b]++;
  }
}

无论colorMode如何,像素值始终以 RGBA 格式返回。所以你的第三个通道值实际上是 blue,而不是 lightness。您可以使用 p5.js lightness() function 从 RGBA 计算亮度。

更新代码

实际的亮度直方图看起来很愚蠢,因为 100% 使所有其他条形相形见绌。

function preload() {
  img = loadImage("https://upload.wikimedia.org/wikipedia/commons/thumb/3/36/Cirrus_sky_panorama.jpg/1200px-Cirrus_sky_panorama.jpg");
}

function setup() {
  const barCount = 100;
  const imageHeight = 200;

  createCanvas(400, 400);
  background(255);
  colorMode(HSL, barCount - 1);

  img.resize(0, imageHeight);
  imageMode(CENTER);
  image(img, width / 2, imageHeight / 2);
  img.loadPixels();

  const histogram = new Array(barCount).fill(0);

  for (let x = 0; x < img.width; x += 5) {
    for (let y = 0; y < img.height; y += 5) {
      const loc = (x + y * img.width) * 4;
      const r = img.pixels[loc];
      const g = img.pixels[loc + 1];
      const b = img.pixels[loc + 2];
      const a = img.pixels[loc + 3];
      const barIndex = floor(lightness([r, g, b, a]));
      histogram[barIndex]++;
    }
  }

  fill(300, 100, 80);
  strokeWeight(0);

  const maxCount = max(histogram);

  const barWidth = width / barCount;
  const histogramHeight = height - imageHeight;

  for (let i = 0; i < barCount; i++) {
    const count = histogram[i];
    const y1 = round(map(count, 0, maxCount, height, imageHeight));
    const y2 = height;
    const x1 = i * barWidth;
    const x2 = x1 + barWidth;
    rect(x1, y1, barWidth, height - y1);
  }
}
<script src="https://cdn.jsdelivr.net/npm/p5@1.4.1/lib/p5.js"></script>

但是blue通道直方图看起来不错!

function preload() {
  img = loadImage("https://upload.wikimedia.org/wikipedia/commons/thumb/3/36/Cirrus_sky_panorama.jpg/1200px-Cirrus_sky_panorama.jpg");
}

function setup() {
  const barCount = 100;
  const imageHeight = 200;

  createCanvas(400, 400);
  background(255);

  img.resize(0, imageHeight);
  imageMode(CENTER);
  image(img, width / 2, imageHeight / 2);
  img.loadPixels();

  const histogram = new Array(barCount).fill(0);

  for (let x = 0; x < img.width; x += 5) {
    for (let y = 0; y < img.height; y += 5) {
      const loc = (x + y * img.width) * 4;
      const r = img.pixels[loc];
      const g = img.pixels[loc + 1];
      const b = img.pixels[loc + 2];
      const a = img.pixels[loc + 3];
      const barIndex = floor(barCount * b / 255);
      histogram[barIndex]++;
    }
  }

  fill(100, 100, 300);
  strokeWeight(0);

  const maxCount = max(histogram);

  const barWidth = width / barCount;
  const histogramHeight = height - imageHeight;

  for (let i = 0; i < barCount; i++) {
    const count = histogram[i];
    const y1 = round(map(count, 0, maxCount, height, imageHeight));
    const y2 = height;
    const x1 = i * barWidth;
    const x2 = x1 + barWidth;
    rect(x1, y1, barWidth, height - y1);
  }
}
<script src="https://cdn.jsdelivr.net/npm/p5@1.4.1/lib/p5.js"></script>

只是为了添加到 Linda 的优秀答案 (+1),您可以使用 p5.svg 使用 p5.js 呈现为 SVG:

let histogram;

function setup() {
  createCanvas(660, 210, SVG);
  background(255);
  noStroke();
  fill("#ed225d");
  // make an array of 256 random values in the (0, 255) range
  histogram = Array.from({length: 256}, () => int(random(255)));
  //console.log(histogram);
  
  // plot the histogram
  barPlot(histogram, 0, 0, width, height);
  // change shape rendering so bars appear connected
  document.querySelector('g').setAttribute('shape-rendering','crispEdges');
  // save the plot
  save("histogram.svg");
}

function barPlot(values, x, y, plotWidth, plotHeight){
  let numValues = values.length;
  // calculate the width of each bar in the plot
  let barWidth  = plotWidth / numValues;
  // calculate min/max value (to map height)
  let minValue  = min(values);
  let maxValue  = max(values);
  // for each value
  for(let i = 0 ; i < numValues; i++){
    // map the value to the plot height
    let barHeight = map(values[i], minValue, maxValue, 0, plotHeight);
    // render each bar, offseting y 
    rect(x + (i * barWidth), 
         y + (plotHeight - barHeight), 
         barWidth, barHeight);
  }
}
<script src="https://unpkg.com/p5@1.3.1/lib/p5.js"></script>
<script src="https://unpkg.com/p5.js-svg@1.0.7"></script>

(在 p5 editor(或在本地测试时)应弹出一个保存对话框。 如果您使用浏览器的开发人员工具检查条形图,它应该确认它是 SVG(不是 <canvas/>))