计算联通区域

对于这样的图片:
这里写图片描述

抠出其中的黑色区域,效果如下:
这里写图片描述

import cv2
import numpy as np
import matplotlib.pyplot as plt
import time



def findUnicomArea(img):
    #先二值化
    ret,threshold = cv2.threshold(img,128,255,cv2.THRESH_BINARY)
    img_flag = np.zeros(threshold.shape,np.int8)
    count = 0
    findpoint = []
    #首先遍历图像找到所有的联通区
    for x in range(threshold.shape[0]):
        for y in range(threshold.shape[1]):
            if(threshold[x][y] == 0 and img_flag[x][y] == 0):
                #这里表示已经找到了一个没有标志过的黑点,是一个新的联通区
                count += 1
                img_flag[x][y] = count
                findpoint.append((x,y))
            while len(findpoint) > 0:
                xx,yy = findpoint.pop()
                if xx > 0 :#上面
                    if threshold[xx-1][yy] == 0 and img_flag[xx-1][yy] == 0:
                        findpoint.append((xx-1,yy))
                        img_flag[xx-1][yy] = count
                if xx < img.shape[0]:#下面
                    if threshold[xx + 1][yy] == 0 and img_flag[xx + 1][yy] == 0:
                        findpoint.append((xx + 1, yy))
                        img_flag[xx+1][yy] = count
                if yy > 0:#左面
                    if threshold[xx][yy-1] == 0 and img_flag[xx][yy-1] == 0:
                        findpoint.append((xx, yy-1))
                        img_flag[xx][yy-1] = count
                if yy < img.shape[1]:#右面
                    if threshold[xx][yy+1] == 0 and img_flag[xx][yy+1] == 0:
                        findpoint.append((xx, yy+1))
                        img_flag[xx][yy+1] = count

    #联通区任然存在一张表中,需要将其分离
    coutours = []
    for num in range(1,count+1):
        coutours.append([])
        for x in range(img_flag.shape[0]):
            for y in range(img_flag.shape[1]):
                if img_flag[x][y] == num:
                    coutours[num-1].append([x,y,img_flag[x][y]])

    desCoutous = np.empty(len(coutours),np.object)
    for num in range(len(coutours)):
        #将分离后的图像提取出来。计算出联通区所在的范围
        tmp = np.mat(coutours[num])
        minX = np.min(tmp[:,0])
        maxX = np.max(tmp[:,0])
        minY = np.min(tmp[:,1])
        maxY = np.max(tmp[:,1])
        desCoutous[num] = img[minX:maxX,minY:maxY]
    return desCoutous

#测试程序如下
img = cv2.imread("testpic/connected_test.png",0);
cost = time.time()
unicoms = findUnicomArea(img)
print("cost",time.time()-cost)
for i in range(len(unicoms)):
    cv2.imshow("test"+str(i),unicoms[i])

cv2.waitKey(0)

目前只是实现了功能,效率还不高,还有很大的优化空间。

这个程序显然有重复计算,第二次循环完全可以略去,可以合并到第一个循环中。另外,只需要遍历1~shape[0]-1的空间就能找到所有的点,所以边界的比较也可以省去,如此,程序如下:

def findUnicomArea(img):
    #先二值化
    ret,threshold = cv2.threshold(img,128,255,cv2.THRESH_BINARY)
    img_flag = np.zeros(threshold.shape,np.int8)
    count = 0
    findpoint = []
    coutours = []
    #首先遍历图像找到所有的联通区
    for x in range(1,threshold.shape[0]-1):
        for y in range(1,threshold.shape[1]-1):
            if(threshold[x][y] == 0 and img_flag[x][y] == 0):
                #这里表示已经找到了一个没有标志过的黑点,是一个新的联通区
                count += 1
                #新增一个联通区存储点
                coutours.append([])
                img_flag[x][y] = count
                findpoint.append((x,y))
            while len(findpoint) > 0:
                xx,yy = findpoint.pop()
                #上面
                if threshold[xx-1][yy] == 0 and img_flag[xx-1][yy] == 0:
                    findpoint.append((xx-1,yy))
                    img_flag[xx-1][yy] = count
                    coutours[count - 1].append([xx, yy, img_flag[x][y]])
                #下面
                if threshold[xx + 1][yy] == 0 and img_flag[xx + 1][yy] == 0:
                    findpoint.append((xx + 1, yy))
                    img_flag[xx+1][yy] = count
                    coutours[count - 1].append([xx, yy, img_flag[x][y]])
                #左面
                if threshold[xx][yy-1] == 0 and img_flag[xx][yy-1] == 0:
                    findpoint.append((xx, yy-1))
                    img_flag[xx][yy-1] = count
                    coutours[count - 1].append([xx, yy, img_flag[x][y]])
                #右面
                if threshold[xx][yy+1] == 0 and img_flag[xx][yy+1] == 0:
                    findpoint.append((xx, yy+1))
                    img_flag[xx][yy+1] = count
                    coutours[count - 1].append([xx, yy, img_flag[x][y]])

    desCoutous = np.empty(len(coutours),np.object)
    for num in range(len(coutours)):
        #将分离后的图像提取出来。计算出联通区所在的范围
        tmp = np.mat(coutours[num])
        minX = np.min(tmp[:,0])
        maxX = np.max(tmp[:,0])
        minY = np.min(tmp[:,1])
        maxY = np.max(tmp[:,1])
        desCoutous[num] = img[minX:maxX,minY:maxY]
    return desCoutous

这样程序性能从1.3秒提升至0.8秒,程序有了显著的提升。
如果我们只是抠出图中的联通区的话,只需要检测边界联通区的外边界就可以了,这样效率能进一步提升。

def findUnicomBoundry(img):
    #先二值化
    ret,threshold = cv2.threshold(img,128,255,cv2.THRESH_BINARY)
    img_flag = np.zeros(threshold.shape,np.int8)
    count = 0
    findpoint = []
    coutours = []
    desCoutous = []
    existCoutous = []
    #首先遍历图像找到所有的联通区
    for x in range(1,threshold.shape[0]-1):
        for y in range(1,threshold.shape[1]-1):
            if(threshold[x][y] == 0 and (threshold[x-1][y]==255 or threshold[x+1][y]==255 or threshold[x][y-1]==255 or threshold[x][y+1]==255) and img_flag[x][y] == 0):#是边界的条件是中心点为0,四周至少要有一个白点
                #这里表示已经找到了一个边界点,并且是一个新的联通区的边界点
                # 将分离后的图像提取出来。计算出联通区所在的范围
                if count > len(desCoutous):
                    desCoutous.append([])
                    tmp = np.mat(coutours[count - 1])
                    minX = np.min(tmp[:, 0])
                    maxX = np.max(tmp[:, 0])
                    minY = np.min(tmp[:, 1])
                    maxY = np.max(tmp[:, 1])
                    existCoutous.append([minX, maxX, minY, maxY])
                    desCoutous[count - 1] = img[minX:maxX, minY:maxY]
                    desCoutous[count-1] = np.mat(desCoutous[count-1])
                isChildArea = False
                for num in range(len(existCoutous)):
                    if x>existCoutous[num][0] and x < existCoutous[num][1]  and y > existCoutous[num][2]  and y < existCoutous[num][3] :
                        isChildArea = True
                if not isChildArea:
                    count += 1
                    #新增一个联通区的边界存储点
                    coutours.append([])
                    img_flag[x][y] = count
                    findpoint.append((x,y))
            while len(findpoint) > 0:
                #xx,yy肯定是一个边界点了,现在寻找下一个边界点
                xx,yy = findpoint.pop()
                #上面
                if threshold[xx-1][yy] == 0 and (threshold[xx-2][yy]==255 or threshold[xx-1][yy-1]==255 or threshold[xx-1][yy+1]==255) and img_flag[xx-1][yy] == 0:
                    findpoint.append((xx-1,yy))
                    img_flag[xx-1][yy] = count
                    coutours[count - 1].append([xx-1, yy, img_flag[xx-1][yy]])
                #下面
                if threshold[xx + 1][yy] == 0 and (threshold[xx+1][yy]==255 or threshold[xx+1][yy-1]==255 or threshold[xx+1][yy+1]==255) and img_flag[xx + 1][yy] == 0:
                    findpoint.append((xx + 1, yy))
                    img_flag[xx+1][yy] = count
                    coutours[count - 1].append([xx+1, yy, img_flag[xx][yy]])
                #左面
                if threshold[xx][yy-1] == 0 and (threshold[xx][yy-2]==255 or threshold[xx-1][yy-1]==255 or threshold[xx+1][yy-1]==255) and img_flag[xx][yy-1] == 0:
                    findpoint.append((xx, yy-1))
                    img_flag[xx][yy-1] = count
                    coutours[count - 1].append([xx, yy-1, img_flag[xx][yy-1]])
                #右面
                if threshold[xx][yy+1] == 0 and (threshold[xx][yy+2]==255 or threshold[xx-1][yy+1]==255 or threshold[xx+1][yy+1]==255) and img_flag[xx][yy+1] == 0:
                    findpoint.append((xx, yy+1))
                    img_flag[xx][yy+1] = count
                    coutours[count - 1].append([xx, yy+1, img_flag[xx][yy+1]])
                #左上
                if threshold[xx-1][yy-1] == 0 and (threshold[xx-2][yy-1]==255 or threshold[xx][yy]==255 or threshold[xx-1][yy-2]==255 or threshold[xx-1][yy]==255) and img_flag[xx-1][yy-1] == 0:
                    findpoint.append((xx-1, yy-1))
                    img_flag[xx-1][yy-1] = count
                    coutours[count - 1].append([xx-1, yy-1, img_flag[xx-1][yy-1]])
                #右上
                if threshold[xx-1][yy+1] == 0 and (threshold[xx-2][yy+1]==255 or threshold[xx][yy+1]==255 or threshold[xx-1][yy]==255 or threshold[xx-1][yy+1]==255) and img_flag[xx-1][yy+1] == 0:
                    findpoint.append((xx-1, yy+1))
                    img_flag[xx-1][yy+1] = count
                    coutours[count - 1].append([xx-1, yy+1, img_flag[xx-1][yy+1]])
                #左下
                if threshold[xx+1][yy-1] == 0 and (threshold[xx][yy-1]==255 or threshold[xx+2][yy-1]==255 or threshold[xx+1][yy-2]==255 or threshold[xx+1][yy]==255) and img_flag[xx+1][yy-1] == 0:
                    findpoint.append((xx+1, yy-1))
                    img_flag[xx+1][yy-1] = count
                    coutours[count - 1].append([xx+1, yy-1, img_flag[xx+1][yy-1]])
                #右下
                if threshold[xx+1][yy+1] == 0 and (threshold[xx][yy+1]==255 or threshold[xx+2][yy+1]==255 or threshold[xx+1][yy]==255 or threshold[xx+1][yy+2]==255) and img_flag[xx+1][yy+1] == 0:
                    findpoint.append((xx+1, yy+1))
                    img_flag[xx+1][yy+1] = count
                    coutours[count - 1].append([xx+1, yy+1, img_flag[xx+1][yy+1]])
    return desCoutous

