* check_blister.hdev * 这个例子演示了制药行业的一个应用。 * 检查自动填充的药片的内容。 * 第一张图片用来做定位模型,然后用这个作为参考图像重新排列后续图像。 * 采用blob分析方法对每个腔体的成分进行分割,最后根据几个形状特征进行分类。
* This example demonstrates an application from the pharmaceutical industry.
* The task is to check the content of automatically filled blisters.
* The first image (reference) is used to locate the chambers within a blister shape as a reference model,
* which is then used to realign the subsequent images along to this reference shape.
* Using blob analysis the content of each chamber is segmented and finally classified by a few shape features.
*
* check_blister.hdev
* 这个例子演示了制药行业的一个应用
* 检查自动填充的药片的内容。
* 第一张图片用来做定位模型,然后用这个作为参考图像重新排列后续图像。
* 采用blob分析方法对每个腔体的成分进行分割,最后根据几个形状特征进行分类。
*
dev_close_window ()
dev_update_off ()
read_image (ImageOrig, 'blister/blister_reference')
dev_open_window_fit_image (ImageOrig, 0, 0, -1, -1, WindowHandle)
set_display_font (WindowHandle, 14, 'mono', 'true', 'false')
dev_set_draw ('margin')
dev_set_line_width (3)
*
* In the first step, we create a pattern to cut out the chambers in the subsequent blister images easily.
* 在第一步中,我们创建了一个模式,以便在随后的水泡图像中轻松地切割出腔室。
*
* 访问多通道图像的一通道图像
access_channel (ImageOrig, Image1, 1)
*
* 1、获取图像中的ROI,并进行矫正
threshold (Image1, Region, 90, 255)
shape_trans (Region, Blister, 'convex')
orientation_region (Blister, Phi)
area_center (Blister, Area1, Row, Column)
vector_angle_to_rigid (Row, Column, Phi, Row, Column, 0, HomMat2D)
affine_trans_image (ImageOrig, Image2, HomMat2D, 'constant', 'false')
* 2、分割出每个药丸并作为数组存放
* gen_empty_obj (Chambers)
* for I := 0 to 4 by 1
* Row := 88 + I * 70
* for J := 0 to 2 by 1
* Column := 163 + J * 150
* gen_rectangle2 (Rectangle, Row, Column, 0, 64, 30)
* concat_obj (Chambers, Rectangle, Chambers)
* endfor
* endfor
* ******************************************************
*
* 2.1 改写
* 获取水泡区域
access_channel (Image2, Image2_1, 3)
threshold (Image2_1, Regions, 108, 200)
connection (Regions, ConnectedRegions1)
select_shape (ConnectedRegions1, SelectedRegions1, ['area','width'], 'and', [760,0], [5000,250])
union1 (SelectedRegions1, RegionUnion)
* 得到外界矩形
shape_trans (RegionUnion, RegionTrans1, 'rectangle1')
smallest_rectangle2 (RegionTrans1, Row2, Column2, Phi1, Length1, Length2)
* 把大矩形自定义为小矩形
* Length1, Length2是半宽和半高,所以要*2,宽是3个,高是5个
partition_rectangle (RegionTrans1, Chambers, Length1*2/3, Length2*2/5)
*
* ******************************************************
*
* 3、得到水泡区域的角度和中心坐标
affine_trans_region (Blister, Blister, HomMat2D, 'nearest_neighbor')
difference (Blister, Chambers, Pattern)
union1 (Chambers, ChambersUnion)
orientation_region (Blister, PhiRef)
PhiRef := rad(180) + PhiRef
area_center (Blister, Area2, RowRef, ColumnRef)
*
*
* Each image read will be aligned to this pattern and reduced to the area of interest, which is the chambers of the blister
* 读取的每个图像都将与这个模式对齐,并缩小到感兴趣的区域,即水泡的腔室
Count := 6
for Index := 1 to Count by 1
read_image (Image, 'blister/blister_' + Index$'02')
threshold (Image, Region, 90, 255)
connection (Region, ConnectedRegions)
select_shape (ConnectedRegions, SelectedRegions, 'area', 'and', 5000, 9999999)
shape_trans (SelectedRegions, RegionTrans, 'convex')
*
* Align pattern along blister of image
* 沿图像的水泡对齐图案
* 获取新读入图像的角度和中心坐标,然后暗模板的对应做变换到系统位置
orientation_region (RegionTrans, Phi)
area_center (RegionTrans, Area3, Row, Column)
vector_angle_to_rigid (Row, Column, Phi, RowRef, ColumnRef, PhiRef, HomMat2D)
affine_trans_image (Image, ImageAffinTrans, HomMat2D, 'constant', 'false')
*
* Segment pills
* 分割药片
reduce_domain (ImageAffinTrans, ChambersUnion, ImageReduced)
* 彩色图粉RGB三通道图
decompose3 (ImageReduced, ImageR, ImageG, ImageB)
* 通过局部均值和标准差分析对图像进行阈值。
* 在该程序中,先用7×7的掩膜在图像上逐像素游走,用原图中的当前像素和对应掩膜中49个像素的灰度均值对比,
* 找出暗(dark)的区域。当原图像素灰度比对应的掩膜灰度均值低(0.2,2)个灰阶时,该区域被分割出来。
* 如何通过StdDevScale和AbsThreshold来确定用于分割的阈值,具体方程可查看帮助文档。
* 方程为:
* 1、d(x,y)指的是遍历每个像素时,掩膜覆盖的那些像素块(本例中是4×4 = 16个像素)灰度的标准差;StdDevScale 是标准差因子。
* 2、当标准差因子StdDevScale ≥ 0 时,v(x,y) 取(StdDevScale ×标准差)和AbsThreshold 中较大的那个。
* 3、当标准差因子StdDevScale < 0 时,v(x,y) 取(StdDevScale ×标准差)和AbsThreshold 中较小的那个。实测发现,这里的比较大小是带符号比较,由于标准差是非负数,当StdDevScale < 0 时,(StdDevScale ×标准差)≤ 0恒成立。所以此时的取值就是(StdDevScale ×标准差)。
* 注意:在黑白过渡处,一般掩膜覆盖的像素的标准差较大,而在其他平缓的地方,标准差较小;因此最终采用的分割阈值随着掩膜在不断遍历像素的过程中,在(StdDevScale×标准差)和AbsThreshold 之间不断切换。
var_threshold (ImageB, Region, 7, 7, 0.2, 2, 'dark')
* 得到每个药丸区域
connection (Region, ConnectedRegions0)
closing_rectangle1 (ConnectedRegions0, ConnectedRegions, 3, 3)
fill_up (ConnectedRegions, RegionFillUp)
select_shape (RegionFillUp, SelectedRegions, 'area', 'and', 1000, 99999)
opening_circle (SelectedRegions, RegionOpening, 4.5)
connection (RegionOpening, ConnectedRegions)
select_shape (ConnectedRegions, SelectedRegions, 'area', 'and', 1000, 99999)
shape_trans (SelectedRegions, Pills, 'convex')
*
* Classify segmentation results and display statistics
* 对分割结果进行分类并显示统计数据
* 通过面积和最小灰度来判断是否有药丸
* 错误药丸:面积在(0,3800)最小灰度在[0,60);
* 缺失药丸:面积 = 0
count_obj (Chambers, Number)
gen_empty_obj (WrongPill)
gen_empty_obj (MissingPill)
for I := 1 to Number by 1
select_obj (Chambers, Chamber, I)
intersection (Chamber, Pills, Pill)
area_center (Pill, Area, Row1, Column1)
if (Area > 0)
min_max_gray (Pill, ImageB, 0, Min, Max, Range)
if (Area < 3800 or Min < 60)
concat_obj (WrongPill, Pill, WrongPill)
endif
else
concat_obj (MissingPill, Chamber, MissingPill)
endif
endfor
*
dev_clear_window ()
dev_display (ImageAffinTrans)
dev_set_color ('forest green')
count_obj (Pills, NumberP)
count_obj (WrongPill, NumberWP)
count_obj (MissingPill, NumberMP)
dev_display (Pills)
if (NumberMP > 0 or NumberWP > 0)
disp_message (WindowHandle, 'Not OK', 'window', 10, 10 + 600, 'red', 'true')
else
disp_message (WindowHandle, 'OK', 'window', 10, 10 + 600, 'forest green', 'true')
endif
disp_message (WindowHandle, '# correct pills: ' + (NumberP - NumberWP), 'window', 10, 10, 'black', 'true')
disp_message (WindowHandle, '# wrong pills : ' + NumberWP, 'window', 10 + 25, 10, 'black', 'true')
if (NumberWP > 0)
disp_message (WindowHandle, NumberWP, 'window', 10 + 25, 10 + 180, 'red', 'true')
endif
disp_message (WindowHandle, '# missing pills: ' + NumberMP, 'window', 10 + 50, 10, 'black', 'true')
if (NumberMP > 0)
disp_message (WindowHandle, NumberMP, 'window', 10 + 50, 10 + 180, 'red', 'true')
endif
dev_set_color ('red')
dev_display (WrongPill)
dev_display (MissingPill)
if (Index < Count)
disp_continue_message (WindowHandle, 'black', 'true')
endif
stop ()
endfor
|