c++ opencv实现区域填充_OpenCV案例分析-目标ROI区域提取

目标ROI提取案例

背景

⛳️ 最近看到一个小伙伴询问一个图片roi提取的问题,我觉得这个案例虽然不难,但是可以很好的结合此前opencv基础知识的分享,因此分享给大家一起学习探讨。

问题

✔️ 需要在下面的图片中提取出圆形的内容,并且单独保存roi截图。

1a78cd8249d15b1b800c630f32c73625.png
目标原图

☝️ 想法

✔️ 看到这个问题时,当时有3个想法,分别是:

  • 霍夫曼圆检测
  • 轮廓筛选提取
  • 连通组件提取

✔️ 认真看图时,发现图片的圆并不是很圆,这样使用霍夫曼检测,在画外接矩形的时候会产生大量的非roi区域,这样效果就不是很好,因此,下面针对轮廓和连通组件方法进行分析。

方案

轮廓检测提取

基础储备:

轮廓相关的基础知识可以参考: OpenCV图像处理-轮廓和轮廓特征。

方案构想:

  1. 读取图像,转灰度;
  2. 二值化,高斯模糊,开运算+闭运算,减少噪点影响;
  3. Canny 算子提取边缘;
  4. 使用 cv2.findContours 提取轮廓;
  5. 分析轮廓的Area,设置阈值,提取出目标轮廓
  6. 根据轮廓找到外接矩形,保存矩形 x,y,w,h,提取ROI

连通组件提取

基础储备:

连通相关的基础知识可以参考: OpenCV图像处理-连通组件。

方案构想:

  1. 读取图像,转灰度;
  2. 二值化,反转binary, 让前景为白色,背景为黑色;
  3. 使用 floodFill 填充前景图,让这些圆形饱和;
  4. 使用 cv2.connectedComponentsWithStats 提取连通域;
  5. 根据提取的连通域,分析,面积,筛选出符合条件的连通域;
  6. 针对筛选后的连通区,找到外接矩形,保存矩形 x,y,w,h,提取ROI。

Opencv的区域填充

✔️ 其实,方案二的难点在于如何填充图片中的圆形区域,这里需要采用opencv的 cv2.floodFill()方法去实现。

这里先用一个小例子来分析填充问题:

如下图,需要把实线区域填充。

daa8fe2cab07cae9010400615b3a1582.png
实线原图

填充代码:

import cv2

# 填充函数
def fillHole(srcBw):
    temp = srcBw.copy()
    cv2.floodFill(temp, None, (0,0), 255)
    cv2.imshow('temp', temp)
    temp = cv2.bitwise_not(temp)

    return temp

img = cv2.imread("fill.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, th1 = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU )
# 反转th1, 让前景为白色,背景为黑色
th2 = cv2.bitwise_not(th1)
out = fillHole(th2)

cv2.imshow('gray', th2)
cv2.imshow('out', out)

fbba69f4cd7ddcc23d8c705cf7d90b7b.png
二值化图

70b4f3b2e1fcb077bf85d4c64e34e963.png
填充图

代码

连通组件提取

import cv2;
import numpy as np;

# get the front image
def fillHole(srcBw):
    '''
    srcBw: 二值图,前景为白色
    '''
    temp = srcBw.copy()
    # Floodfill from point (0, 0)
    cv2.floodFill(temp, None, (0,0), 255)
    # Invert floodfilled image
    temp = cv2.bitwise_not(temp)
    cv2.imshow('temp', temp)

    # Invert floodfilled image
    out = srcBw | temp

    return out

def getRoi(im_out, img):
    '''
    im_out: 预处理好的二值图
    img : 原图,Type, BGR
    '''
    num_labels, labels, stats, centers = cv2.connectedComponentsWithStats(im_out, connectivity=8, ltype=cv2.CV_32S)
    image = np.copy(img)
    roi_list = []
    for t in range(1, num_labels, 1):
        x, y, w, h, area = stats[t]
        if area < 500:
            continue
        cx, cy = centers[t]
        # 标出中心位置
        cv2.circle(image, (np.int32(cx), np.int32(cy)), 2, (0, 255, 0), 2, 8, 0)
        # 画出外接矩形
        cv2.rectangle(image, (x, y), (x+w, y+h), (0, 255, 0), 2, 8, 0)
        # 保存roi的坐标和长宽
        roi_list.append((x, y, w, h))

    return num_labels, labels, image, roi_list

# show the colorful components
def colorImgShow(im_out, num_labels, labels):
    '''
    im_out: 填充后的二值图
    num_labels: 连通组件的个数
    labels: 连通组件的输出标记图像,背景index=0
    '''
    # make the colors
    colors = []
    for i in range(num_labels):
        b = np.random.randint(0, 256)
        g = np.random.randint(0, 256)
        r = np.random.randint(0, 256)
        colors.append((b, g, r))
    colors[0] = (0, 0, 0)

    # draw the image
    h, w = im_out.shape
    image = np.zeros((h, w, 3), dtype=np.uint8)
    for row in range(h):
        for col in range(w):
            image[row, col] = (colors[labels[row, col]])

    return image

# Save the roi
def saveRoi(src, roi_list):
    '''
    src: 原图的copy
    roi_list: List,保存的roi位置信息
    '''
    for i in range(len(roi_list)):
        x, y, w, h = roi_list[i]
        roi = src[y:y+h, x:x+w]
        cv2.imwrite("money_roi/roi_%d.jpg"%i, roi)
        print("No.%02d Finished! "%i)

if __name__ == '__main__':

    # 预处理
    img = cv2.imread("money.png");
    im_in = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)   
    ret, im_th = cv2.threshold(im_in, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU )

    # 前景设置为白色,背景设置为黑色
    im_th = cv2.bitwise_not(im_th)

    # 调用填充函数
    im_out = fillHole(im_th)

    # 利用连通器寻找到需要提取的 roi
    num_labels, labels, image, roi_list = getRoi(im_th, img)

    # 可视化连通域
    color_image = colorImgShow(im_out, num_labels, labels)

    # 保存roi
    saveRoi(img, roi_list)

    # Display images.
    cv2.imshow("Thresholded Image", im_th)
    cv2.imwrite("money_connect_binary.jpg", im_th)

    cv2.imshow("Colorful Image", color_image)
    cv2.imwrite("money_connect_color.jpg", color_image)

    cv2.imshow("Foreground", image)
    cv2.imwrite("money_connect_out.jpg", image)


    cv2.waitKey(0)
    cv2.destroyAllWindows()

