OpenCV水平方向和竖直方向上的像素统计

本文探讨如何利用OpenCV统计图像水平和竖直方向的像素分布,通过示例展示像素和的分布曲线,揭示数字的结构信息,为图像识别提供依据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

对于图像,水平方向上的和竖直方向上的像素分布情况通常也会藏有许多信息,如图:


或许这还看不出些什么,再看:


这样就很明显了,竖直方向上连续像素不为0的区间肯定是数字的竖直位置,水平方向上的连续像素不为0的区间则有可能是数字的水平位置。

把统计数组画出来之后可以得到:


其中橙色的是竖直方向上的像素和的分布,蓝色的为水平方向的像素和的分布。

从曲线图中可以很明显的看出数字的分布,所以如果想要提取图像里面的代码可以这样做:

import numpy as np
import cv2
from matplotlib import pyplot as plt

def getImageHorizontalAndVerticalSum(ImageThre):
    rows, cols = ImageThre.shape
    horsum = []
    versum = []
    for i in range(cols):
        val = np.array(ImageThre[:, i]).sum()
        horsum.append(val)
        # print(val)
    for i in range(rows):
        val = np.array(ImageThre[i, :]).sum()
        versum.appen
<think>好的,我现在需要帮助用户解决使用Python处理二值图像,检测不规则条状横线,并计算平均宽度、最大宽度及长度的问题。首先,我得理清用户的真实需求。用户提到的是“不规则条状横线”,可能这些横线不是完全水平,而是有一定倾斜角度的,所以需要处理倾斜的情况。同时,用户希望提取像素级的宽度特征,这需要准确的轮廓检测测量方法。 首先,我应该考虑图像预处理。用户提供的图像是二值图,可能已经经过阈值处理,但可能需要进一步处理来去除噪声或连接断裂的部分。常用的方法包括形态学操作,比如膨胀腐蚀。例如,使用开运算来去除小的噪点,闭运算来连接线条的断裂部分。这里可能需要调整结构元素的大小形状,以适应不同的图像情况。 接下来是检测横线的部分。由于横线可能倾斜,传统的霍夫直线检测可能不够,因为它主要检测直线,而用户需要的是条状区域,可能包含一定宽度不规则形状。因此,轮廓检测可能是更好的方法。使用OpenCV的findContours函数来提取所有连通区域的轮廓。不过需要筛选出符合条件的轮廓,即横线状的区域。这里的筛选条件可能包括轮廓的长宽比、方向、面积等。例如,横线通常具有较大的长宽比,即长度远大于宽度。同时,可能需要计算轮廓的最小外接矩形或旋转矩形,以获取其角度,从而判断是否为倾斜的横线。 对于宽度长度的计算,一旦得到了每个轮廓的旋转矩形,可以通过旋转矩形的宽度高度来判断。但需要注意,旋转矩形的宽度高度会根据角度变化,需要确定哪个方向是长度,哪个是宽度。此外,用户可能需要的是线条的实际物理宽度,可能需要通过骨架化(骨架提取)来获取中心线,然后计算每个点的垂直方向的宽度。不过这可能比较复杂,尤其是在处理不规则形状时。 另一个方法是使用距离变换。对于每个条状区域,计算其骨架,然后对骨架上的每个点,计算到轮廓边界的距离,这样可以获得每个位置的宽度,进而统计平均最大宽度。不过这可能计算量较大,需要仔细实现。 步骤可能分解如下: 1. 图像预处理:确保二值图像干净,线条连通。 2. 轮廓检测:找到所有可能的条状区域。 3. 筛选轮廓:基于长宽比、面积、角度等。 4. 对每个符合条件的轮廓,计算其几何特征,如长度、平均宽度、最大宽度。 关于计算宽度,假设已经得到了轮廓,可以使用轮廓的最小外接矩形(旋转矩形)。例如,旋转矩形的短边可以作为平均宽度的估计。不过这可能不准确,特别是对于弯曲的条状区域。另一种方法是使用面积除以长度来估算平均宽度,假设区域为长方形,宽度=面积/长度。但这也需要准确的长度测量。 或者,可以沿条状区域的主轴方向(长度方向)进行采样,在每个采样点处测量垂直于主轴方向的宽度,然后取平均值最大值。这需要确定主轴方向,可能需要主成分分析(PCA)来找到主要方向,然后进行宽度测量。这可能比较复杂,但更准确。 关于实现,OpenCV中的函数如minAreaRect可以得到旋转矩形,得到中心点、尺寸角度。尺寸中的宽度高度可以根据角度来判断哪一个是长度,哪一个是宽度。例如,如果旋转矩形的角度接近0度或180度,说明是近似水平的,此时高度可能代表宽度,而长度是较长的边。但需要根据具体情况调整。 此外,用户可能需要处理多个条状区域,所以需要遍历所有轮廓,逐一处理。 现在,代码的大致步骤可能是: - 读取二值图像 - 预处理(形态学操作) - 查找轮廓 - 遍历轮廓,筛选符合条件的轮廓(长宽比、面积阈值) - 对每个轮廓,计算旋转矩形,得到长度宽度 - 或者,计算面积长度,得到平均宽度 - 统计每个轮廓的特征 但用户可能需要更精确的宽度测量,比如沿线条的不同位置测量宽度,取平均最大。这可能需要更复杂的方法,比如使用距离变换或骨架化结合轮廓距离。 例如,对于每个轮廓,可以先提取其骨架,然后对于骨架上的每个点,计算到轮廓边界的距离,这个距离的两倍就是该位置的宽度。然后对所有点的宽度取平均最大值。这种方法更准确,但实现起来更复杂。 OpenCV中可以使用距离变换骨架化来实现。具体步骤可能包括: 1. 对二值图像中的每个连通区域(轮廓)进行骨架化,得到中心线。 2. 对原始二值图像应用距离变换,得到每个像素到最近背景像素的距离。 3. 沿着骨架的每个点,取距离变换的值,乘以2(因为距离是到边界的距离,宽度是两倍的距离)。 4. 统计这些值得到平均宽度最大宽度。 关于骨架化,OpenCV没有直接的函数,但可以通过形态学操作(如细化算法)实现,或者使用第三方库如scikit-image中的skeletonize函数。 另外,用户可能需要计算条状区域的长度,可以使用骨架的长度。例如,骨架的像素数可以作为长度的近似,或者通过拟合曲线来计算实际长度。 综上,整个流程可能包括以下步骤: 1. 图像预处理(形态学闭运算连接断裂,开运算去噪) 2. 轮廓检测 3. 筛选轮廓(面积、长宽比) 4. 对每个筛选后的轮廓: a. 提取对应的二值区域 b. 骨架化得到中心线 c. 应用距离变换,计算每个骨架点的宽度 d. 计算平均宽度、最大宽度 e. 计算骨架的长度(像素数或实际长度) 5. 输出结果 现在需要考虑如何用Python代码实现这些步骤。可能的代码结构如下: - 使用OpenCV读取图像,转为二值。 - 应用形态学操作。 - 查找轮廓。 - 遍历轮廓,计算面积、长宽比等,筛选出符合条件的。 - 对每个轮廓的掩码,进行骨架化。 - 计算距离变换,并在骨架点上取样。 - 统计宽度数据。 - 计算骨架长度。 关于骨架化,如果使用scikit-image的话,可以更简单,但用户可能需要纯OpenCV的方法。或者使用迭代腐蚀的方法实现骨架化,但这可能效率较低。 另外,距离变换可以使用cv2.distanceTransform函数,计算每个前景像素到最近背景像素的距离。对于条状区域,中心线的距离值最大,所以沿着骨架的点,其距离值乘以2就是该位置的宽度。 因此,具体步骤可能为: 对于每个轮廓的掩码: 1. 创建该轮廓的掩码图像(单连通域)。 2. 计算距离变换。 3. 骨架化该掩码图像。 4. 获取骨架上的所有点。 5. 对这些点,从距离变换图像中取出对应的值,乘以2得到宽度。 6. 平均这些宽度即为平均宽度,最大值即为最大宽度。 7. 骨架上的像素数目可以作为长度的像素值,或者通过拟合曲线计算实际几何长度(例如,使用旋转矩形的长度)。 关于长度,如果轮廓的最小外接矩形长度为L,可能更直接。或者,骨架的长度可以通过计算所有骨架点之间的欧氏距离之,但这比较复杂。可能更简单的是使用旋转矩形的长度作为估计。 现在,代码实现的难点在于骨架化距离变换的结合。需要确保骨架准确地表示条状区域的中心线,且距离变换正确。 另外,需要注意处理多个连通域的情况,确保每个条状区域被单独处理。 现在,可能需要整合这些步骤到Python代码中。例如: import cv2 import numpy as np from skimage.morphology import skeletonize # 如果使用scikit-image的骨架化 # 读取图像,二值化 image = cv2.imread('binary_image.png', 0) _, binary = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY) # 预处理:形态学操作 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3)) closed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel) opened = cv2.morphologyEx(closed, cv2.MORPH_OPEN, kernel) # 查找轮廓 contours, _ = cv2.findContours(opened, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: # 筛选轮廓:面积、长宽比等 area = cv2.contourArea(cnt) if area < 100: # 假设面积阈值 continue rect = cv2.minAreaRect(cnt) (x, y), (w, h), angle = rect aspect_ratio = max(w, h) / min(w, h) if aspect_ratio < 5: # 假设长宽比大于5为条状 continue # 创建该轮廓的掩码 mask = np.zeros_like(opened) cv2.drawContours(mask, [cnt], 0, 255, -1) # 计算距离变换 dist_transform = cv2.distanceTransform(mask, cv2.DIST_L2, 3) # 骨架化 skeleton = skeletonize(mask // 255) # scikit-image需要0-1的数组 skeleton_points = np.argwhere(skeleton) # 提取距离值 widths = 2 * dist_transform[skeleton_points[:,0], skeleton_points[:,1]] avg_width = np.mean(widths) max_width = np.max(widths) # 计算长度:这里使用旋转矩形的长度 length = max(w, h) print(f"条状区域:平均宽度{avg_width:.2f},最大宽度{max_width:.2f},长度{length:.2f}") 需要注意的是,这里使用了scikit-image的skeletonize函数,可能需要安装该库。如果用户希望仅使用OpenCV,可能需要自己实现骨架化,或者使用其他方法。例如,通过迭代腐蚀直到骨架形成,但这样代码会更复杂。 另外,对于长宽比的筛选,可能需要根据实际图像调整阈值。比如,条状区域的长宽比可能更高,如10:1以上。 关于长度计算,如果使用旋转矩形的长边,可能在倾斜情况下更准确。或者,使用轮廓的周长或其他方法,但周长可能不适合条状长度。 此外,用户可能需要考虑条状区域的倾斜角度,从而调整宽度计算的方向。例如,如果线条倾斜,垂直于其主轴方向的宽度才是正确的测量。这可能需要更复杂的几何计算,比如在每个骨架点处,沿着法线方向测量到边界的距离。 这可能涉及到在距离变换的基础上,结合方向信息。不过这会大大增加复杂度。可能更简单的方法是假设距离变换的最大值位于中心线,而该处的距离即为半宽,因此乘以2是总宽度。这在条状区域宽度变化不大的情况下是合理的,但对于宽度变化较大的区域可能不够准确。 综上,上述代码可能是一个初步的实现,但可能需要根据具体图像进行调整优化。例如,形态学操作的核大小,筛选轮廓的条件,以及骨架化距离变换的参数。 最后,用户可能需要将结果可视化,比如在原图上标注检测到的条状区域,并显示其宽度长度信息。这可以通过绘制轮廓或旋转矩形来实现。 总结步骤: 1. 预处理图像以连接断裂去除噪声。 2. 检测轮廓并筛选出符合条件的条状区域。 3. 对每个区域计算距离变换骨架。 4. 沿骨架提取宽度数据。 5. 统计特征并输出。 用户可能需要根据实际图像调整参数,比如形态学核的大小、面积阈值、长宽比阈值等。</think>### 分步解决方案 #### 1. 图像预处理 对输入的二值图像进行形态学操作,修复断裂线段并去除噪点: - 闭运算填充小孔洞:$$M_{closed} = (I \oplus B) \ominus B$$ - 开运算消除细小噪点:$$M_{opened} = (I \ominus B) \oplus B$$ ```python kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3)) closed = cv2.morphologyEx(binary_img, cv2.MORPH_CLOSE, kernel, iterations=2) processed_img = cv2.morphologyEx(closed, cv2.MORPH_OPEN, kernel, iterations=1) ``` #### 2. 轮廓检测与筛选 使用OpenCV查找连通域,通过几何特征筛选条状区域: ```python contours, _ = cv2.findContours(processed_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) valid_contours = [] for cnt in contours: area = cv2.contourArea(cnt) _, (w, h), _ = cv2.minAreaRect(cnt) if area > 100 and max(w,h)/min(w,h) > 8: # 面积阈值长宽比阈值 valid_contours.append(cnt) ``` #### 3. 特征提取技术 对每个有效轮廓进行骨架化距离变换计算: ```python for cnt in valid_contours: # 创建单连通域掩码 mask = np.zeros_like(processed_img) cv2.drawContours(mask, [cnt], 0, 255, -1) # 计算距离变换 dist_transform = cv2.distanceTransform(mask, cv2.DIST_L2, 3) # 骨架化处理 skeleton = skeletonize(mask//255) # 使用scikit-image库 skeleton_points = np.argwhere(skeleton) # 提取宽度特征 widths = 2 * dist_transform[skeleton[:,0], skeleton[:,1]] avg_width = np.mean(widths) max_width = np.max(widths) # 计算长度特征 length_pixels = len(skeleton_points) # 骨架像素数作为长度 ``` #### 4. 完整实现代码 ```python import cv2 import numpy as np from skimage.morphology import skeletonize def analyze_binary_image(image_path): # 读取并预处理 img = cv2.imread(image_path, 0) _, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY) # 形态学操作 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3)) closed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel, iterations=2) processed = cv2.morphologyEx(closed, cv2.MORPH_OPEN, kernel, iterations=1) # 轮廓检测 contours, _ = cv2.findContours(processed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) results = [] for cnt in contours: # 几何筛选 area = cv2.contourArea(cnt) if area < 100: continue rect = cv2.minAreaRect(cnt) w, h = rect[1] if max(w,h)/min(w,h) < 8: continue # 创建独立掩码 mask = np.zeros_like(processed) cv2.drawContours(mask, [cnt], 0, 255, -1) # 距离变换 dist = cv2.distanceTransform(mask, cv2.DIST_L2, 3) # 骨架化 skeleton = skeletonize(mask//255) skel_points = np.argwhere(skeleton) # 计算特征 if len(skel_points) == 0: continue widths = 2 * dist[skel_points[:,0], skel_points[:,1]] avg_w = np.mean(widths) max_w = np.max(widths) length = len(skel_points) results.append({ "average_width": avg_w, "max_width": max_w, "length_pixels": length }) return results ``` #### 5. 可视化增强 建议使用以下方法验证结果: ```python # 在原始图像绘制检测结果 output_img = cv2.cvtColor(binary, cv2.COLOR_GRAY2BGR) for cnt in valid_contours: cv2.drawContours(output_img, [cnt], 0, (0,255,0), 1) rect = cv2.minAreaRect(cnt) box = cv2.boxPoints(rect) box = np.int0(box) cv2.drawContours(output_img, [box], 0, (255,0,0), 1) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值