在作熱度圖的時候我們經常需要將熱度圖調整透明度后疊加在原圖上達到更好的展示效果。比如檢測人氣密度的熱度圖:
(來自sensetime)
一般作圖的時候會第一時間想到matplotlib,因為可以很方便作幾乎任何圖圖,但是最近發現用opencv也很容易執行這個操作。
1. 獲取人群密度
輸入一張圖片我們首先需要獲取里面有多少人以及每個人所在的位置信息。這個工作比較復雜,這里不展開講了,不過提一下集中模式:一種是人群密度不高的場景,可以利用行人檢測識別圖片中的人及其所在位置,這個方法有很多了,像MTCNN,RCNN,YOLO之類的方法都能用;另一種是廣場高密度人群的場景,這樣的話因為人與人之間的重疊度很高,大部分人是檢測不到的,需要利用檢測人頭或者用這篇文章的方法來提高檢測精度。
2. 熱度圖的梯度設置
熱力圖的顏色主要表現的就是這個點代表的人群密度,密度越高,顏色越深。之前我們獲取的是檢測到的人的bounding box的中心的坐標,那么獲取每個點的熱度有一下兩種方法:1. 設定半徑,計算每個像素點在此半徑內所包含的人所在的點的數量,此數量即代表詞典的熱度;2. 設定半徑,以每個人所在的點以此半徑設定熱度梯度,如果兩個點有交叉將交叉區域的熱度疊加。第一種方法如果圖片比較大的時候會很慢,所以一般用第二種。
3. 根據熱度分配像素顏色
所謂的熱度圖就是根據所在位置的值的大小從顏色梯度提取合適的顏色。獲取顏色梯度可以通過matplotlib的get_cmap函數,也可以用colour包下的Color函數自己定義(當然也可以用matplotlib定義,就是有點麻煩)。
下面的例子就是用上述2里面第一種結合get_cmap/Color函數將每個像素的熱度轉換成相應的顏色:
def density_heatmap(image, box_centers, radias=100):
import matplotlib.pyplot as plt
from colour import Color
density_range = 100
gradient = np.linspace(0, 1, density_range)
img_width = image.shape[1]
img_height = image.shape[0]
density_map = np.zeros((img_height, img_width))
color_map = np.empty([img_height, img_width, 3], dtype=int)
# get gradient color using rainbow
cmap = plt.get_cmap("rainbow") # 使用matplotlib獲取顏色梯度
blue = Color("blue") # 使用Color來生成顏色梯度
hex_colors = list(blue.range_to(Color("red"), density_range))
rgb_colors = [[rgb * 255 for rgb in color.rgb] for color in hex_colors][::-1]
for i in range(img_height):
for j in range(img_width):
for box in box_centers:
dist = distance.euclidean(box, (j, i))
if dist <= radias * 0.25:
density_map[i][j] += 10
elif dist <= radias:
density_map[i][j] += (radias - dist) / (radias * 0.75) * 10
ratio = min(density_range-1, int(density_map[i][j]))
for k in range(3):
# color_map[i][j][k] = int(cmap(gradient[ratio])[:3][k]*255)
color_map[i][j][k] = rgb_colors[ratio][k]
return color_map
但是利用heatmap包可以很方便用2里面的第二種方式畫熱度圖:
def use_heatmap(image, box_centers):
import heatmap
hm = heatmap.Heatmap()
box_centers = [(i, image.shape[0] - j) for i, j in box_centers]
img = hm.heatmap(box_centers, dotsize=200, size=(image.shape[1], image.shape[0]), opacity=128, area=((0, 0), (image.shape[1], image.shape[0])))
return img
4. 將熱度圖調整透明度覆蓋到原圖上
利用opencv函數(opencv的安裝可以參考這里)里面的addWeighted函數可以實現。
frame = cv2.imread(img) # origin image
heatmap = cv2.imread(hm) # heatmap image
overlay = frame.copy()
alpha = 0.5 # 設置覆蓋圖片的透明度
cv2.rectangle(overlay, (0, 0), (frame.shape[1], frame.shape[0]), (255, 0, 0), -1) # 設置藍色為熱度圖基本色
cv2.addWeighted(overlay, alpha, frame, 1-alpha, 0, frame) # 將背景熱度圖覆蓋到原圖
cv2.addWeighted(heatmap, alpha, frame, 1-alpha, 0, frame) # 將熱度圖覆蓋到原圖
cv2.imshow('frame', frame)
cv2.waitKey(0)
cv2.addWeighted
有6個參數(具體可以看這里):第一個為需要疊加的表層圖片;第二個為疊加圖片的透明度,越接近1越不透明;第三個為疊加的底層圖片;第4個為底層圖片的透明度,為1-alpha;第5個為一個標量直接加在兩張圖片的加權和上面,一般直接設為0就行;第6個為最后的加權和操作后的輸出的目標圖片。
通過上述操作就可以拿到所需的熱度圖了。
參考:
http://docs.opencv.org/3.0-beta/doc/py_tutorials/py_core/py_image_arithmetics/py_image_arithmetics.html
http://jjguy.com/heatmap/
https://pypi.python.org/pypi/colour/
https://matplotlib.org/examples/color/colormaps_reference.html
http://www.pyimagesearch.com/2016/03/07/transparent-overlays-with-opencv/
http://bsou.io/posts/color-gradients-with-python