OpenCV - 去除图像中的噪声
OpenCV - Removal of noise in image
我这里有一张图片 table.. 在右侧的列中,背景充满了噪点
如何检测有噪点的区域?我只想在有噪声的部分应用某种滤镜,因为我需要对其进行 OCR,任何一种滤镜都会降低整体识别度
什么样的滤镜最能去除图像中的背景噪声?
如前所述,我需要对图像进行 OCR
据我所知,中值滤波器是降低噪声的最佳解决方案。我建议使用 3x3 window 的中值滤波器。请参见函数 cv::medianBlur()。
但在与 OCR 同时使用任何噪声过滤时要小心。会导致识别准确率下降。
我还建议尝试使用一对函数(cv::erode() 和 cv::dilate())。但我不确定它会是 cv::medianBlur() 和 window 3x3.
的最佳解决方案
我会选择中值模糊(可能是 5*5 内核)。
如果您打算对图像应用 OCR。我建议您执行以下操作:
- 使用中值滤波器过滤图像。
- 在过滤后的图像中查找轮廓,您将只得到文本轮廓(称它们为F)。
- 在原始图像中找到轮廓(称之为 O)。
- 隔离 O 中与 F.
中的任何轮廓相交的所有轮廓
更快的解决方案:
- 在原始图像中找到轮廓。
- 根据大小过滤它们。
尝试像这样对图像进行阈值处理。确保你的 src
是灰度的。此方法将仅保留强度介于 150 和 255 之间的像素。
threshold(src, output, 150, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
您可能想要反转图像,因为您正试图消除灰色像素。操作完成后,再次反转即可得到您想要的结果。
我在 OpenCV 中尝试了一些 filters/operations,它似乎工作得很好。
第 1 步:放大 图像 -
kernel = np.ones((5, 5), np.uint8)
cv2.dilate(img, kernel, iterations = 1)
如你所见,噪点消失了,但字符很亮,所以我腐蚀了图像。
第 2 步:腐蚀 图像 -
kernel = np.ones((5, 5), np.uint8)
cv2.erode(img, kernel, iterations = 1)
如您所见,噪音消失了,但其他列上的某些字符已损坏。我建议 运行 仅在嘈杂的列上执行这些操作。您可能想使用 HoughLines 来查找最后一列。然后您可以仅提取该列,运行 膨胀 + 腐蚀并将其替换为原始图像中的相应列。
另外,dilation + erosion 实际上是一个叫做closing的操作。您可以直接调用 -
cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
正如@Ermlg 所建议的,内核为 3 的 medianBlur 也能很好地工作。
cv2.medianBlur(img, 3)
备选步骤
如您所见,所有这些过滤器都有效,但如果您仅在噪声所在的部分实施这些过滤器会更好。为此,请使用以下内容:
edges = cv2.Canny(img, 50, 150, apertureSize = 3) // img is gray here
lines = cv2.HoughLinesP(edges, 1, np.pi / 180, 100, 1000, 50) // last two arguments are minimum line length and max gap between two lines respectively.
for line in lines:
for x1, y1, x2, y2 in line:
print x1, y1
// This gives the start coordinates for all the lines. You should take the x value which is between (0.75 * w, w) where w is the width of the entire image. This will give you essentially **(x1, y1) = (1896, 766)**
然后,你可以像这样只提取这部分:
extract = img[y1:h, x1:w] // w, h are width and height of the image
然后,在此图像中执行过滤器(中值或闭合)。去除噪声后,需要将这张过滤后的图像代替原图像中模糊的部分。
图像[y1:h, x1:w] = 中位数
这在 C++ 中很简单:
extract.copyTo(img, new Rect(x1, y1, w - x1, h - y1))
采用替代方法的最终结果
希望对您有所帮助!
我的解决方案是基于阈值法分 4 步得到结果图像。
- 阅读
OpenCV 3.2.0
的图片。
- 应用
GaussianBlur()
平滑图像,尤其是灰色区域。
- 遮住图像以将文本更改为白色,将其余部分更改为黑色。
- 将蒙版图像反转为白底黑字。
代码在Python 2.7
。它可以很容易地更改为C++
。
import numpy as np
import cv2
import matplotlib.pyplot as plt
%matplotlib inline
# read Danish doc image
img = cv2.imread('./imagesWhosebug/danish_invoice.png')
# apply GaussianBlur to smooth image
blur = cv2.GaussianBlur(img,(5,3), 1)
# threshhold gray region to white (255,255, 255) and sets the rest to black(0,0,0)
mask=cv2.inRange(blur,(0,0,0),(150,150,150))
# invert the image to have text black-in-white
res = 255 - mask
plt.figure(1)
plt.subplot(121), plt.imshow(img[:,:,::-1]), plt.title('original')
plt.subplot(122), plt.imshow(blur, cmap='gray'), plt.title('blurred')
plt.figure(2)
plt.subplot(121), plt.imshow(mask, cmap='gray'), plt.title('masked')
plt.subplot(122), plt.imshow(res, cmap='gray'), plt.title('result')
plt.show()
以下是代码绘制的图片,供参考。
这是 结果图像,2197 x 3218 像素。
如果您非常担心删除可能会影响 OCR 检测的像素。在不添加人工制品的情况下,尽可能保持原始状态。然后你应该创建一个 blob 过滤器。并删除任何小于 n 像素左右的斑点。
不打算编写代码,但我知道这很好用,因为我自己使用它,尽管我不使用 openCV(出于速度原因,我编写了自己的多线程 blobfilter)。抱歉,我不能在这里分享我的代码。只是描述如何去做。
结果:
如果处理时间不是问题,在这种情况下一种非常有效的方法是计算所有黑色连通分量,并删除那些小于几个像素的分量。它会删除所有嘈杂的点(除了那些接触有效组件的点),但保留所有字符和文档结构(行等)。
要使用的函数是 connectedComponentWithStats (before you probably need to produce the negative image, the threshold 函数,在这种情况下,THRESH_BINARY_INV
可以工作),在找到小连通分量的地方绘制白色矩形。
事实上,此方法可用于查找字符,这些字符定义为具有给定最小和最大尺寸的连通分量,并且纵横比在给定范围内。
我已经遇到过同样的问题并得到了最好的解决方案。
将源图像转换为 grayscale image
并应用 fastNlMeanDenoising
函数,然后应用 threshold
.
像这样 -
fastNlMeansDenoising(gray,dst,3.0,21,7);
threshold(dst,finaldst,150,255,THRESH_BINARY);
还可以根据背景噪声图像调整阈值。
例如- threshold(dst,finaldst,200,255,THRESH_BINARY);
注意 - 如果您的列线被删除...您可以从源图像中获取列线的掩码,并可以使用 AND、OR、XOR 等 BITWISE 运算将其应用于去噪结果图像。
我这里有一张图片 table.. 在右侧的列中,背景充满了噪点
如何检测有噪点的区域?我只想在有噪声的部分应用某种滤镜,因为我需要对其进行 OCR,任何一种滤镜都会降低整体识别度
什么样的滤镜最能去除图像中的背景噪声?
如前所述,我需要对图像进行 OCR
据我所知,中值滤波器是降低噪声的最佳解决方案。我建议使用 3x3 window 的中值滤波器。请参见函数 cv::medianBlur()。
但在与 OCR 同时使用任何噪声过滤时要小心。会导致识别准确率下降。
我还建议尝试使用一对函数(cv::erode() 和 cv::dilate())。但我不确定它会是 cv::medianBlur() 和 window 3x3.
的最佳解决方案我会选择中值模糊(可能是 5*5 内核)。
如果您打算对图像应用 OCR。我建议您执行以下操作:
- 使用中值滤波器过滤图像。
- 在过滤后的图像中查找轮廓,您将只得到文本轮廓(称它们为F)。
- 在原始图像中找到轮廓(称之为 O)。
- 隔离 O 中与 F. 中的任何轮廓相交的所有轮廓
更快的解决方案:
- 在原始图像中找到轮廓。
- 根据大小过滤它们。
尝试像这样对图像进行阈值处理。确保你的 src
是灰度的。此方法将仅保留强度介于 150 和 255 之间的像素。
threshold(src, output, 150, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
您可能想要反转图像,因为您正试图消除灰色像素。操作完成后,再次反转即可得到您想要的结果。
我在 OpenCV 中尝试了一些 filters/operations,它似乎工作得很好。
第 1 步:放大 图像 -
kernel = np.ones((5, 5), np.uint8)
cv2.dilate(img, kernel, iterations = 1)
如你所见,噪点消失了,但字符很亮,所以我腐蚀了图像。
第 2 步:腐蚀 图像 -
kernel = np.ones((5, 5), np.uint8)
cv2.erode(img, kernel, iterations = 1)
如您所见,噪音消失了,但其他列上的某些字符已损坏。我建议 运行 仅在嘈杂的列上执行这些操作。您可能想使用 HoughLines 来查找最后一列。然后您可以仅提取该列,运行 膨胀 + 腐蚀并将其替换为原始图像中的相应列。 另外,dilation + erosion 实际上是一个叫做closing的操作。您可以直接调用 -
cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
正如@Ermlg 所建议的,内核为 3 的 medianBlur 也能很好地工作。
cv2.medianBlur(img, 3)
备选步骤
如您所见,所有这些过滤器都有效,但如果您仅在噪声所在的部分实施这些过滤器会更好。为此,请使用以下内容:
edges = cv2.Canny(img, 50, 150, apertureSize = 3) // img is gray here
lines = cv2.HoughLinesP(edges, 1, np.pi / 180, 100, 1000, 50) // last two arguments are minimum line length and max gap between two lines respectively.
for line in lines:
for x1, y1, x2, y2 in line:
print x1, y1
// This gives the start coordinates for all the lines. You should take the x value which is between (0.75 * w, w) where w is the width of the entire image. This will give you essentially **(x1, y1) = (1896, 766)**
然后,你可以像这样只提取这部分:
extract = img[y1:h, x1:w] // w, h are width and height of the image
然后,在此图像中执行过滤器(中值或闭合)。去除噪声后,需要将这张过滤后的图像代替原图像中模糊的部分。 图像[y1:h, x1:w] = 中位数
这在 C++ 中很简单:
extract.copyTo(img, new Rect(x1, y1, w - x1, h - y1))
采用替代方法的最终结果
我的解决方案是基于阈值法分 4 步得到结果图像。
- 阅读
OpenCV 3.2.0
的图片。 - 应用
GaussianBlur()
平滑图像,尤其是灰色区域。 - 遮住图像以将文本更改为白色,将其余部分更改为黑色。
- 将蒙版图像反转为白底黑字。
代码在Python 2.7
。它可以很容易地更改为C++
。
import numpy as np
import cv2
import matplotlib.pyplot as plt
%matplotlib inline
# read Danish doc image
img = cv2.imread('./imagesWhosebug/danish_invoice.png')
# apply GaussianBlur to smooth image
blur = cv2.GaussianBlur(img,(5,3), 1)
# threshhold gray region to white (255,255, 255) and sets the rest to black(0,0,0)
mask=cv2.inRange(blur,(0,0,0),(150,150,150))
# invert the image to have text black-in-white
res = 255 - mask
plt.figure(1)
plt.subplot(121), plt.imshow(img[:,:,::-1]), plt.title('original')
plt.subplot(122), plt.imshow(blur, cmap='gray'), plt.title('blurred')
plt.figure(2)
plt.subplot(121), plt.imshow(mask, cmap='gray'), plt.title('masked')
plt.subplot(122), plt.imshow(res, cmap='gray'), plt.title('result')
plt.show()
以下是代码绘制的图片,供参考。
这是 结果图像,2197 x 3218 像素。
如果您非常担心删除可能会影响 OCR 检测的像素。在不添加人工制品的情况下,尽可能保持原始状态。然后你应该创建一个 blob 过滤器。并删除任何小于 n 像素左右的斑点。
不打算编写代码,但我知道这很好用,因为我自己使用它,尽管我不使用 openCV(出于速度原因,我编写了自己的多线程 blobfilter)。抱歉,我不能在这里分享我的代码。只是描述如何去做。
结果:
如果处理时间不是问题,在这种情况下一种非常有效的方法是计算所有黑色连通分量,并删除那些小于几个像素的分量。它会删除所有嘈杂的点(除了那些接触有效组件的点),但保留所有字符和文档结构(行等)。
要使用的函数是 connectedComponentWithStats (before you probably need to produce the negative image, the threshold 函数,在这种情况下,THRESH_BINARY_INV
可以工作),在找到小连通分量的地方绘制白色矩形。
事实上,此方法可用于查找字符,这些字符定义为具有给定最小和最大尺寸的连通分量,并且纵横比在给定范围内。
我已经遇到过同样的问题并得到了最好的解决方案。
将源图像转换为 grayscale image
并应用 fastNlMeanDenoising
函数,然后应用 threshold
.
像这样 -
fastNlMeansDenoising(gray,dst,3.0,21,7);
threshold(dst,finaldst,150,255,THRESH_BINARY);
还可以根据背景噪声图像调整阈值。
例如- threshold(dst,finaldst,200,255,THRESH_BINARY);
注意 - 如果您的列线被删除...您可以从源图像中获取列线的掩码,并可以使用 AND、OR、XOR 等 BITWISE 运算将其应用于去噪结果图像。