opencv 实战案例 (二)

论坛 期权论坛 编程之家     
选择匿名的用户   2021-5-31 19:14   11   0

目录

1、寻找英语试卷填空题的下划线任务(形态学操作+HoughLines)

2、指定目标提取任务 (二值分割 + 形态学 + 横纵比计算)

3、药片分割任务 (距离变换+分水岭算法)

代码基于:opencv-python (3.4.0.12)

1. 直线检测

需求:寻找英语试卷填空题的下划线,这个对后期的切图与自动识别都比较重要。 实现思路:通过图像形态学操作来寻找直线,霍夫获取位置信息与显示

import cv2
from PIL import Image
import numpy as np
raw_ = cv2.imread("2_1.jpg")
raw = cv2.cvtColor(raw_,cv2.COLOR_BGR2RGB)
raw_gray = cv2.cvtColor(raw,cv2.COLOR_RGB2GRAY)
image1 = Image.fromarray(raw.astype('uint8')).convert('RGB')
print("下面是原图")
display(image1)
image2 = Image.fromarray(raw_gray.astype('uint8')).convert('RGB')
print("下面是灰度图")
display(image2)
ret,thresh2 = cv2.threshold(raw_gray,0,255,cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
image3 = Image.fromarray(thresh2.astype('uint8')).convert('RGB')
print("下面是二值图")
display(image3)
# 一个开操作
# 开操作 = 腐蚀+膨胀 ,输入图像 + 结构元素
# 作用:用来消除小物体、平滑较大物体的边界的同时并不明显改变其面积,提取水平或竖直的线
op_kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(60,1))
#注意这个60*1的核,就是提取水平直线用的
opening = cv2.morphologyEx(thresh2, cv2.MORPH_OPEN, op_kernel)
#膨胀,让直线更加明显
image4 = Image.fromarray(opening.astype('uint8')).convert('RGB')
print("下面是做了开操作")
display(image4)
di_kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(3,3))
dilate = cv2.dilate(opening,di_kernel)
#再次膨胀会让直线更粗
image5 = Image.fromarray(dilate.astype('uint8')).convert('RGB')
print("下面是再次膨胀图")
display(image5)
#霍夫变换检测直线
lines = cv2.HoughLinesP(dilate,  #输入图像
                       1,       #累加器分辨率
                       np.pi/180.0,#角度分辨率
                       30,      #确定直线之前收到的最小投票数
                       minLineLength=20, #直线的最小长度
                       maxLineGap=0)     #直线上允许的最大缝隙
line = lines[:,0,:]
for x1,y1,x2,y2 in line[:]:
    cv2.line(raw,  #输入图像
             (x1,y1),  #起点
             (x2,y2),  #终点
             (255,0,0), #颜色
             2)        #宽度
image1 = Image.fromarray(raw.astype('uint8')).convert('RGB')
print("下面是霍夫直线检测图")
display(image1)
#效果还不错,就不用过滤结果了
下面是原图

下面是灰度图

下面是二值图

下面是做了开操作

下面是再次膨胀图

下面是霍夫直线检测图

2. 对象提取

需求:对图像中对象(圆形)进行提取,获取这样的对象,去掉其它干扰和非目标对象。并获取圆形的面积和周长。

#实现思路:二值分割 + 形态学 + 横纵比计算。

#步骤: - 找到对象的轮廓 - 通过面积、横纵比过滤点其它不需要的对象

import cv2
from PIL import Image
import numpy as np
raw_ = cv2.imread("2_2.jpg")
raw = cv2.cvtColor(raw_,cv2.COLOR_BGR2RGB)
raw_gray = cv2.cvtColor(raw,cv2.COLOR_RGB2GRAY)
image1 = Image.fromarray(raw.astype('uint8')).convert('RGB')
print("下面是原图")
display(image1)
image2 = Image.fromarray(raw_gray.astype('uint8')).convert('RGB')
print("下面是灰度图")
display(image2)
#  注意这个THRESH_BINARY
#  cv2.THRESH_BINARY(黑白二值)
#  cv2.THRESH_BINARY_INV(黑白二值反转)
#  cv2.THRESH_TRUNC (得到的图像为多像素值)
ret,thresh2 = cv2.threshold(raw_gray,0,255,cv2.THRESH_BINARY + cv2.THRESH_OTSU)
image3 = Image.fromarray(thresh2.astype('uint8')).convert('RGB')
print("下面是二值图")
display(image3)
下面是原图

下面是灰度图

下面是二值图

#形态学操作 闭操作链接里面的洞 开操作去掉小的洞
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(3,3))
#先做闭操作
op_close = cv2.morphologyEx(thresh2, cv2.MORPH_CLOSE, kernel)
image4 = Image.fromarray(op_close.astype('uint8')).convert('RGB')
print("下面是闭操作图")
display(image4)
#再做开操作
op_open = cv2.morphologyEx(op_close, cv2.MORPH_OPEN, kernel)
image5 = Image.fromarray(op_open.astype('uint8')).convert('RGB')
print("下面是开操作图")
display(image5)
#临时合成一个三通道显示检测结果用
draw_raw = cv2.merge([op_open,op_open,op_open])
draw_raw_2 = cv2.merge([op_open,op_open,op_open])
下面是闭操作图

