如何摆脱 bw 图像中的点并留下连接的像素?
How to get rid of points in bw image and be left with connected pixels?
我有如下图片(图片全黑,不要介意边框)
我只想留下(1 像素宽)线,并去掉所有点(其中一些实际上是几个像素)。
我知道这很容易,但我现在没有想法:(
我尝试过的:
用于打开的形态学操作(侵蚀 + 扩张)- 让我一无所有或保持原样
blob 检测 - 做 this 可能会在膨胀后工作,但它崩溃没有错误(windows 异常),我没有时间调试 opencv。
喜欢任何可行的想法。
我会使用自定义侵蚀函数来计算 3x3 window 中的非黑色像素,如果有,像素值不变,如果没有,像素将变为黑色。
在 C++ 中它看起来像(编辑:我添加了一个基于模糊的方法,它更快):
#include <opencv2/opencv.hpp>
#include <chrono>
int main(void)
{
cv::Mat input = cv::imread("salt.png", cv::IMREAD_GRAYSCALE);
imshow("input", input);
cv::Mat thresh;
// threshold the input just to be sure to get only white or black pixels.
cv::threshold(input, thresh, 128, 255, cv::THRESH_BINARY);
cv::Mat mask = cv::Mat::ones(3,3,CV_8UC1);
mask.at<unsigned char>(1,1)=0;
cv::imshow("mask", mask);
cv::Mat output1 = thresh;
cv::Mat output2 = thresh;
{ // first method : iterate over all pixels (very slow but didactic)
auto start = std::chrono::high_resolution_clock::now();
for(int r=1; r<thresh.rows-1; r++)
{
for(int c=1; c<thresh.cols-1; c++)
{
cv::Rect roi(c-1,r-1,3,3);
cv::Mat window = thresh(roi);
cv::Scalar val = cv::sum(window.mul(mask));
if(val[0]==0)
output1.at<unsigned char>(r,c) = 0;
}
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end-start);
std::cout << "iterating method: " << duration.count() << std::endl; // 116965µs
cv::imshow("output", output1);
}
{ // optimized method : use blur and threshold
cv::Mat blur, output2=thresh, thresh2;
auto start = std::chrono::high_resolution_clock::now();
cv::blur(thresh, blur, cv::Size(3,3));
cv::threshold(blur, thresh2, 60, 255, cv::THRESH_BINARY);
output2.mul(thresh2);
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end-start);
std::cout << "blur method: " << duration.count() << std::endl; // 378µs
cv::imshow("blur", output2);
}
auto diff = (output1 - output2) * 255;
std::cout << "diff norm: " << cv::norm(diff) << std::endl; // 0
while(1)
{
if(27 ==cv::waitKey(0))
break;
}
return 0;
}
输出:
我最后做了以下,只是为了完成它。我相信可以做一些更聪明的事情:
thread_defect_mask_clean = thread_defect_mask_noisy.copy()
ret, connected_components_labels = cv2.connectedComponents(thread_defect_mask_noisy.astype('uint8'), connectivity=8)
for label in range(1, ret):
label_count = np.count_nonzero(label == connected_components_labels)
if label_count < self._min_thread_defect_size:
thread_defect_mask_clean[label == connected_components_labels] = 0
这会计算每个连接组件的大小,并强制执行阈值(此处为 5)
[更新] - 没注意到它需要 1x1 像素线
就像你说的 - 我忽略了边框
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread("dumpster/eLIRj.png")
cv2.imwrite("dumpster/masking/1.png", img)
# convert to grayscale (single channel)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imwrite("dumpster/masking/2.png", gray)
# otsu thresholding
otsu = cv2.threshold(
gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
cv2.imwrite("dumpster/masking/3.png", otsu)
# erode with horizontal and vertical lines (2x1 pixels)
kernel = np.ones((2,1), dtype=np.uint8)
erosion = cv2.erode(otsu, kernel, iterations = 1)
kernel = np.ones((1,2), dtype=np.uint8)
erosion = erosion + cv2.erode(otsu, kernel, iterations = 1)
cv2.imwrite("dumpster/masking/4.png", erosion)
# dilate whats left with 3x3 kernel
kernel = np.ones((3,3), dtype=np.uint8)
dilated = cv2.dilate(erosion, kernel, iterations=1)
cv2.imwrite("dumpster/masking/5.png", dilated)
# create binary mask and apply it on thresholded, grayscale image
mask = dilated / 255
final = otsu * mask
cv2.imwrite("dumpster/masking/6.png", final)
原图:
膨胀:
决赛:
我有如下图片(图片全黑,不要介意边框)
我只想留下(1 像素宽)线,并去掉所有点(其中一些实际上是几个像素)。
我知道这很容易,但我现在没有想法:(
我尝试过的:
用于打开的形态学操作(侵蚀 + 扩张)- 让我一无所有或保持原样
blob 检测 - 做 this 可能会在膨胀后工作,但它崩溃没有错误(windows 异常),我没有时间调试 opencv。
喜欢任何可行的想法。
我会使用自定义侵蚀函数来计算 3x3 window 中的非黑色像素,如果有,像素值不变,如果没有,像素将变为黑色。
在 C++ 中它看起来像(编辑:我添加了一个基于模糊的方法,它更快):
#include <opencv2/opencv.hpp>
#include <chrono>
int main(void)
{
cv::Mat input = cv::imread("salt.png", cv::IMREAD_GRAYSCALE);
imshow("input", input);
cv::Mat thresh;
// threshold the input just to be sure to get only white or black pixels.
cv::threshold(input, thresh, 128, 255, cv::THRESH_BINARY);
cv::Mat mask = cv::Mat::ones(3,3,CV_8UC1);
mask.at<unsigned char>(1,1)=0;
cv::imshow("mask", mask);
cv::Mat output1 = thresh;
cv::Mat output2 = thresh;
{ // first method : iterate over all pixels (very slow but didactic)
auto start = std::chrono::high_resolution_clock::now();
for(int r=1; r<thresh.rows-1; r++)
{
for(int c=1; c<thresh.cols-1; c++)
{
cv::Rect roi(c-1,r-1,3,3);
cv::Mat window = thresh(roi);
cv::Scalar val = cv::sum(window.mul(mask));
if(val[0]==0)
output1.at<unsigned char>(r,c) = 0;
}
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end-start);
std::cout << "iterating method: " << duration.count() << std::endl; // 116965µs
cv::imshow("output", output1);
}
{ // optimized method : use blur and threshold
cv::Mat blur, output2=thresh, thresh2;
auto start = std::chrono::high_resolution_clock::now();
cv::blur(thresh, blur, cv::Size(3,3));
cv::threshold(blur, thresh2, 60, 255, cv::THRESH_BINARY);
output2.mul(thresh2);
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end-start);
std::cout << "blur method: " << duration.count() << std::endl; // 378µs
cv::imshow("blur", output2);
}
auto diff = (output1 - output2) * 255;
std::cout << "diff norm: " << cv::norm(diff) << std::endl; // 0
while(1)
{
if(27 ==cv::waitKey(0))
break;
}
return 0;
}
输出:
我最后做了以下,只是为了完成它。我相信可以做一些更聪明的事情:
thread_defect_mask_clean = thread_defect_mask_noisy.copy()
ret, connected_components_labels = cv2.connectedComponents(thread_defect_mask_noisy.astype('uint8'), connectivity=8)
for label in range(1, ret):
label_count = np.count_nonzero(label == connected_components_labels)
if label_count < self._min_thread_defect_size:
thread_defect_mask_clean[label == connected_components_labels] = 0
这会计算每个连接组件的大小,并强制执行阈值(此处为 5)
[更新] - 没注意到它需要 1x1 像素线
就像你说的 - 我忽略了边框
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread("dumpster/eLIRj.png")
cv2.imwrite("dumpster/masking/1.png", img)
# convert to grayscale (single channel)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imwrite("dumpster/masking/2.png", gray)
# otsu thresholding
otsu = cv2.threshold(
gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
cv2.imwrite("dumpster/masking/3.png", otsu)
# erode with horizontal and vertical lines (2x1 pixels)
kernel = np.ones((2,1), dtype=np.uint8)
erosion = cv2.erode(otsu, kernel, iterations = 1)
kernel = np.ones((1,2), dtype=np.uint8)
erosion = erosion + cv2.erode(otsu, kernel, iterations = 1)
cv2.imwrite("dumpster/masking/4.png", erosion)
# dilate whats left with 3x3 kernel
kernel = np.ones((3,3), dtype=np.uint8)
dilated = cv2.dilate(erosion, kernel, iterations=1)
cv2.imwrite("dumpster/masking/5.png", dilated)
# create binary mask and apply it on thresholded, grayscale image
mask = dilated / 255
final = otsu * mask
cv2.imwrite("dumpster/masking/6.png", final)
原图:
膨胀:
决赛: