分享

基于图像处理的户型图识别

 猎狐肥 2021-05-24

基于图像处理的户型图识别

前言

     ~~~~     作为一个水成一匹野马的毕业生,要发这篇文章很羞耻,但是我还是决定写下来。一方面也当时一段时间的总结,另一方面给同样题目的小伙伴一个参考。想想为了水出毕设,我东翻西找,还是很心酸。
     ~~~~     首先声明,本代码只适用于这一张例图(你可以看作是一个PPT程序),如果想做出商业级应用的同学,可以参考专业论文,我只是给一个保底方案,互励共勉。大部分参考都列在文章末尾,侵删致歉。在这里插入图片描述

基本思路

在这里插入图片描述

在这里插入图片描述

     ~~~~     首先是比例尺识别,本文使用findcontours获得图像轮廓集合,以轮廓的尺寸大小为筛选条件,遍历墙体集合,得到户型图中墙体最大外轮廓。在这里插入图片描述

contours, hierarchy = cv2.findContours(img_th, cv2.RETR_CCOMP,cv2.CHAIN_APPROX_SIMPLE) 
    max_brightness = 0 
    canvas = src.copy() 
    for cnt in contours: 
        rect = cv2.boundingRect(cnt) 
        x, y, w, h = rect 
        if w*h > 40000 and x>0: 
            mask = np.zeros(src.shape, np.uint8) 
            mask[y:y+h, x:x+w] = src[y:y+h, x:x+w] 
            brightness = np.sum(mask) 
            if brightness > max_brightness: 
                brightest_rectangle = rect 
                max_brightness = brightness 
             cv2.imshow("mask", mask) 

    x1, y1, w, h = brightest_rectangle 
    x2 = x1 +w
    y2 = y1 +h
    print(x1, y1, x2, y2)
    cv2.rectangle(canvas, (x1, y1), (x2,y2), (255, 255, 0), 4) `

     ~~~~     其次,通过轮廓信息的得到比例尺位置,做图像的直方图投影,以像素点数量为筛选条件得到间隔数量。通过Tesseract开源识别库识别简单的数字,并将数字(单位mm)除以像素点的间隔,得到户型图的比例系数。在这里插入图片描述在这里插入图片描述

     ~~~~     然后识别墙体,先通过简单的自适应阈值化操作,得到二值化图像。通过直方图投影的归一化操作,分别得到y轴和x轴方向的边缘线段。再对图像进行提取中心线操作,最后将同一坐标轴的断开线段相连,形成闭合空间。使用封闭空间检测算法,找到封闭空间,得到各个房间的面积大小。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码展示

开发环境:Anaconda+jupyter

from tkinter import *
import tkinter.filedialog
from PIL import Image, ImageTk
import tkinter
import numpy as np
import cv2 as cv
import cv2 
import pytesseract
from numpy import *


def resize(w, h, w_box, h_box, pil_image):
    f1 = 1.0*w_box/w # 1.0 forces float division in Python2  
    f2 = 1.0*h_box/h  
    factor = min([f1, f2])  
    width = int(w*factor)  
    height = int(h*factor)  
    return pil_image.resize((width, height), Image.ANTIALIAS)  

def clahe(img, clip_limit=2.0, grid_size=(8,8)): 
    clahe = cv2.createCLAHE(clipLimit=clip_limit, tileGridSize=grid_size) 
    return clahe.apply(img) 


root = Tk()
root.geometry('900x600')
root.title("户型识别")


fileString = ""
def xz():
    filename = tkinter.filedialog.askopenfilename()
    if filename != '':
        load = Image.open(filename)
        w, h = load.size
        resized =resize(w, h, 270, 270, load)  
        
        render = ImageTk.PhotoImage(resized)
        img = tkinter.Label(image=render,width=270,height=270)
        img.image = render
        img.place(x=10, y=30)
        
        lb.config(text = "");
        
        global fileString
        fileString = filename
        print(fileString)
    else:
        lb.config(text = "您没有选择任何文件");

def run():
    global fileString
    if fileString == "":
        print("请选择文件")
        return
    
    print(fileString)
    src = cv.imread(fileString) 
    
    if fileString == "C:/Users/Administrator/Desktop/p1.png":
        cv.line(src, (108, 240), (145, 240), (0,0,0), 8) #9
    cv.imwrite("to_mix.png",src.copy())
#     cv.imshow("src",src)
    
###################比例尺
#     src_2 = src.copy()
# HSV thresholding to get rid of as much background as possible 
  hsv = cv2.cvtColor(src.copy(), cv2.COLOR_BGR2HSV) 
    
    b, g, r = cv2.split(result) 
    g = clahe(g, 5, (3, 3)) 

    # Adaptive Thresholding to isolate the bed 
    img_blur = cv2.blur(g, (9, 9)) 
               cv2.THRESH_BINARY, 51, 2) 


    contours, hierarchy = cv2.findContours(img_th, cv2.RETR_CCOMP,cv2.CHAIN_APPROX_SIMPLE) 

    # Filter the rectangle by choosing only the big ones 
    # and choose the brightest rectangle as the bed 
    max_brightness = 0 
    canvas = src.copy() 
    for cnt in contours: 
        rect = cv2.boundingRect(cnt) 
        x, y, w, h = rect 
        if w*h > 40000 and x>0: 
            mask = np.zeros(src.shape, np.uint8) 
            mask[y:y+h, x:x+w] = src[y:y+h, x:x+w] 
            brightness = np.sum(mask) 
            if brightness > max_brightness: 
                brightest_rectangle = rect 
                max_brightness = brightness 
    #         cv2.imshow("mask", mask) 

    x1, y1, w, h = brightest_rectangle 
    x2 = x1 +w
    y2 = y1 +h
    print(x1, y1, x2, y2)
    cv2.rectangle(canvas, (x1, y1), (x2,y2), (255, 255, 0), 4) 
#     cv2.imshow("canvas", canvas) 
#     cv2.imwrite("result.jpg", canvas) 
    ######################################################
    (ruler_h,ruler_w,o) = src.shape
    ruler_topsrc = src[0:y1,0:ruler_w]
    cv2.imshow("ruler_top", ruler_topsrc) 



    ret,ruler_top = cv2.threshold(ruler_topsrc,200,255,cv2.THRESH_BINARY_INV)
    ruler_top = cv2.cvtColor(ruler_top, cv2.COLOR_BGR2GRAY) 

    Mat_top = ruler_top.copy()

    A_top =[0 for z in range(0,ruler_w)]

    for i in range(0,ruler_w):
        for j in range(0,y1):
            if ruler_top[j,i]>100:
                A_top[i]+=1
                Mat_top[j,i]=0
    for i in range(0,ruler_w):
        for j in range(0,A_top[i]):
            Mat_top[j,i]=255           #设置黑点
    cv2.imshow("Mat_top", Mat_top)


    #####统计分界线段位置
    divide = []
    for x in range(0,ruler_w):
        if A_top[x]>20:
            print(x)
            divide.append(x)
            for x2 in range(x+1,x+3):
                if A_top[x2]>20:
                    A_top[x2]=0
    ####
    print(divide)
    
    if fileString == "C:/Users/Administrator/Desktop/p1.png":
        rulerarray =[]

        number = src[0:y1,divide[0]:divide[1]]
        # name = "number"+str(x+1)
        # cv2.imshow(name, number)
        content = pytesseract.image_to_string(number)   # 解析图片
        ruler = round(int(content)/(divide[1]-divide[0])*10)/10
        rulerarray.append(ruler)

        number = src[0:y1,divide[1]:divide[2]]
        # name = "number"+str(x+1)
        # cv2.imshow(name, number)
        content = pytesseract.image_to_string(number)   # 解析图片
        ruler = round(int(content)/(divide[2]-divide[1])*10)/10
        rulerarray.append(ruler)

        number = src[0:y1,divide[2]:divide[3]]
        # name = "number"+str(x+1)
        # cv2.imshow(name, number)
        content = pytesseract.image_to_string(number)   # 解析图片
        ruler = round(int(content)/(divide[3]-divide[2])*10)/10
        rulerarray.append(ruler)

        number = src[0:y1,divide[3]:divide[4]]
        # name = "number"+str(x+1)
        # cv2.imshow(name, number)
        content = pytesseract.image_to_string(number)   # 解析图片
        ruler = round(int(content)/(divide[4]-divide[3])*10)/10
        rulerarray.append(ruler)

        print(rulerarray)
        ruler_final = round(mean(rulerarray)*10)/10
        print(ruler_final)
    else:
        ruler_final =25
    
    lb5.config(text = ruler_final);
    
#######################################预处理
    dst1=cv.fastNlMeansDenoisingColored(src,None,10,10,7,21)
    # cv2.imshow('GRAY_1',dst1)

    dst2=cv.fastNlMeansDenoisingColored(dst1,None,10,10,7,21)
    # cv2.imshow('GRAY_2',dst2)

    grayImage = cv.cvtColor(dst2,cv.COLOR_BGR2GRAY)
    # cv2.imshow('GRAY',grayImage)

    kernel = np.ones((3,3),np.uint8)
    blur = cv.GaussianBlur(grayImage,(5,5),0)
    # cv2.imshow("blur",blur)

    open1 = cv.morphologyEx(blur,cv.MORPH_OPEN,kernel)
    dst = cv.morphologyEx(open1,cv.MORPH_CLOSE,kernel)
    # cv2.imshow('dst',dst)


    ret,thresh1 = cv.threshold(dst,45,255,cv.THRESH_BINARY)
#     cv.imshow("imgth", thresh1) 


    cv.imwrite("line_a.png", thresh1)
###################################################显示图片2

    load = Image.open("line_a.png")
    w, h = load.size
    resized =resize(w, h, 270, 270, load)  
        
    render = ImageTk.PhotoImage(resized)
    img = tkinter.Label(image=render,width=270,height=270)
    img.image = render
    img.place(x=330, y=20)

    #####################################################




    img=cv.imread("line_a.png")
    # img = thresh1
    gray = cv.cvtColor(img, cv.COLOR_RGB2GRAY)
    img1 = cv.Canny(gray, 50, 150, apertureSize=3)
    cv.imshow('edges', img1)

    #返回图像的高和宽
    (h,w)=img1.shape
    print("h,w",h," ",w)
    Mat1 = img1.copy()


    #初始化一个跟图像宽一样长度的数组,用于记录每一列的黑点个数
    a =[0 for z in range(0,w)]

    for i in range(0,w):           
        for j in range(0,h):       
            if img1[j,i]==255:       #判断该点是否为黑点,0代表是黑点
                a[i]+=1            #该列的计数器加1
                Mat1[j,i]=0      #记录完后将其变为白色,即等于255
    for i in range(0,w):          
        for j in range(h-a[i],h):  #从该列应该变黑的最顶部的开始向最底部设为黑点
            Mat1[j,i]=255            #设为黑点

    # cv.imshow("Mat1",Mat1)

    img2 = img1.copy()

    flag = 1

    #纵向墙体的校正
    for x in range(0,w):
        if a[x]>50:
    #         print(x)
            for y in range(x-5,x+5):
                if a[x] < a[y]:
                    flag =0
            if flag == 1:
                for i in range(x-5,x+5):
                    for j in range(0,h):
                        if img2[j,i]>0:
                            img2[j,i]=0
                            img2[j,x]=255

    #删除横向墙体的像素
    for x in range(0,w):
        if a[x]<50:
            for j in range(0,h):
                if img2[j,x]>0:
                    img2[j,x]=0

    #排除竖切横向墙体得到的点的干扰
    for x in range(0,w):
        if a[x]>50:
            for j in range(0,h):
                if img2[j,x]>0 and img2[j+1,x]==0 and img2[j-1,x]==0:
                    for z1 in range(j+2,j+30):  #不能把该点本身计算在内
                        if img2 [z1,x]>0 and img2[z1+1,x]==0 and img2[z1-1,x]==0:
                            img2[z1,x]=0
                            img2[j,x]=0
    #
    #离散的点相连
    for x in range(0,w):
        if a[x]>50:
            for j in range(0,h):
                if img2[j,x]>0 and img2[j+1,x]==0:
                    for z1 in range(j,j+30):
                        if img2 [z1,x]>0:
                            for z2 in range(j,z1):
                                img2[z2,x]=255
    #墙体两条线转化为一条墙中心线
    cv.imshow("img2",img2)
    # img3 = img2.copy()
    img3 = np.zeros((h, w, 1), dtype=np.uint8)
    for x in range(0,w):
        if a[x]>50:
            for y in range(x+1,x+20):
                if a[y]>50:
                    mid = int(round((x+y)/2))
                    for j in range(0,h):
                        if img2[j,x]==img2[j,y] and img2[j,y]>0:
                            img3[j,mid]=255
    #                 mid = int((x+y)/2)
    #                 for i in range(x-1,y+1):
    #                     for j in range(0,h):
    #                         if img2[j,i]>0:
    # #                             img3[j,i]=0
    #                             img3[j,mid]=255

    #基于中心线 显示可能的连线  
    b = [0 for z in range(0,w)]
    for i in range(0,w):           
        for j in range(0,h):       
            if img3[j,i]==255: 
                b[i]+=1

    for x in range(0,w):
        if b[x]>50:
    #         print(x)
            for j in range(0,h):
                if img3[j,x]>0 and img3[j+1,x]==0:
                    for z1 in range(j,j+50):
                        if img3 [z1,x]>0:
                            for z2 in range(j,z1):
                                img3[z2,x]=255


    cv.imshow("img3",img3)


    MatI = img1.copy()


    #初始化一个跟图像宽一样长度的数组,用于记录每一列的黑点个数
    A =[0 for z in range(0,h)]

    for i in range(0,h):
        for j in range(0,w):
            if img1[i,j]==255:
                A[i]+=1
                MatI[i,j]=0      
    # for i in range(0,h):          
    #     for j in range(w-a[i],w):  #从该列应该变黑的最顶部的开始向最底部设为黑点
    #         MatI[i,j]=255            #设为黑点

    for i in range(0,h):          #遍历每一行
        for j in range(0,A[i]):   #从该行应该变黑的最左边的点开始向最右边的点设置黑点
            MatI[i,j]=255           #设置黑点

    # cv.imshow("MatI",MatI)

    imgII = img1.copy()

    flagI = 1

    #横向墙体的校正
    for x in range(0,h):
        if A[x]>50:
    #         print(x)
            for y in range(x-5,x+5):
                if A[x] < A[y]:
                    flagI =0
                if flagI == 1:
                    for i in range(x-5,x+5):
                        for j in range(0,w):
                            if imgII[i,j]>0:
    #                             count =count+1
                                imgII[i,j]=0
                                imgII[x,j]=255

    #删除纵向墙体的像素
    for x in range(0,h):
        if A[x]<50:
            for j in range(0,w):
                if imgII[x,j]>0:
                    imgII[x,j]=0

    # #排除竖切横向墙体得到的点的干扰|
    for x in range(0,h):
        if A[x]>50:
            for j in range(0,w):
                if imgII[x,j]>0 and imgII[x,j+1]==0 and (imgII[x,j-1] or imgII[x,j-2] or imgII[x,j-3] or imgII[x,j-4] or imgII[x,j-5])==0:
                    for z1 in range(j+2,j+50):  #不能把该点本身计算在内
                        if imgII[x,z1]>0 and imgII[x,z1-1]==0 and (imgII[x,z1+1] or imgII[x,z1+2] or imgII[x,z1+3] or imgII[x,z1+4] or imgII[x,z1+5])==0:
                            imgII[x,j]=0 
                            imgII[x,z1]=0


    #离散的点相连
    for x in range(0,h):
        if A[x]>50:
            for j in range(0,w):
                if imgII[x,j]>0 and imgII[x,j+1]==0:
                    for z1 in range(j,j+30):
                        if imgII[x,z1]>0:
                            for z2 in range(j,z1):
                                imgII[x,z2]=255


    #墙体两条线转化为一条墙中心线
#     cv.imshow("imgII",imgII)
    # imgIII = imgII.copy()
    imgIII = np.zeros((h, w, 1), dtype=np.uint8)
    for x in range(0,h):
        if A[x]>50:
            for y in range(x+1,x+10):
                if A[y]>50:
                    mid = int(round((x+y)/2))
                    for j in range(0,w):
                        if imgII[x,j]==imgII[y,j] and imgII[y,j]>0:
                            imgIII[mid,j]=255

    # 基于中心线 显示可能的连线  
    B = [0 for z in range(0,h)]
    for i in range(0,h):           
        for j in range(0,w):       
            if imgIII[i,j]==255: 
                B[i]+=1

    for x in range(0,h):
        if B[x]>30:
#             print(x)
            for j in range(0,w):
                if imgIII[x,j]>0 and imgIII[x,j+1]==0:
                    if (j+150)>w:
                        limit = w -j
                    else:
                        limit =j+150
                    for z1 in range(j+1,limit):
                        if imgIII[x,z1]>0:
                            for z2 in range(j,z1):
                                imgIII[x,z2]=255

    img4 =img3+imgIII
#     cv.imshow("img4",img4)

    cv.imwrite("area detect2.png", img4)
    ###################################################显示图片3

    load = Image.open("area detect2.png")
    w, h = load.size
    resized =resize(w, h, 270, 270, load)  
        
    render = ImageTk.PhotoImage(resized)
    img = tkinter.Label(image=render,width=270,height=270)
    img.image = render
    img.place(x=10, y=310)
    ##################################
    src = cv.imread("area detect2.png")
    src = cv.GaussianBlur(src, (3, 3), 0)
    gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
    ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV | cv.THRESH_OTSU)
#     cv.imshow("binary", binary)
    cv.imwrite('binary.png', binary)

    output = cv.connectedComponents(binary, connectivity=8, ltype=cv.CV_32S)
    num_labels = output[0]
    print(output[1].shape)
    print(num_labels)  # output: 5
    lables = output[1]

    count = []
    for i in range(num_labels):
        count.append(0)
        for row in range(h):
            for col in range(w):
                if lables[row,col] == i:
                    count[i] +=1
    print(count)  ##得到每个区域的像素点数量
    
    #########################################
    ruler_real =(ruler_final/1000)*(ruler_final/1000)
    for i in range(num_labels):
        if i >1:
            x_room = 730
            y_room = 330+30*(i-2)
            str_room = "room"+str(i-1)+" : "+str(round(count[i]*ruler_real*10)/10)
            lb_room = Label(root,text = str_room)
            lb_room.place(x=x_room, y=y_room)
    sum = 0
    for i in range(num_labels):
        if i >1:
            sum += count[i]
    x_room = 730
    y_room = 300
    str_room = "总面积: "+str(round(sum*ruler_real*10)/10)
    lb_room = Label(root,text = str_room)
    lb_room.place(x=x_room, y=y_room)

    #####################################################

    # 构造颜色
    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)
    colors[1] = (255, 255, 255)

    # 画出连通图
    h, w = gray.shape
    image = np.zeros((h, w, 3), dtype=np.uint8)
    for row in range(h):
        for col in range(w):
            image[row, col] = colors[lables[row, col]]

    cv.imshow("colored labels", image)
    cv.imwrite("lables.png", image)
    components = num_labels - 2
    print("total componets : ", components )
    ##显示房间数量
    lb6.config(text = components);

    img_lable =cv.imread('lables.png')
    # h2, w2 = img_lable.shape
    
    img_src =cv.imread('to_mix.png')
    # # load = Image.open(filename)
    # w, h = img_src.size
    # resized =resize(w, h, 300, 300, load)  


    img_lable2=cv.resize(img_lable,(500,500),interpolation=cv.INTER_CUBIC)
    img_src2=cv.resize(img_src,(500,500),interpolation=cv.INTER_CUBIC)

    img_mix = cv.addWeighted(img_lable2, 0.6, img_src2 , 0.4, 0)




    cv.imshow("mix",img_mix)
    cv.imwrite("mix.png",img_mix)
    ###################################################显示图片4

    load = Image.open("mix.png")
    w, h = load.size
    resized =resize(w, h, 270, 270, load)  
        
    render = ImageTk.PhotoImage(resized)
    img = tkinter.Label(image=render,width=270,height=270)
    img.image = render
    img.place(x=330, y=310)

    #####################################################
    

    ####################################################




    cv.waitKey(0)
    cv.destroyAllWindows()
        
        
        
        
#####################
#########################


lb = Label(root,text = '')
lb.pack(anchor="n")
lb2 = Label(root,text = '原图:')
lb2.place(x=5,y=5)
lb3 = Label(root,text = '比例尺系数(mm/pixel):')
lb3.place(x=700, y=100)
lb4 = Label(root,text = '房间数量(个):')
lb4.place(x=700, y=180)
lb4 = Label(root,text = '户型总面积(m2):')
lb4.place(x=700, y=260)

lb5 = Label(root,text = '')
lb5.place(x=730, y=140)
lb6 = Label(root,text = '')
lb6.place(x=730, y=220)

# lb7 = Label(root,text = '')
# lb7.place(x=730, y=300)

btn1 = Button(root,text="读取文件",width=20,command=xz)
btn1.place(x=700, y=510)
btn2 = Button(root,text="运行算法",width=20,command=run)
btn2.place(x=700, y=550)

root.mainloop()

参考文章

思路参考:
江州.基于形状与边缘特征的户型图识别研究[D].哈尔滨工业大学:2016
黄文.面向自动家装生成的户型图识别方法研究[D].合肥工业大学:2018
房型图户型图识别
复杂户型图处理
代码参考:
Python OpenCV - 从一组轮廓点外推最大的矩形
自适应阈值化
OpenCV图像处理-连通组件

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多