使用OpenCV删除水平线。

9
我正在尝试从我女儿的画中删除水平线,但是一直无法做到完美。

我的方法是创建一个具有水平线的掩模(https://dev59.com/w6zla4cB1Zd3GeqPFPQx#57410471),然后从原始图像中删除该掩模(https://docs.opencv.org/3.3.1/df/d3d/tutorial_py_inpainting.html)。

如下图所示,这只能部分地去除水平线,并且还会产生一些扭曲,因为一些原始图像的水平线也出现在了掩模中。

非常感谢任何帮助改进此方法的人!

创建具有水平线的掩模

来自https://dev59.com/w6zla4cB1Zd3GeqPFPQx#57410471

import cv2
import numpy as np

img = cv2.imread("input.png", 0)

if len(img.shape) != 2:
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
else:
    gray = img

gray = cv2.bitwise_not(gray)
bw = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, 
cv2.THRESH_BINARY, 15, -2)

horizontal = np.copy(bw)

cols = horizontal.shape[1]
horizontal_size = cols // 30

horizontalStructure = cv2.getStructuringElement(cv2.MORPH_RECT, (horizontal_size, 1))

horizontal = cv2.erode(horizontal, horizontalStructure)
horizontal = cv2.dilate(horizontal, horizontalStructure)

cv2.imwrite("horizontal_lines_extracted.png", horizontal)

  

使用掩模去除水平线

来自 https://docs.opencv.org/3.3.1/df/d3d/tutorial_py_inpainting.html

import numpy as np
import cv2
mask = cv2.imread('horizontal_lines_extracted.png',0)
dst = cv2.inpaint(img,mask,3,cv2.INPAINT_TELEA)
cv2.imwrite("original_unmasked.png", dst)

图片

原始图片

Original picture

口罩

enter image description here

部分清理:

Partially cleaned


修复图像中的缺陷肯定是个好主意,但两种实现算法会产生一些“模糊”的东西。它们无法复制纹理。-- 你可能需要计算更精细的遮罩。你想要去除的那些线条相当细,而你不想去除的东西并不那么细。-- 如果你不需要完全自动化,你可以手动定义这些遮罩...在照片编辑器中打开扫描件,添加一个图层,在顶部绘制一个遮罩,并只保留你刚刚绘制的图层。 - Christoph Rackwitz
线条可能不是完全水平的。您尝试过使用形态学膨胀来加粗掩模中的线条吗? - fmw42
谢谢@nathancy,可惜似乎不起作用。检测到的线条图像主要是角色的头发... :( - Gorka
@ChristophRackwitz,我有很多这些图纸,因此完全自动化的流水线会更好。 - Gorka
@fmw42,我编辑了原始图像,使线条完全水平,但似乎并没有什么帮助。我是一个彻底的新手,我该如何加粗这些线条? - Gorka
形态学膨胀操作将加粗掩模边缘线。 - fmw42
4个回答

9

因此,我发现将绘图与纸张分开处理会导致更好的结果。在纸张上使用MORPH_CLOSE,在内部线条上使用MORPH_OPEN。希望你的女儿喜欢它 :)

img = cv2.imread(r'E:\Downloads\i0RDA.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# Remove horizontal lines
thresh = cv2.adaptiveThreshold(gray,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY_INV,81,17)
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (25,1))

# Using morph close to get lines outside the drawing
remove_horizontal = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, horizontal_kernel, iterations=3)
cnts = cv2.findContours(remove_horizontal, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
mask = np.zeros(gray.shape, np.uint8)
for c in cnts:
    cv2.drawContours(mask, [c], -1, (255,255,255),2)

# First inpaint
img_dst = cv2.inpaint(img, mask, 3, cv2.INPAINT_TELEA)

enter image description here

enter image description here

gray_dst = cv2.cvtColor(img_dst, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray_dst, 50, 150, apertureSize = 3)
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15,1))