程序的运行时间缩小到0.6秒

c++基于opencv的实现:

#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;

Mat findContinousArea(Mat& img)
{
    int rows = img.rows;
    int cols = img.cols;
    
    Mat coutinous = Mat(img.rows,img.cols,img.type(),Scalar(0));
    queue<Point> tmp;
    int cCount = 0;
    for(int i=0;i<rows;i++){
        for(int j=0;j<cols;j++){
            if(img.at<char>(i,j)==0 && coutinous.at<char>(i,j)==0){
                cCount+=30;
                tmp.push(Point(i,j));
                coutinous.at<char>(i,j) = cCount;
                while(!tmp.empty()){
                    Point p = tmp.front();
                    tmp.pop();
                    
                    if(p.x>0 && img.at<char>(p.x-1,p.y)==0 && coutinous.at<char>(p.x-1,p.y)==0){
                        tmp.push(Point(p.x-1,p.y));
                        coutinous.at<char>(p.x-1,p.y) = cCount;
                    }
                    if(p.x<rows && img.at<char>(p.x+1,p.y)==0 && coutinous.at<char>(p.x+1,p.y)==0){
                        tmp.push(Point(p.x+1,p.y));
                        coutinous.at<char>(p.x+1,p.y) = cCount;
                    }
                    if(p.y>0 && img.at<char>(p.x,p.y-1)==0 && coutinous.at<char>(p.x,p.y-1)==0){
                        tmp.push(Point(p.x,p.y-1));
                        coutinous.at<char>(p.x,p.y-1) = cCount;
                    }
                    if(p.y<cols && img.at<char>(p.x,p.y+1)==0 && coutinous.at<char>(p.x,p.y+1)==0){
                        tmp.push(Point(p.x,p.y+1));
                        coutinous.at<char>(p.x,p.y+1) = cCount;
                    }
                }
            }
        }
    }
    return coutinous;
}


int main() {
    
    Mat frame = imread("/Users/jinweiliu/Pictures/coutinous.png",0);
    int rows = frame.rows;
    int cols = frame.cols;

    //二值化
    for(int i=0;i<rows;i++){
        for(int j=0;j<cols;j++){
            if((frame.at<char>(i,j)&0xff)<128){
                frame.at<char>(i,j) = 0;
            }else{
                frame.at<char>(i,j) = (char)255;
            }
        }
    }
    TickMeter tm;
    tm.start();
    Mat coutinous = findContinousArea(frame);
    tm.stop();
    cout<<tm.getTimeMilli()<<endl;
    imshow("test",coutinous);
    waitKey();
    return 0;
}

原图:
在这里插入图片描述
结果如下:
在这里插入图片描述

注意,结果是用不同的数字表示不同的联通区的,比如,第一个联通区全部标志为30,第二个全部标志为60,以此类推。之所以是30,60,90等而不是1,2,3等是因为显示出来看着比较明显。这里只是把不同的联通区标示出来,并没有做切割之类的,仅供参考。最后,c++的这段代码耗时只有3ms,性能还是挺不错的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章