8e534594713581bc77bccf17a8a880bd.png
二值化图

5c2d9bbf921e9a8e3b9aba591f31a3da.png
填充图

8e2c163810a1a1a21a282205f4c40779.png
连通组件

fb2a378c4498bba5314fdc9bacb49738.png
结果图

轮廓检测提取

import cv2
import numpy as np

# 读取图片并且进行预处理
def processImg(path):
    '''
    path: 图片路径
    '''
    img = cv2.imread(path)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    gray = cv2.GaussianBlur(gray, (3, 3), 1)
    ret, th1 = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU )
    # 开闭运算去除噪点
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
    th1 = cv2.morphologyEx(th1, cv2.MORPH_OPEN, kernel)
    th1 = cv2.morphologyEx(th1, cv2.MORPH_CLOSE, kernel)
    edge = cv2.Canny(th1, 50, 100)

    return th1, img, edge

# 利用轮廓处理获取roi
def getRoi(img, binary):
    '''
    img: 原图
    binary: 预处理后得到的canny边缘
    '''
    # 寻找轮廓
    _, contours, _ = cv2.findContours(
        binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

    roi_list = []

    # 判断出圆形区域
    for cnt in range(len(contours)):
        area = cv2.contourArea(contours[cnt])
        # 判断提取所需的轮廓,经验值需要调试获取
        if 4000 < area < 10000:
            # 获取外接矩形的值
            x,y,w,h = cv2.boundingRect(contours[cnt])
            roi_list.append((x,y,w,h))
            cv2.rectangle(img, (x,y),(x+w, y+h),(0,255,0),2)
            cv2.drawContours(img, [contours[cnt]], 0, (255, 0, 255), 2)

    return img, roi_list

# Save the roi
def saveRoi(src, roi_list):
    '''
    src: 原图的copy
    roi_list: List,保存的roi位置信息
    '''
    for i in range(len(roi_list)):
        x, y, w, h = roi_list[i]
        roi = src[y:y+h, x:x+w]
        cv2.imwrite("money_roi/roi_%d.jpg"%i, roi)
        print("No.%02d Finished! "%i)

if __name__ == '__main__':

    th1, img, edge = processImg("money.png")
    # copy img
    src = img.copy()
    # 获取roi
    img, roi_list = getRoi(img, edge)

    # 保存roi
    saveRoi(src, roi_list)

    # Display images.
    cv2.imshow("Thresholded Image", th1)
    cv2.imwrite("money_contour_binary.jpg", th1)

    cv2.imshow("edge", edge)
    cv2.imwrite("money_contour_edge.jpg", edge)

    cv2.imshow("roi image", img)
    cv2.imwrite("money_contour_out.jpg", img)

    cv2.waitKey(0)
    cv2.destroyAllWindows()

312c4e0040e61cc9930d9af0ea90ba19.png
预处理后的二值化图

fd3cdcb817836dac7ce1f553f04a6a46.png
Canny边缘

087179bb41e517427e3db8fc2c1c1afc.png
结果图

⛳️⛳️⛳️ 最后处理后得到的roi截图如下:

04c9094040d85ee4bed2eec72c98e1f3.png
ROI截图

后记

✔️ 通过简单的轮廓处理和连通组件的方法能够合理的解决这个案例,截取出我们所需要的 ROI 区域,当然可能还有别的方法去解决,希望大家也能够集思广益,一起学习进步!

------------------------------------------可爱の分割线------------------------------------------

更多Opencv教程可以 Follow github的opencv教程,中文&English 欢迎Star❤️❤️❤️

JimmyHHua/opencv_tutorials​github.com
5cff40a418111c416c6ed6431353d0e1.png
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值