# Using morph open to get lines inside the drawing
opening = cv2.morphologyEx(edges, cv2.MORPH_OPEN, horizontal_kernel)
cnts = cv2.findContours(opening, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
mask = np.uint8(img_dst)
mask = np.zeros(gray_dst.shape, np.uint8)
for c in cnts:
    cv2.drawContours(mask, [c], -1, (255,255,255),2)

# Second inpaint
img2_dst = cv2.inpaint(img_dst, mask, 3, cv2.INPAINT_TELEA)

enter image description here enter image description here


由于某些原因,我得到的结果是这个:https://i.stack.imgur.com/rLoLC.jpg。可能是因为我们使用了不同的输入图像?或者不同版本的OpenCV? - Ann Zen
很奇怪!我刚刚重新尝试了一下代码。一切看起来都完美无缺。让我们看看其他人的情况如何。 - Esraa Abdelmaksoud
1
@AnnZen 我可以确认我得到了与Esraa相同的输出。看起来非常不错:https://i.imgur.com/sPEWU6q.png。OpenCV版本:4.5.1,使用OP发布的输入图像。 - stateMachine
2
@esraa-abdelmaksoud 这太棒了!我可以毫无问题地为这幅画复制它。我的女儿对于有多少好心人在帮助这件事感到非常兴奋。 <3 - Gorka
3
太棒了!你让我的一天都美好起来了,Gorka!我从小就喜欢画画,所以我理解这对她有多重要。❤️ - Esraa Abdelmaksoud
显示剩余4条评论

3
  1. 获取边缘

  2. 膨胀以关闭线条

  3. 霍夫线变换检测直线

  4. 过滤掉非水平线

  5. 修复遮罩

  6. 获取边缘

gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray,50,150,apertureSize=3)

enter image description here

  1. 膨胀以关闭线条
img_dilation = cv2.dilate(edges, np.ones((3,3), np.uint8), iterations=1)

enter image description here

  1. 使用Hough线检测直线
lines = cv2.HoughLinesP(
            img_dilation, # Input edge image
            1, # Distance resolution in pixels
            np.pi/180, # Angle resolution in radians
            threshold=100, # Min number of votes for valid line
            minLineLength=5, # Min allowed length of line
            maxLineGap=10 # Max allowed gap between line for joining them
            )
  1. 使用斜率过滤非水平线。
lines_list = []

for points in lines:
    x1,y1,x2,y2=points[0]
    lines_list.append([(x1,y1),(x2,y2)])
    slope = ((y2-y1) / (x2-x1)) if (x2-x1) != 0 else np.inf
    
    if slope <= 1:
        cv2.line(mask,(x1,y1),(x2,y2), color=(255, 255, 255),thickness = 2)

  1. 填充遮罩
result = cv2.inpaint(image,mask,3,cv2.INPAINT_TELEA)

enter image description here

完整代码:

import cv2
import numpy as np
 
# Read image
image = cv2.imread('input.jpg')
mask = np.zeros((image.shape[0], image.shape[1]), dtype=np.uint8)

# Convert image to grayscale
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
 
# Use canny edge detection
edges = cv2.Canny(gray,50,150,apertureSize=3)

# Dilating
img_dilation = cv2.dilate(edges, np.ones((3,3), np.uint8), iterations=1)

 
# Apply HoughLinesP method to
# to directly obtain line end points
lines = cv2.HoughLinesP(
            img_dilation, # Input edge image
            1, # Distance resolution in pixels
            np.pi/180, # Angle resolution in radians
            threshold=100, # Min number of votes for valid line
            minLineLength=5, # Min allowed length of line
            maxLineGap=10 # Max allowed gap between line for joining them
            )

lines_list = []

for points in lines:
    x1,y1,x2,y2=points[0]
    lines_list.append([(x1,y1),(x2,y2)])
    slope = ((y2-y1) / (x2-x1)) if (x2-x1) != 0 else np.inf
    
    if slope <= 1:
        cv2.line(mask,(x1,y1),(x2,y2), color=(255, 255, 255),thickness = 2)
    
result = cv2.inpaint(image,mask,3,cv2.INPAINT_TELEA)

