1

I have a urban plan picture as follows:

Urban Plans

I want to detect the color blocks in the image and label them with different lands utilities, for example, green area for lawn, pink for residence area, light blue for commercial areas, etc. and finally, if possible transform from png picture to shape file for ArcGis use. Please share your ideas, thanks. I have tried with OpenCV Canny edge detection but still far from what I need:

import cv2
import numpy as np  

img = cv2.imread("test.png", 0)

img = cv2.GaussianBlur(img,(3,3),0)
canny = cv2.Canny(img, 50, 150)

cv2.imshow('Canny', canny)
cv2.waitKey(0)
cv2.destroyAllWindows()

Canny edge detection

ah bon
  • 9,293
  • 12
  • 65
  • 148
  • 1
    are the colors perfect (one exact color value and lossless compression)? If yes, you could threshold each individual HSV or RGB color value individually and just use findCountours to get the areas. – Micka Jan 05 '19 at 11:56
  • 1
    Here is a HSV colormap for better understanding: [Choosing the correct upper and lower HSV boundaries for color detection with`cv::inRange` (OpenCV)](https://stackoverflow.com/questions/10948589/choosing-the-correct-upper-and-lower-hsv-boundaries-for-color-detection-withcv/48367205#48367205). – Kinght 金 Jan 06 '19 at 09:34

2 Answers2

5

As @Micka says, your image is very easily separable on color. I've provided code below that does this for the dark green. You can easily edit the color selectors to get the other colors.

Note that there are pixel artifacts in your image, probably due to compression. The current result seems fine, but I hope you have access to full quality images - then result will be best.

The image is converted to the HSV-colorspace (image) to make selecting colors easier. (openCV) findContours returns a list that holds the coordinates around the border for every shape found.

I know nothing of shapefiles, but maybe this could be of some use.

Result:

enter image description here

Code:

# load image
img = cv2.imread("city.png")
# add blur because of pixel artefacts 
img = cv2.GaussianBlur(img, (5, 5),5)
# convert to HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) 
# set lower and upper color limits
lower_val = (40, 100, 100)
upper_val = (60,255,200)
# Threshold the HSV image to get only green colors
mask = cv2.inRange(hsv, lower_val, upper_val)
# apply mask to original image
res = cv2.bitwise_and(img,img, mask= mask)
#show imag
cv2.imshow("Result", res)
# detect contours in image
im2, contours, hierarchy = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# draw filled contour on result
for cnt in contours:
    cv2.drawContours(res, [cnt], 0, (0,0,255), 2)
# detect edges in mask
edges = cv2.Canny(mask,100,100)
# to save an image use cv2.imwrite('filename.png',img)
#show images
cv2.imshow("Result_with_contours", res)
cv2.imshow("Mask", mask)
cv2.imshow("Edges", edges)
cv2.waitKey(0)
cv2.destroyAllWindows()
J.D.
  • 4,511
  • 2
  • 7
  • 20
  • Hi @J.D. ,I need to know colors name for pixel values for example [255 255 255] is equals to white.and asking to you because you had used the pixel limit for green colors – MathanKumar Apr 27 '20 at 12:23
  • You can do a websearch for 'color picker'. You can find these for rgb, hsv, all colorspaces. Then using the color you want defined, for example 'gold', move the settings until you find acceptable boundaries. – J.D. Apr 27 '20 at 19:48
  • Actually i did extracted the pixel values of each color in image .Now i have to find its name not hex value!!. need help in this case – MathanKumar Apr 28 '20 at 06:11
  • Check the pixel color against all the ranges you defined. You can use the process described [here](https://stackoverflow.com/a/58288791/10699171) as a starting point. If it is still unclear, it's best to open a new question ;) – J.D. Apr 28 '20 at 08:00
2

I work in Jupyter Notebook. First download your image:

!wget https://i.stack.imgur.com/SJxo3.png

Then create an RGBA array from your pic:

import numpy as np
from PIL import Image
import matplotlib.pyplot as plt

img = Image.open('SJxo3.png').convert('RGBA')
arr = np.array(img)

We want to have a set of different colors on your pic so we create a set:

colors=set()
for each in arr:
    for EACH in each:
        colors.add(tuple(EACH.tolist()))

We want to loop over these colors and select the area in which each color is present. We start with:

for index, each in enumerate(colors):

Now each color is a tuple in this for loop is currently a tuple and we want a list so we do:

color=[]
for EACH in each:
    color.append(EACH)

We now create an array containing booleans, True if the corresponding RGBA component is the same as the color we are currently inspecting:

boolarr=[]
for eachinarr2 in [arr == color]:
    boolarr.append(eachinarr2)

Then we choose those pixels which are the same as the color we currently inspecting, ie all of the four components of RGBA is matching (so we have the same color). We store these pixel cordinates in indexx and INDEXX.

featurepixels=[]
for indexx, eachh in enumerate(boolarr[0]):
    for INDEXX, EACHH in enumerate(eachh):
        if EACHH.all() == True:
            featurepixels.append([indexx, INDEXX])

Now we create a grid of zeros:

grid = np.zeros((len(arr[0]),len(arr)))

We change the value of this full-of-zeros grid to 1 in places we have a pixel from the specific color we are inspecting:

for eachhh in featurepixels:
    grid[eachhh[1],eachhh[0]] = 1

We then create a colormap of the pixels which have the same color, effectively selecting that part of the picture:

plt.figure()
plt.pcolormesh(grid)

Putting all these together:

!wget https://i.stack.imgur.com/SJxo3.png

import numpy as np
from PIL import Image
import matplotlib.pyplot as plt

img = Image.open('SJxo3.png').convert('RGBA')
arr = np.array(img)

colors=set()
for eachH in arr:
    for eACH in eachH:
        colors.add(tuple(eACH.tolist()))

for index, each in enumerate(colors):

    if index < 30: # for debugging

        color=[]
        for EACH in each:
            color.append(EACH)



        boolarr=[]
        for eachinarr2 in [arr == color]:
            boolarr.append(eachinarr2)

        featurepixels=[]
        for indexx, eachh in enumerate(boolarr[0]):
            for INDEXX, EACHH in enumerate(eachh):
                if EACHH.all() == True:
                    featurepixels.append([indexx, INDEXX])



        grid = np.zeros((len(arr[0]),len(arr)))

        for eachhh in featurepixels:
            grid[eachhh[1],eachhh[0]] = 1

        plt.figure()
        plt.pcolormesh(grid)

From here you can create different color groups, so more pixels will be classified as being in the same feature. In this current version, one tiny difference in pixel color cause it to be classified as a separate feature. So I suggest creating those color groups/categories. Or use image where the number of colors is low, and a single feature consists only the same colors. Hope this helps.

zabop
  • 6,750
  • 3
  • 39
  • 84
  • 1
    Thanks for your helps. Just tried my outputs are several purple pictures with some yellow points, is it same result for you? – ah bon Jan 05 '19 at 17:19
  • Wlcm. For most colors yes. I found one color for which the output was massive yellow region in the purple background. – zabop Jan 05 '19 at 17:36
  • So if you choose the colors well, then you can get a large area selected. – zabop Jan 05 '19 at 17:37
  • Also probably worthy to check the other answer as well (I didn't but the number of votes suggests it is pretty good.) – zabop Jan 05 '19 at 17:38