第 11 章 形状表示与描述
教材P554页,第11.1题
重新定义链码的一个起始点,以便所得的数字序列形成一个最小值整数.请证明该编码与边界上的初始起点无关.
设链码
.另选一个初始起点,相当于循环位移该链码.设循环左移
位,得到链码
重新定义链码的一个起始点,相当于将该链码循环移动若干位.假设最小数字为
.
循环左移
位得到最小值
;由于最小值唯一,所以将
循环左移
位得到
.
求编码10176722335422 的归一化起始点.
编码 10176722335422 归一化后为01767223354221,起始点为原始链码的第2 个数字.
教材P554页,第11.2题
- 如11.1.2节中解释的那样,证明链码的一次差分会将该链码关于旋转归一化.
计算编码 0110233210332322111 的一次差分.
故编码
的一次差分为
;用循环首差链码计算的一次差分为
.
求图 1 中图形的链码、一阶差分、形状数和形状数的阶(起点在左上角,按照顺时针方向).
图 1 形状 表 1 答案 教材P556页,第11.26题
一家使用瓶子盛装各种工业化学品的公司在听说您成功地解决了图像处理问题后,雇用您来设计一种检测瓶子未装满的方法.瓶子在传送带上移动并通过自动装填和封盖机时的情形如下图所示.当液位低于瓶颈底部和瓶子肩部的中间点时,则认为瓶子未装满.瓶子横断面的侧面与倾斜面的区域定义为瓶子的肩部.瓶子在不断移动,但该公司有一个成像系统,该系统装备了一个前端照明闪光灯,可有效地停止瓶子的移动,所以您可以得到非常接近于这里显示的样例图像图 2.基于上述资料,请您提出一个检测未完全装满的瓶子的解决方案.清楚地陈述您所做的那些可能会影响到解决方案的所有假设.
图 2 原图 假设
- 瓶子位置和尺寸一致:假设瓶子不倾斜,垂直位置和尺寸大致一致.
- 图片拍摄条件一致:假设拍摄条件一致,瓶子在传送带上的位置和角度相对固定.
- 灰度对比明显:假设图像中瓶子和背景有明显的灰度对比,能够通过二值化处理有效区分.
- 传送带速度合适、恒定:假设传送带的速度恒定,瓶子在传送带上的运动速度和方向一致,瓶子中液体不会摇晃.
- 整齐排列:假设瓶子在传送带上排列整齐,水平位置和大小差异不大,便于排序和检测.
方案
- 图像预处理:读取灰度图像并进行二值化处理,将图像转换为黑白图,以区分瓶子和背景.
- 形态学操作:对二值化图像进行形态学开操作,去除图像噪声,确保瓶子轮廓清晰.
- 连通分量分析:使用连通分量分析技术检测瓶子位置,计算各瓶子的统计信息,如面积和位置.
- 液位检测:根据瓶子的高度和液面位置判断瓶子是否装满.如果液面低于设定的阈值,则认为瓶子未装满.
- 结果显示:在图像上标注检测结果,使用不同颜色框和编号表示检测结果.
实现
图像预处理与二值化
gray_image = cv2.imread("bottles-assembly-line.tif", 0) # 0 表示以灰度模式读取
BW_THRESHOLD = 170 # 二值化阈值
_, binary_image = cv2.threshold(gray_image, BW_THRESHOLD, 255, cv2.THRESH_BINARY)读取灰度图像,并使用阈值170进行二值化处理,将图像转换为黑白图.
形态学操作
kernel = np.ones((3, 7), np.uint8)
opened_image = cv2.morphologyEx(binary_image, cv2.MORPH_OPEN, kernel)使用形态学开操作去除噪声,确保瓶子的轮廓更加清晰.若不进行形态学操作,可能会导致连通分量分析不准确,如图 3 所示.
图 3 未进行形态学开操作,直接处理的结果 连通分量分析
num_labels, labels_im, stats, centroids = cv2.connectedComponentsWithStats(opened_image)使用
cv2.connectedComponentsWithStats获取连通分量及其统计信息.液位检测与结果显示
AREA_THRESHOLD = 900 # 面积阈值
components = [
stats[i]
for i in range(1, num_labels)
if stats[i, cv2.CC_STAT_AREA] > AREA_THRESHOLD
]
components.sort(key=lambda x: x[cv2.CC_STAT_LEFT]) # 按水平位置排序
for i, component in enumerate(components):
x, y, width, height, area = component
bottom = y + height
is_full = bottom < 100 # 液面阈值
color = (0, 255, 0) if is_full else (0, 0, 255) # 满的为绿色,未满的为红色
cv2.rectangle(output_image, (x, y), (x + width, y + height), color, 2)
cv2.putText(output_image, f"{i + 1}", (x, y + height + 25), cv2.FONT_HERSHEY_SIMPLEX, 0.75, color, 2, cv2.LINE_AA)根据连通分量的面积和位置筛选出有效的瓶子,计算液面位置,判断是否装满,并在图像上绘制矩形框和编号.液面低于100像素认为瓶子未装满,用红色框表示;否则用绿色框表示.
结果输出
最终输出图片结果如图 4 所示.
图 4 检测结果 控制台输出结果如下:
第 0 个连通分量的面积为 2607,高度为 65,液面位置的 y 坐标为 83,是满的
第 1 个连通分量的面积为 3132,高度为 63,液面位置的 y 坐标为 81,是满的
第 2 个连通分量的面积为 8606,高度为 119,液面位置的 y 坐标为 136,不是满的
第 3 个连通分量的面积为 3132,高度为 63,液面位置的 y 坐标为 81,是满的
第 4 个连通分量的面积为 1871,高度为 65,液面位置的 y 坐标为 83,是满的通过以上方法,能够有效检测传送带上未完全装满液体的瓶子,并在图像上直观展示检测结果.
附:程序完整代码
import cv2
import numpy as np
# 读取图像
gray_image = cv2.imread("bottles-assembly-line.tif", 0) # 0 表示以灰度模式读取
# 二值化
BW_THRESHOLD = 170 # 阈值
_, binary_image = cv2.threshold(gray_image, BW_THRESHOLD, 255, cv2.THRESH_BINARY)
# 对图片进行开操作
kernel = np.ones((3, 7), np.uint8)
opened_image = cv2.morphologyEx(
binary_image,
cv2.MORPH_OPEN, # = 2: 开操作
# cv2.MORPH_ERODE = 0:腐蚀
# MORPH_DILATE = 1:膨胀处理
# MORPH_CLOSE = 3:闭运算处理
kernel,
)
# 获取连通分量及其统计信息
num_labels, labels_im, stats, centroids = cv2.connectedComponentsWithStats(opened_image)
# 显示每个连通分量
output_image = np.zeros((gray_image.shape[0], gray_image.shape[1], 3), dtype=np.uint8)
# 为每个连通分量赋予随机颜色
for label in range(1, num_labels): # 从1开始,0是背景
mask = labels_im == label
output_image[mask] = np.random.randint(0, 255, 3)
AREA_THRESHOLD = 900 # 面积阈值
components = [
stats[i]
for i in range(1, num_labels)
# 保存面积大于阈值的连通分量
if stats[i, cv2.CC_STAT_AREA] > AREA_THRESHOLD
]
components.sort(
key=lambda x: x[cv2.CC_STAT_LEFT],
) # 按照左上角 x 的坐标排序,从左到右
for i, component in enumerate(components):
x, y, width, height, area = component
bottom = y + height # 液面位置的 y 坐标
is_full = bottom < 100
print(
f"第 {i+1} 个连通分量的面积为 {area},高度为 {height},液面位置的 y 坐标为 {bottom},"
+ ("是满的" if is_full else "不是满的")
)
# 绘制矩形框
color = (0, 255, 0) if is_full else (0, 0, 255)
cv2.rectangle(output_image, (x, y), (x + width, y + height), color, 2)
cv2.putText(
output_image,
f"{i + 1}",
(x, y + height + 25),
cv2.FONT_HERSHEY_SIMPLEX,
0.75,
color,
2,
cv2.LINE_AA,
)
# 显示结果
cv2.imshow("Output", output_image)
# 保存到文件
cv2.imwrite("output.png", output_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
评论