非常感谢您的帮助,已经接近成功了!我正在调整HoughLinesP()参数以避免绘制线条时的失真,但似乎找不到解决方法 :( - Gorka
1
经过进一步的调试,我可以看出每个图像可能需要一个特定的参数集。非常感谢@cyborg! - Gorka
我不明白。当我运行您的代码时,显示了 https://i.stack.imgur.com/MXOGt.jpg。您使用哪张图片作为输入图像? - Ann Zen
我在问题中使用了相同的图像。可能是不同的OpenCV版本。 - cyborg
哦!我刚意识到我使用了原帖发布的第二张图片! - Ann Zen

3

一种方法是定义一个HSV掩码,仅遮蔽所需的细节 (在这种情况下,它们是人物、闪光和签名)

获取正确的掩码后,在未被遮蔽的部分简单地对图像进行模糊处理。这是使用下限为0, 0, 160和上限为116, 30, 253的HSV掩码的结果:

enter image description here

以下是按此顺序处理图像:

(原始图像),(掩码),
(模糊图像),(结果遮罩图像):

enter image description here enter image description here

代码:

import cv2
import numpy as np

img = cv2.imread("input.jpg")
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
lower = np.array([0, 0, 160])
upper = np.array([116, 30, 253])
mask = cv2.inRange(img_hsv, lower, upper)
img_blurred = cv2.GaussianBlur(img, (31, 31), 10)
img_blurred[mask == 0] = img[mask == 0]

cv2.imshow("Result", img_blurred)
cv2.waitKey(0)

如您所见,人头发中的波浪线比预期要细。可以通过对二进制掩码进行几次腐蚀迭代来修复这个问题(只需在mask变量定义下的代码中添加mask = cv2.erode(mask, np.ones((3, 3)), 3):

import cv2
import numpy as np

img = cv2.imread("input.jpg")

img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

lower = np.array([0, 0, 160])
upper = np.array([116, 30, 253])

mask = cv2.inRange(img_hsv, lower, upper)
mask = cv2.erode(mask, np.ones((3, 3)), 3)
img_blurred = cv2.GaussianBlur(img, (31, 31), 10)
img_blurred[mask == 0] = img[mask == 0]

cv2.imshow("Result", img_blurred)
cv2.waitKey(0)

输出:

输入图片描述

再次按照相同的顺序进行处理:

输入图片描述 输入图片描述

我在这里添加了一篇文章(链接),其中包含您可以使用的程序来调整值并实时查看结果,以防您有其他要使用相同方法的图像。


请注意,这是使用原帖中部分处理过的图像作为输入。 - Ann Zen

2

这个答案的基础上,这里提供了一个程序,可以让你将同样的方法(遮盖图像所需的细节、对图像进行模糊处理,并用原始图像替换被遮盖部分)应用到任何图像上:

import cv2
import numpy as np

def show(imgs, win="Image", scale=1):
    imgs = [cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) \
            if len(img.shape) == 2 \
            else img for img in imgs]
    img_concat = np.concatenate(imgs, 1)
    h, w = img_concat.shape[:2]
    cv2.imshow(win, cv2.resize(img_concat, (int(w * scale), int(h * scale))))

d = {"Hue Min": (0, 179),
     "Hue Max": (116, 179),
     "Sat Min": (0, 255),
     "Sat Max": (30, 255),
     "Val Min": (160, 255),
     "Val Max": (253, 255),
     "k1": (31, 50),
     "k2": (31, 50),
     "sigma": (10, 20)}

img = cv2.imread(r"input.jpg")
cv2.namedWindow("Track Bars")
for i in d:
    cv2.createTrackbar(i, "Track Bars", *d[i], id)

img = cv2.imread("input.jpg")

img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
while True:
    h_min, h_max, s_min, s_max, v_min, v_max, k1, k2, s = (cv2.getTrackbarPos(i, "Track Bars") for i in d)
    lower = np.array([h_min, s_min, v_min])
    upper = np.array([h_max, s_max, v_max])
    mask = cv2.inRange(img_hsv, lower, upper)
    mask = cv2.erode(mask, np.ones((3, 3)))
    k1, k2 = k1 // 2 * 2 + 1, k2 // 2 * 2 + 1
    img_blurred = cv2.GaussianBlur(img, (k1, k2), s)
    result = img_blurred.copy()
    result[mask == 0] = img[mask == 0]
    show([img, mask], "Window 1", 0.5) # Show original image & mask
    show([img_blurred, result], "Window 2", 0.5) # Show blurred image & result
    if cv2.waitKey(1) & 0xFF == ord("q"):
        break

运行程序的演示:

在此输入图片描述


1
感谢 @ann-zen,这很有帮助!我喜欢这个互动式小部件。 - Gorka

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接