Python骨架长度检测
🏆本文收录于《CSDN问答解惑-专业版》专栏,主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
问题描述
Python骨架长度检测。我用的skimage进行骨架提取然后测量长度,我建立了一个系统界面,将图片显示在QHBoxLayout里面然后进行阈值分割和骨架提取,这两部都很正常,然后等到我长度测量的时候就一直报错
我检查了一下数组他是[0,1]的但是就是报这个错误,原代码如下
import os
import cv2
import numpy as np
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap, QImage
from PyQt5.QtWidgets import QMessageBox, QFileDialog, QLabel, QHBoxLayout, QTableWidgetItem
from lun_kuo_ce_liang import calculate_contour_properties
from skimage import measure
from skimage.morphology import skeletonize
from skimage import util
class LingJianPanDing:
def __init__(self, ui, main_window):
self.ui = ui
self.main_window = main_window
self.ui.pushButton_3.clicked.connect(self.open_defect_images)
self.ui.yuzhifenge.valueChanged.connect(self.update_threshold_from_slider)
self.ui.yuzhifengeshu.valueChanged.connect(self.update_slider_from_spinbox)
self.ui.kspd_2.clicked.connect(self.start_length_measurement)
self.ui.kspd_3.clicked.connect(self.generate_skeleton)
self.folder_path = ""
self.images = []
self.binarized_images = []
self.skeleton_images = []
def open_defect_images(self):
self.folder_path = QFileDialog.getExistingDirectory(self.main_window, "选择图片文件夹",
"C:/Users/fenghuajuedie/PycharmProjects/pythonProject1/heji/quexian")
if not self.folder_path:
QMessageBox.information(self.main_window, "提示", "没有选择任何文件夹")
return
try:
self.images = [f for f in os.listdir(self.folder_path) if f.endswith(('.png', '.jpg', '.jpeg', '.bmp'))]
if not self.images:
QMessageBox.information(self.main_window, "提示", "选中的文件夹没有图片")
return
layout = self.ui.label_image_4.layout()
if not layout:
layout = QHBoxLayout(self.ui.label_image_4)
self.ui.label_image_4.setLayout(layout)
while layout.count():
child = layout.takeAt(0)
if child.widget():
child.widget().deleteLater()
for image_name in self.images:
pixmap = QPixmap(os.path.join(self.folder_path, image_name))
label = QLabel()
label.setPixmap(pixmap)
layout.addWidget(label)
self.update_threshold_images()
except Exception as e:
QMessageBox.critical(self.main_window, "错误", f"无法加载图片: {str(e)}")
def update_threshold_from_slider(self):
self.ui.yuzhifengeshu.setValue(self.ui.yuzhifenge.value())
self.update_threshold_images()
def update_slider_from_spinbox(self):
self.ui.yuzhifenge.setValue(self.ui.yuzhifengeshu.value())
self.update_threshold_images()
def calculate_contour_similarity(self, imageA, imageB):
contoursA, _ = cv2.findContours(imageA, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contoursB, _ = cv2.findContours(imageB, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if len(contoursA) > 0 and len(contoursB) > 0:
match_score = abs(cv2.matchShapes(contoursA[0], contoursB[0], cv2.CONTOURS_MATCH_I1, 0.0))
return 1 - match_score
return 0.0
def update_threshold_images(self):
threshold = self.ui.yuzhifenge.value()
layout_4 = self.ui.horizontalLayout_4
layout_5 = self.ui.horizontalLayout_5
for layout in [layout_4, layout_5]:
while layout.count():
child = layout.takeAt(0)
if child.widget():
child.widget().deleteLater()
self.binarized_images = []
for image_index, image_name in enumerate(self.images):
image_path = os.path.join(self.folder_path, image_name)
image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
if image is not None:
_, binary_image = cv2.threshold(image, threshold, 255, cv2.THRESH_BINARY)
binary_image[binary_image == 255] = 1
self.binarized_images.append(binary_image)
pixmap_original = QPixmap(image_path)
label_original = QLabel()
label_original.setPixmap(pixmap_original)
layout_4.addWidget(label_original)
contour_image = np.zeros_like(binary_image, dtype=np.uint8)
contours = measure.find_contours(binary_image, 0.5)
for contour in contours:
for i in range(len(contour) - 1):
pt1 = (int(round(contour[i, 1])), int(round(contour[i, 0])))
pt2 = (int(round(contour[i + 1, 1])), int(round(contour[i + 1, 0])))
if 0 <= pt1[0] < image.shape[1] and 0 <= pt1[1] < image.shape[0]:
cv2.line(contour_image, pt1, pt2, (255,), 1)
height, width = contour_image.shape
qimage_contour = QImage(contour_image.data, width, height, width, QImage.Format_Grayscale8)
pixmap_contour = QPixmap.fromImage(qimage_contour).scaled(width, height, Qt.KeepAspectRatio)
label_contour = QLabel()
label_contour.setPixmap(pixmap_contour)
layout_5.addWidget(label_contour)
similarity = self.calculate_contour_similarity(image, contour_image)
current_row_count = self.ui.tableWidget_2.rowCount()
if image_index >= current_row_count:
self.ui.tableWidget_2.insertRow(current_row_count)
self.ui.tableWidget_2.setItem(image_index, 0, QTableWidgetItem(f"目标 {image_index + 1}"))
self.ui.tableWidget_2.setItem(image_index, 1, QTableWidgetItem("划痕"))
self.ui.tableWidget_2.setItem(image_index, 2, QTableWidgetItem(f"{similarity:.4f}"))
def generate_skeleton(self):
layout = self.ui.horizontalLayout_5
while layout.count():
child = layout.takeAt(0)
if child.widget():
child.widget().deleteLater()
self.skeleton_images = []
for index, binary_image in enumerate(self.binarized_images):
if binary_image.size == 0:
print(f"警告:第 {index + 1} 张骨架图像为空,跳过。")
continue
binary_image[binary_image == 255] = 1
skeleton = skeletonize(binary_image)
self.skeleton_images.append(skeleton)
display_image = np.zeros((*skeleton.shape, 3), dtype=np.uint8)
display_image[skeleton > 0] = [0, 0, 255]
height, width, _ = display_image.shape
qimage = QImage(display_image.data, width, height, 3 * width, QImage.Format_RGB888)
pixmap = QPixmap.fromImage(qimage).scaled(width, height, Qt.KeepAspectRatio)
label = QLabel()
label.setPixmap(pixmap)
layout.addWidget(label)
def start_length_measurement(self):
for index, skeleton_image in enumerate(self.skeleton_images):
if skeleton_image.size == 0:
print(f"警告:第 {index + 1} 张骨架图像为空,跳过。")
continue
binary_skeleton = (skeleton_image > 0).astype(np.uint8)
contours, _ = cv2.findContours(binary_skeleton, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if not contours:
print(f"第 {index + 1} 张图像未检测到轮廓")
continue
try:
calculate_contour_properties(binary_skeleton, self.ui.tableWidget_2)
except ValueError as ve:
print(f"处理第 {index + 1} 张骨架图时解包错误: {ve}")
continue
except Exception as e:
print(f"处理第 {index + 1} 张骨架图时出错: {e}")
continue
下面是calculate_contour_propertie的具体代码,请参考代码再帮帮我,另外我把图片以数组的形式打印出来了进行全局的监测,图像全都是数字的形式,但是在进行长度测量的时候还是在报错
def calculate_contour_properties(image, table_widget):
"""
计算轮廓的长宽信息并显示在表格中
"""
# Step 1: 提取轮廓
contours = measure.find_contours(image, 0.8)
if not contours:
print("未找到任何轮廓")
return
# Step 2: 获取中心线点
skeleton = skeletonize(image)
x, y = np.where(skeleton > 0)
centers = np.hstack((x.reshape(-1, 1), y.reshape(-1, 1)))
# Step 3: 法向量估计
normals = estimate_normals(centers, n=3) # n为近邻点数量,默认为3
# Step 4: 获取轮廓点
bl = contours[0] # 左轮廓点
br = contours[1] # 右轮廓点
bpoints = np.vstack((bl, br))
# Step 5: 计算宽度
_, widths = get_crack_ctrlpts(centers, normals, bpoints, hband=5, vband=2)
# Step 6: 将计算结果填入表格
for i, (length, width) in enumerate(widths):
table_widget.setItem(i, 0, QTableWidgetItem(str(i + 1))) # 目标编号
table_widget.setItem(i, 1, QTableWidgetItem("划痕")) # 目标类别
table_widget.setItem(i, 2, QTableWidgetItem(f"{length:.2f}")) # 长度
table_widget.setItem(i, 3, QTableWidgetItem(f"{width:.2f}")) # 宽度
print("轮廓计算完成并已更新到表格中。")
下面是带有图片数组信息的报错图片
解决方案
如下是上述问题的解决方案,仅供参考:
从你的描述和代码来看,骨架提取和轮廓检测步骤都是正常的,但在进行骨架长度测量时发生了错误。这个问题可能有几个原因,我会帮你分析可能的原因,并提供解决方案。
可能的原因分析:
-
数组格式问题:
你的代码中有些地方将二值图像数组中的255值替换为1。虽然这对二值图像处理是常见的做法,但有时可能会导致意外行为。特别是在使用skimage.measure.find_contours
或cv2.findContours
时,这些函数可能期待的输入数据类型和范围与实际不符。 -
轮廓检测到的点太少:
如果骨架图像中的轮廓点数过少,可能会导致后续处理出错,特别是在计算轮廓长宽时。 -
骨架图像可能存在问题:
骨架图像可能不连续或存在断裂,这可能会导致在calculate_contour_properties
函数中计算轮廓属性时产生异常。
解决方案:
-
确保输入数据的类型和格式:
在进行骨架提取和轮廓检测之前,确保图像的数组格式正确。例如,使用cv2.findContours
时,确保输入的数组是uint8
格式,并且值范围为0或1。你可以在骨架提取后打印出骨架图像的数组信息来验证格式。 -
处理骨架图像为空的情况:
在generate_skeleton
方法中,骨架图像可能为空或近似为空。你可以在处理前加入判断,确保图像中有足够多的骨架点。 -
添加异常处理:
在calculate_contour_properties
函数中添加更详细的异常处理,以捕获并记录导致出错的具体原因。例如,检查轮廓点的数量、检查骨架图像的有效性等。
更新后的代码
我为你修改了代码,增加了一些调试信息和防御性编程以避免潜在的问题。
def calculate_contour_properties(image, table_widget):
"""
计算轮廓的长宽信息并显示在表格中
"""
# Step 1: 提取轮廓
contours = measure.find_contours(image, 0.8)
if not contours or len(contours) < 2:
print("未找到任何轮廓或轮廓数量不足")
return
# Step 2: 获取中心线点
skeleton = skeletonize(image)
x, y = np.where(skeleton > 0)
if len(x) == 0 or len(y) == 0:
print("未检测到有效骨架点")
return
centers = np.hstack((x.reshape(-1, 1), y.reshape(-1, 1)))
# Step 3: 法向量估计
normals = estimate_normals(centers, n=3) # n为近邻点数量,默认为3
# Step 4: 获取轮廓点
try:
bl = contours[0] # 左轮廓点
br = contours[1] # 右轮廓点
bpoints = np.vstack((bl, br))
except IndexError as ie:
print(f"轮廓点处理异常: {ie}")
return
# Step 5: 计算宽度
try:
_, widths = get_crack_ctrlpts(centers, normals, bpoints, hband=5, vband=2)
except Exception as e:
print(f"宽度计算时出错: {e}")
return
# Step 6: 将计算结果填入表格
for i, (length, width) in enumerate(widths):
table_widget.setItem(i, 0, QTableWidgetItem(str(i + 1))) # 目标编号
table_widget.setItem(i, 1, QTableWidgetItem("划痕")) # 目标类别
table_widget.setItem(i, 2, QTableWidgetItem(f"{length:.2f}")) # 长度
table_widget.setItem(i, 3, QTableWidgetItem(f"{width:.2f}")) # 宽度
print("轮廓计算完成并已更新到表格中。")
调试建议:
-
打印调试信息:
在骨架图像生成后打印它的基本信息(如数组形状、数据类型、值范围等)来确认图像是否生成正确。 -
逐步调试:
可以使用小数据集或单一图像逐步调试整个流程,这样能更容易定位出错位置。
希望这些改进能够帮助你解决问题!如果问题依然存在,请提供具体的错误信息,我会继续协助你。
希望如上措施及解决方案能够帮到有需要的你。
PS:如若遇到采纳如下方案还是未解决的同学,希望不要抱怨&&急躁,毕竟影响因素众多,我写出来也是希望能够尽最大努力帮助到同类似问题的小伙伴,即把你未解决或者产生新Bug黏贴在评论区,我们大家一起来努力,一起帮你看看,可以不咯。
若有对当前Bug有与如下提供的方法不一致,有个不情之请,希望你能把你的新思路或新方法分享到评论区,一起学习,目的就是帮助更多所需要的同学,正所谓「赠人玫瑰,手留余香」。
☀️写在最后
如上问题有的来自我自身项目开发,有的收集网站,有的来自读者…如有侵权,立马删除。再者,针对此专栏中部分问题及其问题的解答思路或步骤等,存在少部分搜集于全网社区及人工智能问答等渠道,若最后实在是没能帮助到你,还望见谅!并非所有的解答都能解决每个人的问题,在此希望屏幕前的你能够给予宝贵的理解,而不是立刻指责或者抱怨!如果你有更优解,那建议你出教程写方案,一同学习!共同进步。
ok,以上就是我这期的Bug修复内容啦,如果还想查找更多解决方案,你可以看看我专门收集Bug及提供解决方案的专栏《CSDN问答解惑-专业版》,都是实战中碰到的Bug,希望对你有所帮助。到此,咱们下期拜拜。
码字不易,如果这篇文章对你有所帮助,帮忙给 bug菌 来个一键三连(关注、点赞、收藏) ,您的支持就是我坚持写作分享知识点传播技术的最大动力。
同时也推荐大家关注我的硬核公众号:「猿圈奇妙屋」 ;以第一手学习bug菌的首发干货,不仅能学习更多技术硬货,还可白嫖最新BAT大厂面试真题、4000G Pdf技术书籍、万份简历/PPT模板、技术文章Markdown文档等海量资料,你想要的我都有!
📣关于我
我是bug菌,CSDN | 掘金 | InfoQ | 51CTO | 华为云 | 阿里云 | 腾讯云 等社区博客专家,C站博客之星Top30,华为云2023年度十佳博主,掘金多年度人气作者Top40,掘金等各大社区平台签约作者,51CTO年度博主Top12,掘金/InfoQ/51CTO等社区优质创作者;全网粉丝合计 30w+;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试真题、4000G PDF电子书籍、简历模板等海量资料,你想要的我都有,关键是你不来拿哇。