下面是开操作图

#寻找轮廓
_,counts,hierarchy = cv2.findContours(op_open,  #输入图像
                                    cv2.RETR_TREE, #轮廓返回模式
                                    cv2.CHAIN_APPROX_SIMPLE) #发现方法
cv2.drawContours(draw_raw, #一个三通道图像
             counts, #全部发现的轮廓
             -1,    #轮廓索引号 -1就是绘制所有轮廓
             (255,0,0),  #轮廓颜色
             2)      #宽度
image6 = Image.fromarray(draw_raw.astype('uint8')).convert('RGB')
print("下面是轮廓检测图")
display(image6)
下面是轮廓检测图

bucket = []
for index in range(len(counts)):
    #不一定所有参数都有用,视情况注释掉没用到的
    min_rect = cv2.minAreaRect(counts[index])      #最小外接矩形
    box_min_rect = cv2.boxPoints(min_rect)         #最小外接矩形4个点的坐标值
    lenth = cv2.arcLength(counts[index],True)     #周长 True对应闭合 False不闭合
    area = cv2.contourArea(counts[index])          #轮廓所包含的面积
    rect = cv2.boundingRect(counts[index])         #横平竖直的外接矩形
    #rect = x,y,w,h  外接矩形自带宽和高
    w = rect[2]
    h = rect[3]
    #print(index,area,lenth)
    #过滤掉面积小于100像素的 宽高比大于1.1 小于0.9的 因为圆的宽高比接近1:1
    if area <= 100 or w/h > 1.1 or w/h < 0.9 :
        bucket.append(index)
bucket = bucket[::-1]
for i in bucket:
    counts.pop(i)
#不确定最后是不是只剩下一个 还是使用循环
for index in range(len(counts)):
    rect = cv2.boundingRect(counts[index])         #横平竖直的外接矩形
    x0 = int(rect[0] + rect[2]/2)
    y0 = int(rect[1] + rect[3]/2)
    cv2.circle(draw_raw_2,(x0,y0),1,(255,1,0),4)
    cv2.drawContours(draw_raw_2, #一个三通道图像
             counts, #全部发现的轮廓
             -1,    #轮廓索引号 -1就是绘制所有轮廓
             (255,0,0),  #轮廓颜色
             2)      #宽度
image7 = Image.fromarray(draw_raw_2.astype('uint8')).convert('RGB')
print("下面是确定圆心图")
display(image7)
下面是确定圆心图

3. 分水岭分割方法

#它是依赖于形态学的,图像的灰度等级不一样,如果图像的灰度等级一样的情况下怎么人为的把它造成不一样?

#可以通过距离变换实现,这样它们的灰度值就有了阶梯状的变换。

#风水岭算法常见的有三种方法:

#(1)基于浸泡理论的分水岭分割方法;

#(2)基于连通图方法;

#(3)基于距离变换的方法。

#OpenCV 中是基于距离变换的分割方法,就相当于我们的小山头(认为造成的)。

#基本步骤:输入图像-》灰度-》二值-》距离变换-》寻找种子-》

#生成Mark-》分水岭变换-》输出图像

原理: 灰度图像可以被看成拓扑平面,灰度值高的区域可以看出山峰,灰度值低的区域可以看成是山谷。向每一个山谷当中灌不同颜色的水。水位升高,不同山谷的水会汇合,为防止不同山谷的水汇合,小在汇合处建立起堤坝。然后继续灌水,然后再建立堤坝,直到山峰都掩模。构建好的堤坝就是图像的分割。

#例子1 粘连对象分离和计数。

import cv2
from PIL import Image
import numpy as np
raw_ = cv2.imread("2_3.jpg")
raw = cv2.cvtColor(raw_,cv2.COLOR_BGR2RGB)
print(type(raw))
image1 = Image.fromarray(raw.astype('uint8')).convert('RGB')
print("下面是原图")
display(image1)
dst = cv2.pyrMeanShiftFiltering(raw, 21, 51)   #边缘保留滤波EPF
image2 = Image.fromarray(dst.astype('uint8')).convert('RGB')
print("下面是边缘保留滤波图")
display(image2)
raw_gray = cv2.cvtColor(dst,cv2.COLOR_RGB2GRAY)
ret,thresh2 = cv2.threshold(raw_gray,0,255,cv2.THRESH_BINARY + cv2.THRESH_OTSU)
image3 = Image.fromarray(thresh2.astype('uint8')).convert('RGB')
print("下面是二值图")
display(image3)
#距离变换
dist_img = cv2.distanceTransform(thresh2, cv2.DIST_L2, 3)
#归一化
norm = cv2.normalize(dist_img,dst=None,alpha=0.0, beta=1.0, norm_type=cv2.NORM_MINMAX)
#二值化用来寻找上面归一化之后的局部最大值
ret,thresh = cv2.threshold(norm,0.4,1,cv2.THRESH_BINARY)
#这个值太小了,不乘200根本看不见
x = thresh*200
image4 = Image.fromarray(x.astype('uint8')).convert('RGB')
print("下面是距离变换图")
display(image4)
#转个格式 还不知道有什么用
thresh= thresh.astype(np.uint8)

