使用 Opencv 去除水平线
Remove horizontal lines with Open CV
我正在尝试从我女儿的画中删除水平线,但不太正确。
我采用的方法是创建一个带有水平线的蒙版 () and then removing that mask from the original (https://docs.opencv.org/3.3.1/df/d3d/tutorial_py_inpainting.html)。
正如您在下面的图片中看到的,这只会部分删除水平线,并且还会产生一些扭曲,因为一些原始绘图水平线也会出现在蒙版中。
如果能帮助改进此方法,我们将不胜感激!
创建带有水平线的蒙版
来自
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)
图片
原图
面具
部分清理:
获取边
扩张以关闭线条
Hough线检测线
过滤掉非水平线
修补蒙版
获得优势
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray,50,150,apertureSize=3)
- 扩张以关闭线条
img_dilation = cv2.dilate(edges, np.ones((3,3), np.uint8), iterations=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
)
- 使用斜率过滤掉非水平线。
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)
完整代码:
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)
所以,我发现将绘图与纸张分开进行会得到更好的结果。我使用 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)
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)
一种方法是定义一个仅屏蔽掉所需细节的 HSV 掩码(在这种情况下,它们是人物、火花和签名)。
获得合适的遮罩后,只需对未遮罩部分的图像进行模糊处理即可。这是下限 0, 0, 160
和上限 116, 30, 253
:
的 HSV 掩码的结果
下面是图片的处理,顺序是:
(原始图像),(蒙版),
(模糊图像),(生成蒙版图像):
代码:
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 = cv2.erode(mask, np.ones((3, 3)), 3)
添加到 mask
变量定义下的代码中):
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)
输出:
再次按相同顺序处理:
我添加了一个 post 来包含您可以用来调整值并在 real-time 中查看结果的程序,以防您有其他图像想用同样的方法。
答案的扩展,这里是允许您应用相同方法的程序 (屏蔽图像所需的细节,对图像应用模糊,并将图像的 masked-out 部分替换为原始图像) 到任何图像上:
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
运行程序演示:
我正在尝试从我女儿的画中删除水平线,但不太正确。
我采用的方法是创建一个带有水平线的蒙版 (
正如您在下面的图片中看到的,这只会部分删除水平线,并且还会产生一些扭曲,因为一些原始绘图水平线也会出现在蒙版中。
如果能帮助改进此方法,我们将不胜感激!
创建带有水平线的蒙版
来自
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)
图片
原图
面具
部分清理:
获取边
扩张以关闭线条
Hough线检测线
过滤掉非水平线
修补蒙版
获得优势
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray,50,150,apertureSize=3)
- 扩张以关闭线条
img_dilation = cv2.dilate(edges, np.ones((3,3), np.uint8), iterations=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
)
- 使用斜率过滤掉非水平线。
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)
完整代码:
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)
所以,我发现将绘图与纸张分开进行会得到更好的结果。我使用 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)
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)
一种方法是定义一个仅屏蔽掉所需细节的 HSV 掩码(在这种情况下,它们是人物、火花和签名)。
获得合适的遮罩后,只需对未遮罩部分的图像进行模糊处理即可。这是下限 0, 0, 160
和上限 116, 30, 253
:
下面是图片的处理,顺序是:
(原始图像),(蒙版),
(模糊图像),(生成蒙版图像):
代码:
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 = cv2.erode(mask, np.ones((3, 3)), 3)
添加到 mask
变量定义下的代码中):
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)
输出:
再次按相同顺序处理:
我添加了一个 post
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
运行程序演示: