likes
comments
collection
share

浏览器中对图像进行二值化处理

作者站长头像
站长
· 阅读数 5

前提

最近对做了一些图像 OCR 相关的东西,发现文字识别不准确不只是引擎的问题,浏览器中也可以做一些优化来提高识别率,其中一个方法就是图像的二值化处理。

简介

什么是二值化呢?

二值化(Binarization)是图像处理中的一种基本技术,它将图像中的每个像素点转换成两个颜色值中的一个,通常是黑色和白色。这种处理方法特别适用于准备图像数据以进行进一步分析,比如文字识别(OCR)和图形识别等场景。二值化通过减少图像的颜色信息来简化图像内容,使得背景与前景(如文字或物体)之间的对比更加明显。

二值化的工作原理

二值化过程通常涉及以下几个步骤:

  1. 灰度化(Grayscale) :首先,将彩色图像转换成灰度图像。这是因为原始的彩色图像包含三个颜色通道(红、绿、蓝),而灰度图像只有一个颜色通道,处理起来更简单。灰度化通常通过计算每个像素点在三个颜色通道的加权平均值来实现。
  2. 设置阈值(Thresholding) :接着,选择一个阈值来区分前景和背景。像素点的灰度值如果大于这个阈值,则将其设置为一种颜色(通常是白色);如果小于或等于阈值,则设置为另一种颜色(通常是黑色)。
  3. 应用阈值:最后,遍历图像的每个像素点,根据上一步设定的阈值将每个像素点的颜色转换为黑色或白色。

实现

这里是用 svelte.js 为框架来实现的,主要看 JS 部分的代码即可。 并且为了简单起见,通过粘贴图片的方式来快速查看效果

<script>
  import {onMount} from 'svelte';
  import Tesseract from 'tesseract.js';
  import {spring} from 'svelte/motion';

  let imageInput;
  let ocrResult = '识别结果将在这里显示';

  let imageUrl = '';
  let binarizedImageUrl = '';
  let progress = spring(0, {
    stiffness: 0.1,
    damping: 0.8
  });
  onMount(() => {
    window.addEventListener('paste', handlePaste);
  });

  const handlePaste = (event) => {
    const items = (event.clipboardData || event.originalEvent.clipboardData).items;
    for (let item of items) {
      if (item.type.indexOf('image') === 0) {
        const blob = item.getAsFile();
        const reader = new FileReader();
        reader.onload = (e) => {
          imageUrl = e.target.result;
          currentProgress = 0; // 重置当前进度
          progress.set(0); // 重置进度条显示
        };
        reader.readAsDataURL(blob);
      }
    }
  }

  let currentProgress = 0;

  const fileChange = (e) => {
    const file = e.target.files[0];
    // 转成 url
    imageUrl = URL.createObjectURL(file);
  }

  const imgLoad = (e) => {
    const img = e.target;
    binarizeImage(img);
  }

  const binarizeImage = (image) => {
    const originalCanvas = document.createElement('canvas');
    const binarizedCanvas = document.createElement('canvas');
    document.body.appendChild(originalCanvas);
    document.body.appendChild(binarizedCanvas);
    const oCtx = originalCanvas.getContext('2d');
    const bCtx = binarizedCanvas.getContext('2d');

    originalCanvas.width = image.width;
    originalCanvas.height = image.height;
    binarizedCanvas.width = image.width;
    binarizedCanvas.height = image.height;

    oCtx.drawImage(image, 0, 0, image.width, image.height);
    const imageData = oCtx.getImageData(0, 0, image.width, image.height);
    const data = imageData.data;
    // 计算灰度平均值作为阈值
    let sumGray = 0;
    for (let i = 0; i < data.length; i += 4) {
      const gray = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
      sumGray += gray;
    }
    const avgGray = sumGray / (data.length / 4);
    // 应用动态阈值进行二值化
    for (let i = 0; i < data.length; i += 4) {
      const gray = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
      const color = (gray >= avgGray) ? 255 : 0;
      data[i] = data[i + 1] = data[i + 2] = color;
    }
    bCtx.putImageData(imageData, 0, 0);
    // 输出为 blob
    binarizedCanvas.toBlob((_blob => {
      binarizedImageUrl = URL.createObjectURL(_blob);
    }));
    originalCanvas.remove();
    binarizedCanvas.remove()
  }
</script>

<div class="container">
  <input type="file" bind:this={imageInput} on:change={fileChange} accept="image/*">
  <div class="img-preview">
    <div style="border: 1px red solid;min-width: 200px">
      <div>
        原图预览:
      </div>
      {#if imageUrl}
        <img class="img" src={imageUrl} on:load={imgLoad} alt="Pasted image"/>
      {/if}
    </div>
    <div style="border: 1px gray dashed; min-width: 200px">
      <div>
        二值化处理:
      </div>
      {#if binarizedImageUrl}
        <img class="img" src={binarizedImageUrl} alt="Pasted image"/>
      {/if}
    </div>
  </div>
</div>

<style>
 
  .container {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 20px;
  }

  img {
    max-width: 300px;
    height: auto;
    margin-top: 20px;
    border: 1px solid #ddd;
    border-radius: 4px;
    padding: 5px;
  }

  .img-preview {
    display: flex;
    justify-content: space-around;
    width: 500px;
    min-height: 100px;
    margin-bottom: 20px;
  }
</style>

效果

浏览器中对图像进行二值化处理

后记

在进行二值化处理时,噪点是一个常见问题。噪点可能会导致二值化后的图像出现不希望看到的黑点或白点,尤其是在图像的细节部分。为了改进二值化函数以减少噪点带来的影响,我们可以采取一些预处理和后处理策略。

如果这些操作需要在浏览器中进行,可以考虑使用 WebAssembly

预处理:降噪

在二值化之前,对图像进行降噪处理是一个好的做法。常见的降噪方法包括:

  • 高斯模糊(Gaussian Blur) :通过对图像应用高斯模糊,可以平滑图像的噪点。高斯模糊能够减少图像的细节级别,从而在不过度影响图像主要内容的情况下,降低噪点的影响。
  • 中值滤波(Median Filtering) :中值滤波是一种非线性的滤波技术,它将每个像素点的值替换为该点周围像素值的中位数。这种方法特别适合去除椒盐噪声(随机出现的黑点和白点)。

二值化处理

在进行了预处理之后,再对图像进行二值化处理。如果使用自适应阈值方法,可能会自然地减少一些噪点的影响,因为阈值会根据局部区域的光照条件自动调整。

后处理:噪点移除

即使在预处理之后,二值化的结果仍然可能包含一些噪点。这时,可以通过后处理步骤来进一步清理这些噪点:

  • 形态学操作(Morphological Operations) :形态学操作如腐蚀(Erosion)和膨胀(Dilation)对于去除小噪点非常有效。腐蚀操作可以去除小的白色噪点,而膨胀操作可以填补黑色噪点造成的小洞。
  • 开运算和闭运算:这两种形态学操作是腐蚀和膨胀的组合。开运算(先腐蚀后膨胀)可以去除小的对象,闭运算(先膨胀后腐蚀)可以填补对象中的小洞。这两种操作经常用来进一步清理二值化图像。