_,counts,hierarchy = cv2.findContours(thresh,  #输入图像
                                    cv2.RETR_EXTERNAL, #轮廓返回模式
                                    cv2.CHAIN_APPROX_SIMPLE) #发现方法
draw_img = np.zeros(thresh.shape,dtype="uint8")
draw_img_3 = cv2.merge([draw_img,draw_img,draw_img])
cv2.drawContours(draw_img_3, #一个三通道图像
             counts, #全部发现的轮廓
             -1,    #轮廓索引号 -1就是绘制所有轮廓
             (255,0,0),  #轮廓颜色
             1)      #宽度
#我也不知道我为什么要做填充,反正看起来还可以的样子
draw_img_3=cv2.fillPoly(draw_img_3, counts ,(255,0,0))
image5 = Image.fromarray(draw_img_3.astype('uint8')).convert('RGB')
print("下面是轮廓检测图")
display(image5)

kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(3,3))
#先做闭操作
op_close = cv2.morphologyEx(draw_img_3, cv2.MORPH_ERODE, kernel)
image6 = Image.fromarray(op_close.astype('uint8')).convert('RGB')
print("下面是腐蚀图")
display(image6)
#抽取单通道转为int32
(B,G,R) = cv2.split(draw_img_3)
B = np.int32(B)
image7 = Image.fromarray(B.astype('uint8')).convert('RGB')
print("下面是单通道图")
display(image7)
#分水岭有问题不知道是为什么?
#要求第一个是3通道的,uint8格式,第二个是三通道的,int32格式的,且尺寸必须对应
# result = cv2.watershed(raw,B)
# # result = result * 100
# raw[result == -1] = [0,0,0]
# # print("下面是腐蚀图")
# image6 = Image.fromarray(raw.astype('uint8')).convert('RGB')
# display(image6)
下面是原图

下面是边缘保留滤波图

下面是二值图

下面是距离变换图

下面是轮廓检测图

下面是腐蚀图

下面是单通道图

'''
    换了另外一种做法
'''
import numpy as np
import cv2
from matplotlib import pyplot as plt
from PIL import Image
#读图
img = cv2.imread("2_3.jpg")
#灰度处理
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#二值化分割
ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
image1 = Image.fromarray(thresh.astype('uint8')).convert('RGB')
print("原图")
display(image1)
#开操作 去除小的噪声
kernel = np.ones((3,3),np.uint8)
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel)
image2 = Image.fromarray(opening.astype('uint8')).convert('RGB')
print("去除噪声之后的")
display(image2)
sure_bg = cv2.dilate(opening,kernel,iterations=3)
#膨胀的剩下的肯定是背景
image3 = Image.fromarray(sure_bg.astype('uint8')).convert('RGB')
print("背景")
display(image3)
#距离变换完了的就是前景
dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,3)
#阈值分割
# 原办法不好 我给换了
# ret, sure_fg = cv2.threshold(dist_transform,0.7*dist_transform.max(),255,0)
norm = cv2.normalize(dist_transform,dst=None,alpha=0.0, beta=1.0, norm_type=cv2.NORM_MINMAX)
#二值化用来寻找上面归一化之后的局部最大值
ret,sure_fg_pre = cv2.threshold(norm,0.4,1,cv2.THRESH_BINARY)
sure_fg = sure_fg_pre * 250
#寻找未知区域
sure_fg = np.uint8(sure_fg)
image4 = Image.fromarray(sure_fg.astype('uint8')).convert('RGB')
print("前景")
display(image4)
#矩阵减法,背景(包含确认部分和不确认部分)- 前景(确认部分)=不知道有没有包含目标的未知区域
unknown = cv2.subtract(sure_bg,sure_fg)
image5 = Image.fromarray(unknown.astype('uint8')).convert('RGB')
print("背景 - 前景")
display(image5)
# 创建标记,(这个是和原始图像的大小一样的数组,只不过数据类型是int32)
# 然后在里面标记区域,我们确认的区域(前景或者背景)用不同的正整数标记出来,
# 我们不确认的区域保持0,我们可以用cv2.connectedComponents()来做这个,它把图像背景标成0,
# 其他目标用从1开始的整数标记。
# 13个区域 每个区域里面的值都是同一个正整数序号
ret, markers = cv2.connectedComponents(sure_fg)
# 将1添加到所有标签,以确保背景不是0,而是1
markers = markers+1
#现在,让未知区域标记都是0
markers[unknown==255]=0
#分水岭
markers = cv2.watershed(img,markers)
#简写赋值
img[markers == -1] = [255,0,0]
image6 = Image.fromarray(img.astype('uint8')).convert('RGB')
print("分水岭结果")
display(image6)
原图

去除噪声之后的

背景

前景

背景 减去 前景

分水岭结果

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:3875789
帖子:775174
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP