2018年3月29日 星期四

TensorFlow的第一步

請參考原始資料:
https://colab.research.google.com/notebooks/mlcc/first_steps_with_tensor_flow.ipynb?hl=en#scrollTo=EL8-9d4ZJNR7
(我對原資料的理解不一定正確,請自行修正)

要運用tensorflow 的第步驟如下:

第一步:定義特性及設定特性行位

  首先我們要告訴 tensorflow 所謂的 「特性行位」feature column是資料內的那一行。tensorflow 才會知道從那些資料去進行分析。而所謂的特性行位的資料行,大致分成兩種:
一是類別型資料(Catagorical Data),例如這行的資料都是形容詞:大中小、紅黃綠、方扁圓 等等。
另一種是數字型資料(Numerical Data),例如面積:30米平方、60米平方、90米平方,RGB:(200,179,87),時間差:20秒,90秒,45秒 等等很明確的數字資料。
在TensorFlow 內,透過 feature column 的結構來指定特性行位,而這個feature column 只儲存特性行位的此行的說明,並不會儲存此行的所有資料。
在本範例,我們只用資料內的一行 total_rooms 來做為特性行位,下面兩行,第一行是把其中的 total_rooms 從 california_housing_dataframe 取出來,存成 my_feature, 第二行則是將 my_feature指定給feature_column,並設定為數字型資料numeric_column:

# Define the input feature: total_rooms.
my_feature = california_housing_dataframe[["total_rooms"]]

# Configure a numeric feature column for total_rooms.
feature_columns = [tf.feature_column.numeric_column("total_rooms")]

你可以參考 tensorflow 對 feature_column.numeric_column 的說明,它其實包含了其他的參數,但在這裡,先簡化,用一行來示範即可。

第二步:定義目標

再來我們要定義目標,即,要估算的的目標是為何,給了特性和目標給tensorflow去學習,tensorflow 才可以依據新的feature 來推算目標值可能為何。在這範例中,我們要估的是 Median_house_value:

# Define the label.
targets = california_housing_dataframe["median_house_value"]

第三步:設定線性迴歸(LinearRegressor)

在學習使用tensorflow 的範例裡,我們用較單純的線性迴歸方法來做學習。並使用 GradientDescentOptimizer (即使用 Mini-Batch Stochastic Gradient Descent(SGD)). learning_rate 參數可以控制推估步驟的大小。同時我們也使用透過 clip_gradients_by_norm 函式來使用 gradient clipping方法,以避免誤差越算越大,造成估算錯誤。

# Use gradient descent as the optimizer for training the model.
my_optimizer=tf.train.GradientDescentOptimizer(learning_rate=0.0000001)
my_optimizer = tf.contrib.estimator.clip_gradients_by_norm(my_optimizer, 5.0)

# Configure the linear regression model with our feature columns and optimizer.
# Set a learning rate of 0.0000001 for Gradient Descent.
linear_regressor = tf.estimator.LinearRegressor(
    feature_columns=feature_columns,
    optimizer=my_optimizer
)

第四步:設定輸入的部分

在匯入 California housing data 到 LinearRegressor 函式內之前,我們必須先定設定好輸入函式的部分,好告訴 tensorflow 如何處理匯進來的資料,好在學習的過程中進行如何分批次,打散資料,重覆這些動作等等。
記得嗎?我們在第一步驟只是先定義特性行的名稱,資料並還沒匯入喔。
所以,首先,我們必須把pandas 的 特性資料 轉成 numpy 的 array 型式的 dict. 然後我們就可以用 TensorFlow 的 Dataset API 來建構 dataset 物件,再根據給定的 batch_size 來將資料分散成幾個群組(batches), 以便用來重覆執行每次的學習(num_epochs)。(如果指定 num_epochs=None 給 repeat(), 那輸入資料將會被無止䀆地重覆?
再來,如果 shuffle 設為 True, 那在訓練期間的資料匯入時就會被打散,這樣資料會比較平均一些。buffer_size 參數則是用來指定打散後匯進 dataset 的資料量大小。
最後,我們的輸入函式會建構一個 iterator 給 dataset, 並傳回下一個要處理的批次資料給 LinearRegressor.

def my_input_fn(features, targets, batch_size=1, shuffle=True, num_epochs=None):
    """Trains a linear regression model of one feature.
  
    Args:
      features: pandas DataFrame of features
      targets: pandas DataFrame of targets
      batch_size: Size of batches to be passed to the model
      shuffle: True or False. Whether to shuffle the data.
      num_epochs: Number of epochs for which data should be repeated. None = repeat indefinitely
    Returns:
      Tuple of (features, labels) for next data batch
    """
  
    # Convert pandas data into a dict of np arrays.
    features = {key:np.array(value) for key,value in dict(features).items()}                                           
    # Construct a dataset, and configure batching/repeating
    ds = Dataset.from_tensor_slices((features,targets)) # warning: 2GB limit
    ds = ds.batch(batch_size).repeat(num_epochs)
    
    # Shuffle the data, if specified
    if shuffle:
      ds = ds.shuffle(buffer_size=10000)
    
    # Return the next batch of data
    features, labels = ds.make_one_shot_iterator().get_next()
    return features, labels

第五步:開始訓練

至此,我們可以準備呼叫在 linear_regressor 的 train() 來訓練了。
_ = linear_regressor.train(
    input_fn = lambda:my_input_fn(my_feature, targets),
    steps=100
) 

第六步:評估此模型

我們可以開始針對訓練來測試訓練結果,看看我們的模型是否符合我們的訓練設定。
# Create an input function for predictions.
# Note: Since we're making just one prediction for each example, we don't
# need to repeat or shuffle the data here.
prediction_input_fn =lambda: my_input_fn(my_feature, targets, num_epochs=1, shuffle=False)

# Call predict() on the linear_regressor to make predictions.
predictions = linear_regressor.predict(input_fn=prediction_input_fn)

# Format predictions as a NumPy array, so we can calculate error metrics.
predictions = np.array([item['predictions'][0] for item in predictions])

# Print Mean Squared Error and Root Mean Squared Error.
mean_squared_error = metrics.mean_squared_error(predictions, targets)
root_mean_squared_error = math.sqrt(mean_squared_error)
print ("Mean Squared Error (on training data): {}".format(mean_squared_error))
print ("Root Mean Squared Error (on training data): {}".format(root_mean_squared_error))

Mean Squared Error (on training data): 56367.025 Root Mean Squared Error (on training data): 237.417

這是好的估算模型嗎?我們怎麼判定誤差是多少才算 好?
Mean Squared Error(MSE 均方差) 並不好判讀,所以可以改成 Root Mean Squared Error(RMSE 均方差根) 來判讀,均方差根的好處是不會受範圍的影響。
我們可以比較一下 target 中最大和最小值的 median_house_value 的 RMSE 的差異:
min_house_value = california_housing_dataframe["median_house_value"].min() 
max_house_value = california_housing_dataframe["median_house_value"].max() 
min_max_difference = max_house_value - min_house_value 
print ("Min. Median House Value: {}".format(min_house_value)) 
print ("Max. Median House Value: {}".format(max_house_value)) 
print ("Difference between Min. and Max.: {}".format(min_max_difference)) 
print ("Root Mean Squared Error: {}".format(root_mean_squared_error))

Min. Median House Value: 14.999 Max. Median House Value: 500.001 Difference between Min. and Max.: 485.002 Root Mean Squared Error: 237.417

我們的誤差值大約落在目標值的一半之間。
讓我們再來改個基本架構,看能不能減少誤差。
首先來看看我們的估計值有多接近目標值:

calibration_data = pd.DataFrame() 
calibration_data["predictions"] = pd.Series(predictions) 
calibration_data["targets"] = pd.Series(targets) 
calibration_data.describe()

predictionstargets
count17000.017000.0
mean0.1207.3
std0.1116.0
min0.015.0
25%0.1119.4
50%0.1180.4
75%0.2265.0
max1.9500.0

這樣的資訊(說實在的,我不懂)也許夠用了,我們來把它視覺化看看。
首先,我們胡亂取一些sample 的資料,好畫一張看得懂的分佈圖。

sample = california_housing_dataframe.sample(n=300)

然後,再用前面(?)學的步驟,將分佈狀況和特性權重 把它畫在分佈圖上,線會用紅色呈現。

# Get the min and max total_rooms values. 
x_0 = sample["total_rooms"].min() 
x_1 = sample["total_rooms"].max() 

# Retrieve the final weight and bias generated during training. 
weight = linear_regressor.get_variable_value('linear/linear_model/total_rooms/weights')[0] 
bias = linear_regressor.get_variable_value('linear/linear_model/bias_weights') 

# Get the predicted median_house_values for the min and max total_rooms values. 
y_0 = weight * x_0 + bias 
y_1 = weight * x_1 + bias 

# Plot our regression line from (x_0, y_0) to (x_1, y_1). 
plt.plot([x_0, x_1], [y_0, y_1], c='r') 

# Label the graph axes. 
plt.ylabel("median_house_value") 
plt.xlabel("total_rooms") 

# Plot a scatter plot from our data sample. 
plt.scatter(sample["total_rooms"], sample["median_house_value"]) 
# Display graph. 
plt.show()


嗯嗯,不太懂那條紅色代表 RMSE 的什麼意思⋯⋯ 😅

再進階一下:

調整這個模型的一些參數⋯⋯

def train_model(learning_rate, steps, batch_size, input_feature="total_rooms"):
  """Trains a linear regression model of one feature.
  
  Args:
    learning_rate: A `float`, the learning rate.
    steps: A non-zero `int`, the total number of training steps. A training step
      consists of a forward and backward pass using a single batch.
    batch_size: A non-zero `int`, the batch size.
    input_feature: A `string` specifying a column from `california_housing_dataframe`
      to use as input feature.
  """
  
  periods = 10
  steps_per_period = steps / periods

  my_feature = input_feature
  my_feature_data = california_housing_dataframe[[my_feature]]
  my_label = "median_house_value"
  targets = california_housing_dataframe[my_label]

  # Create feature columns
  feature_columns = [tf.feature_column.numeric_column(my_feature)]
  
  # Create input functions
  training_input_fn = lambda:my_input_fn(my_feature_data, targets, batch_size=batch_size)
  prediction_input_fn = lambda: my_input_fn(my_feature_data, targets, num_epochs=1, shuffle=False)
  
  # Create a linear regressor object.
  my_optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)
  my_optimizer = tf.contrib.estimator.clip_gradients_by_norm(my_optimizer, 5.0)
  linear_regressor = tf.estimator.LinearRegressor(
      feature_columns=feature_columns,
      optimizer=my_optimizer
  )

  # Set up to plot the state of our model's line each period.
  plt.figure(figsize=(15, 6))
  plt.subplot(1, 2, 1)
  plt.title("Learned Line by Period")
  plt.ylabel(my_label)
  plt.xlabel(my_feature)
  sample = california_housing_dataframe.sample(n=300)
  plt.scatter(sample[my_feature], sample[my_label])
  colors = [cm.coolwarm(x) for x in np.linspace(-1, 1, periods)]

  # Train the model, but do so inside a loop so that we can periodically assess
  # loss metrics.
  print "Training model..."
  print "RMSE (on training data):"
  root_mean_squared_errors = []
  for period in range (0, periods):
    # Train the model, starting from the prior state.
    linear_regressor.train(
        input_fn=training_input_fn,
        steps=steps_per_period
    )
    # Take a break and compute predictions.
    predictions = linear_regressor.predict(input_fn=prediction_input_fn)
    predictions = np.array([item['predictions'][0] for item in predictions])
    
    # Compute loss.
    root_mean_squared_error = math.sqrt(
        metrics.mean_squared_error(predictions, targets))
    # Occasionally print the current loss.
    print "  period %02d : %0.2f" % (period, root_mean_squared_error)
    # Add the loss metrics from this period to our list.
    root_mean_squared_errors.append(root_mean_squared_error)
    # Finally, track the weights and biases over time.
    # Apply some math to ensure that the data and line are plotted neatly.
    y_extents = np.array([0, sample[my_label].max()])
    
    weight = linear_regressor.get_variable_value('linear/linear_model/%s/weights' % input_feature)[0]
    bias = linear_regressor.get_variable_value('linear/linear_model/bias_weights')

    x_extents = (y_extents - bias) / weight
    x_extents = np.maximum(np.minimum(x_extents,
                                      sample[my_feature].max()),
                           sample[my_feature].min())
    y_extents = weight * x_extents + bias
    plt.plot(x_extents, y_extents, color=colors[period]) 
  print "Model training finished."

  # Output a graph of loss metrics over periods.
  plt.subplot(1, 2, 2)
  plt.ylabel('RMSE')
  plt.xlabel('Periods')
  plt.title("Root Mean Squared Error vs. Periods")
  plt.tight_layout()
  plt.plot(root_mean_squared_errors)

  # Output a table with calibration data.
  calibration_data = pd.DataFrame()
  calibration_data["predictions"] = pd.Series(predictions)
  calibration_data["targets"] = pd.Series(targets)
  display.display(calibration_data.describe())

  print "Final RMSE (on training data): %0.2f" % root_mean_squared_error

調整一:

調整參數以改進耗損並接近目標分布狀況。 如果⋯⋯超過跑超過5分鐘以上,表示 RMSE 被你操掛了,要重新調整一下參數。

train_model(
    learning_rate=0.00001,
    steps=100,
    batch_size=1
)


2018年3月20日 星期二

程式設計的開發者介面

半路出家的和尚,實在說不出什麼哲理。
但既然每次學習都要用到,還是自己記錄一下好了。
初學python ,就是使用python install 後的 launch. 但launch 的功能太兩光,所以上網找看看有沒有免費又好用的IDE(Interactive Development Environment), 中文稱為「互動式開發環境」。沒有IDE時,我們必須先在某個editor 編輯好程式後,再在editor 外的環境進行編釋或執行,editor 一般也沒有針對特殊程式語言進行關鍵字標示或語法錯誤的提示,因此非常地不方便,但這就是以前的開發程式的環境。後來有了IDE的概念後,程式開發變得比較友善了。
python 下的開發環境也有不少,包括像pyCharm 等,但大多要$$,在這個自由的世界,當然要找個自由的軟體,於是找到了 Atom , 這是一個相容於Windows/Mac OS/Linux 的自由軟體。當然MS 也提供了一個免費的IDE Visual Studio Code, 看來和 Atom 很類似,但因為對MS 的印象不好,所以沒試過VSC。

Atom 提供了自行修改的靈活度,可讓User 自行調整相關參數,以更符合自己的使用需求。同時它也提供了內建的套件管理,這些套件都各有優點,包括將python 語法錯誤特別標示的功能,python 自動定位的功能,將程式編列更整齊的套件等等,讓我在coding 時,可以一目瞭然。同時也支援 GitHub, 讓你可以做好版本管理,但我還是沒搞懂怎麼運作。最近的功能是提供協同作業,讓團隊可以一起編輯程式,這個很cool,只是我都是一個人作業,沒能用上。

在python 上,很多machine learning 或AI 的學習都支援python, 而其中另一個介面是 Jupyter。但我不想再裝一套介面⋯⋯畢竟6 年前MBP HD 才 256G,能減少重覆功能的程式是最好的。還好,Atom 也提供了 Jupyter 類似功能的套件 - Hygrogen。 Hydrogen 提供了在Atom 內類似Jupyter 的即時觀看結果,和圖形結果的介面內顯示,讓你可以將mathlab 的圖形直接在語言窗格內呈現,不用再另外跳出一個視窗。也提供了 cell 的執行,即單一或幾行的 程式執行,省去每次都要重頭執行的困擾。


我的Atom 介面



還有一個特別的套件 叫 platformio 要特別介紹一下,這是一個支援多種MCU 開發板的開發介面,不用再使用Arduino 的不方便介面,而且除了Arduino 還支援 ESP8266, STM32 等等,同時提供 terminal 介面,真的很方便。

PlatformIO package
當然也支援conda 的環境。Anaconda 是一套提供python 及 環境的另一套介面程式,但我覺得跑起來有點慢,所以只使用了miniconda 的環境。另外也裝了intel 版的python,intel 的說法是,它針對 python 的AI 相關package 有做修正,執行速度更快(如Numpy, matlab, scimitar-learn 等)。
可以看到我的Python 是 intel 版的

要在Atom 內使用conda, 可以先安裝miniconda 後,再建立虛擬環境。
Intel 版的 python 可以在這裡找到,你可以裝單機版的,也可以裝 conda 版的,我是使用conda 版本,這裡有完整說明安裝方法。
在裝好conda 版的 intel python 虛擬環境後,再確認一下 hydrogen 的環境可不可正常work。
同樣可以在conda 的環境安裝ipython(互動式python)

conda create -n ipykernel_py2 python=2 ipykernel
source activate ipykernel_py2    # On Windows, remove the word 'source'
python -m ipykernel install --user
其中最後一行,是起動 ipykernel 的重要步驟,沒跑這行,你的 Atom 只會呼叫原始python 版本,不會使用你的ipython kernel 和 intel 版 python.

以上記錄,希望自己不會忘記~

2018年3月19日 星期一

物件辨識學習 - 3

找到一個網頁,用另一個方法來學習 「阿拉伯數字」的辨識(不敢單寫「數字」,怕有人會和 中國用語 搞混)
是由 Bikramjot 寫的。
它用的是 MNIST 所提供的影像資料來做學習,MNIST 的database 提供數種不同的圖型資料庫,以不同的格式提供。其中 MNIST database of handwritten digits 提供了70000個手寫數字的圖像,以利AI 學programer 進行利用。
作者將程式分成兩部分,一是AI 學習的部分,一是學習後辨識的部分。整個程式並做了詳細的說明。學習的部分匯整如下:

# coding=utf-8
# This program is adopted from http://hanzratech.in/2015/02/24/handwritten-digit-recognition-using-opencv-sklearn-and-python.html
# which uses another module called scikit-learn that you have to install it first.
# in python, just use pip install -U scikit-sklearn
# in conda, use conda install scikit-learn
# and dont forget to install skimage module by conda install scikit-image
# import the modules
#
from sklearn.externals import joblib
from sklearn import datasets
from skimage.feature import hog
from sklearn.svm import LinearSVC
import numpy as np

# firstable, we ask program to fetch the MNIST database for handwritten digits.
# It's about 55.4M
print "Fetching MNIST Original datase for handwritten ditigs..."
print "It may take a while...會花一點時間"
dataset = datasets.fetch_mldata("MNIST Original",
                                data_home="/Volumes/64G/python3-learning/objRecProg/digits-classfication-1/scikit_learn_data")

# Then we will save the images of the digits in a numpy array features
# and the corresponding labels.
print dataset
features = np.array(dataset.data, 'int16')
labels = np.array(dataset.target, 'int')

# Next, we calculate the HOG features for each image in the database
# and save them in another numpy array named hog_feature.
print "Calculating HOG features and save them in to numpy array..."
list_hog_fd = []
for feature in features:
    fd = hog(feature.reshape((28, 28)), orientations=9,
             pixels_per_cell=(14, 14), cells_per_block=(1, 1), visualise=False)
    list_hog_fd.append(fd)
hog_features = np.array(list_hog_fd, 'float64')

# The next step is to create a Linear SVM object. Since there are 10 digits,
# we need a multi-class classifier.
# The Linear SVM that comes with sklearn can perform multi-class classification.
print "Classfying the features with LinearSVC..."
clf = LinearSVC()

# We preform the training using the fit member function of the clf object.
# The fit function required 2 arguments, one an array of the HOG features of
# the handwritten digit that we calculated earlier and a corresponding array of labels.
# Each label value is from the set , [0, 1, 2, 3,..., 8, 9].
print "Mapping HOG features with related labels..."
clf.fit(hog_features, labels)

# When the training finishes, we will save the classifier in a file named digits_cls.pkl
#  as shown in the code below.
print "Compressing trained data into a file named digits_cls.pkl"
joblib.dump(clf, "digits_cls.pkl", compress=3)

# which, compress: integer for 0 to 9, optional
# Optional compression level for the data. 0 is no compression. Higher means more compression,
# but also slower read and write times. Using a value of 3 is often a good compromise.

#Up till this point, we have successfully completed the first task of preparing our classifier.


作者也是用 HOG 的模式去進行學習,並將學習結果以LinearSVC 進行分類(classify),再將結果存進digits_cls.pkl , 存檔的好處是,以後要拿別的圖來辨別時,就用不再重新學一次。聽來不錯。當然還是會有缺點,那就是學習程度只到某個程度而己,因為沒有再學習新的圖形⋯⋯雖然重學也許會更差就是⋯⋯

-----

進行辨識的程式則是另外寫,整個程式的說明也很清楚,但要特別注意的是可能會產生一個error:
OpenCV Error: Assertion failed (ssize.width > 0 && ssize.height > 0) in resize, file /opt/concourse/work
er/volumes/live/3b96f7c7-93a4-48c6-665d-2f7ff1dac914/volume/opencv_1512680443756/work/modules/imgproc/sr
c/resize.cpp, line 3289
Traceback (most recent call last):
  File "testingTheClassifier.py", line 59, in <module>
    roi = cv2.resize(roi, (28, 28), interpolation=cv2.INTER_AREA)
cv2.error: /opt/concourse/worker/volumes/live/3b96f7c7-93a4-48c6-665d-2f7ff1dac914/volume/opencv_1512680
443756/work/modules/imgproc/src/resize.cpp:3289: error: (-215) ssize.width > 0 && ssize.height > 0 in fu
nction resize

我一開始上網查,也以為是因為imread 進來辨識用的圖檔是空的,才會造成 這個 error,後來慢慢往回追,才發現,原來在第18行的 cv2.threshold 來進行顏色2元化時,即數值高於 thresh 時,則將其改為 maxValue. 但作者原來訂的 90, 會造成影像辨識錯誤,所以會有上述error.
我不確定原因為何,是cv2 的更版,還是使用的電腦不一樣所造成,我用 90 是會造成上述error的。經過調整後70 就可以正常辨識並顯示出來。
可惜,仍不知為何,最後兩個字的辨識仍不正確。
數字辨識結果
上圖是我稍微修改原程式的內容,紅線是用 cv2.drawContours 來畫出原圖最接近方塊的外框。

可以看到最後兩個數字 5,9 被辨識成3,8 。 這差很多,和作者的結果不一樣。我猜,threshold 的值有差吧。


2018年3月9日 星期五

物件辨識學習 - 2

看了 openCV 的官方文件,大致了解 openCV 在物件辨識的能力,包括用SIFT、SURF、FAST、BRIEF、ORB等特性尋找方法,其中ORB 應該是最快的吧? HARRIS,SIFT,SURF 等都可以精確地辨識,但以openCV 的說明,這些都太耗記憶體,對於embeded system 不利,所以他們(openCV LABs)又找出一個可以更快的方式來找出圖形特性。

至於比對的方法,官方文件就提 Brute-Force(暴力比對,因為就是一個一個試), 速度上應該會很慢,所以我就沒特別看。另一個就是FLANN 比對法,基本上要用FLANN 要給定不同的參數,例如如果用SIFT 那就要:

# FLANN parameters
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks=50)   # or pass empty dictionary
flann = cv2.FlannBasedMatcher(index_params,search_params)
matches = flann.knnMatch(des1,des2,k=2)

但如果是用ORB,參數就不一樣:
FLANN_INDEX_LSH = 6
index_params = dict(algorithm=FLANN_INDEX_LSH,
                    table_number=6,   # 12
                    key_size=12,      # 20
                    multi_probe_level=1) # 2
search_params = dict(checks=50)   # or pass empty dictionary
flann = cv2.FlannBasedMatcher(index_params,search_params)
matches = flann.knnMatch(des1, des2, k=2)

我用6張蝦子的圖來測試,以圖6為 trainImage, 圖1-5為QueryImage,
其中SIFT 方法都有對應點,但可惜對應點都不對。例如頭對到尾,或對去不同蝦子。
但以ORB方法比對,有2張圖完全比對不出來,有2張圖比對點不對,但有一張圖的比對點準確率應該有9成以上,我覺得可以⋯⋯哈哈哈。

再回到上一篇用findHomography 的方法來找物體,大概看了一下,這篇對Homography(全像攝影)有比較淺顯的介紹。大概就是原始物件圖可以透過矩陣轉換來算出其投射在其他平面的樣貌,這個轉換可以findHomography 得出 H,所以原圖可以透過H 換算成旋轉和縮放的新
圖。上一篇用findHomography 的部分,就是用來找出新圖經轉換後的位置,以畫出綠框。
即以下:
if(len(goodMatch) > MIN_MATCH_COUNT):
        tp = []
        qp = []
        for m in goodMatch:
            tp.append(trainKP[m.trainIdx].pt)
            qp.append(queryKP[m.queryIdx].pt)
        tp, qp = np.float32((tp, qp))
        H, status = cv2.findHomography(tp, qp, cv2.RANSAC, 3.0)
        h, w = trainImg.shape
        traingBorder = np.float32([[[0, 0], [0, h-1], [w-1, h-1], [w-1, 0]]])
        queryBorder = cv2.perspectiveTransform(traingBorder, H)
        cv2.polylines(QueryImgBGR, [np.int32(queryBorder)], True, (0, 255, 0), 5)
        cv2.drawKeypoints(QueryImgBGR, queryKP,QueryImgBGR,flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)

對於以SIFT 或 ORB 找出對應點,再以findHomography 畫出新圖的物件位置,對於使用者來說,會容易理解多了。
但至此為止,「物件」辨識都還算是在圖學的階段,還不到AI 的程度。因為至此為止,它就是辨識兩個圖形的相似度,來確認新物件是不是與原物件為同一物件,若新物件的拍攝角度不同,少了某些特徵,openCV 應該就認不出來了。如果是以AI,它又是怎麼去辨認的呢?我想到的是建立一個3*3*3 的原始物件拍攝角度的「基圖」,讓每個新圖都與其比對,應該可以,但似乎會太耗CPU 和 Memory,而且速度也不快。

2018年3月8日 星期四

物件辨識學習 - 1

看了一篇Siraj Raval 的物件辨識教學,直撥錄製下來的,
它是用OpenCV來實現辨識功能,
我原本期待OpenCV真能做到簡單辨識我要的物件,
可惜畢竟OpenCV 終究只是影像處理,
所以它用的原理是將草草莓的顏色強化,
再將其紅色區域標示出來,
雖然在單一的草莓圖裡,的確可以辨識出來,
但如果是一坉草莓,或是白草莓,
甚或是紅色的東西,
它可能就會辨識不出來,
或誤認為是草莓了。

簡單說,它只是辨識出紅色的區域,
而不是真的由AI去辨識出草莓,
這對簡單的圖形很有用,
因為不用經過大量的運算,即可辨識出特定顏色或形狀,
速度可以很快。

但若圖形內容數量多或複雜時,
就沒作用了,
而且,其實這支程式也不知道它標出來的東西是什麼。
基本上,這支程式是沒AI功能的。

但如果用在RPi 上可以速度非常快的執行,
AI在RPi 上,可能效果不好吧,我猜。

等我學到了再說⋯⋯

另外找到了這篇,也是單純用OpenCV 來做,但有圖像辨識的功能,
好像比較有深度,我試著用我的手機當辨識物,
sample 是開機畫面的手機,
不管是正面或是正面斜面,可以很快地辨識出來,
但是若關機,或是背面,因為畫面差異太大,
它就無法辨識。
而且sample 必須佔滿整個sample 的畫面,
如果你隨便拍一張sample 照,
還先必須將它修成滿版才行,
不知圓的它能不能辨識出來。
也就是說,對於靜物它可以很快地辨識,
但如果形狀或色彩變化太大,它就無法辨識了。
如果要做為動物的辨識,就可能沒辦法了。
但仍不失為一個有效的方法。

PS. 試了一個圓柱體的盒子,以斜的方式拍攝當sample,果然它只能辨識出斜角度的圓盒,以正面或伏面,它都認不出來。因為底部是純白的,以底部為主,同樣的角度,它也認不出來。還有,大小必須和sample 類似,拉太遠或太近,同樣它也辨識不出來。但它的SIFT 演算法還是值得了解一下。

2017年9月21日 星期四

Lumex OLED 實作

行走江湖,該還的還是要還~

今年七月有幸參加LASS 年會,報告了自己做的水質偵測。會場中,Lumex的老董還大心地送了其產品做為贈品。雖然沒能抽中,但很開心企業來支持開放社團,對於社團的發展是非常大的助力。

會後Lumex謝總更是再加碼,再送20套OLED 和 LCD 產品給社團成員。這次總算有幸抽中,謝總真的是大心,各給一片,再加一組 CP2102 的USB 模組,ezDisplay 系列的優點就是透過USB 就可以直接當display 用來顯示ASCII 字元,不用再輸入複雜的控制碼。對於只是單純要輸入ASCII 字元需求的使用者,真的非常方便。

剛好最近在測Wi-Fi 訊號,於是向同事借了一片 ESP8266 的NodeMCU 來檢測WiFi 網路訊號範圍,想用最簡單的方式來做,剛好,一片NodeMCU 和 Lumex 的 LOD-H12864GP-x-UR
就可以搞定。

我是要測Wifi 的訊號強度,所以一開始WiFi 要有 連結,因此就要先顯示出 WiFi 連結的圖型,顯示完後,在不同的列數分別顯示 取得IP,SSID,RSSI值,再顯示 ping gateway, seednet DNS, Google DNS 的狀況。讓此動作以loop 的方式持續執行。若中途超出WiFi 的範圍,則重新進行WiFi 偵測。

因此,程式的流程大概就是:

  1.  NodeMCU 嘗試連到指定的IP。取得IP 前,顯示 WiFi 連線動畫。未取得IP 前,持續顯示WiFi 連線動畫。
  2.  順利連上AP後,在OLED上顯示IP,顯示 SSID,顯示RSSI
  3. ping gateway ,並顯示結果
  4. ping seednet DNS IP, 並顯示結果
  5. ping Google DNS IP, 並顯示結果
  6. 回到步驟2, 若失聯,則回到步驟1
以上搞定。


硬體架構


其實硬體架構還蠻簡單的,肇於NodeMCU 在 WiFi 上的強大功能,以及Lumex 的ezDisplay 的簡易操作,我直接將NodeMCU 的 5V Output 給 OLED,再使用 ESP8266 的 SoftwareSerial 來模擬 Serial port ,使NodeMCU 直接控制 OLED。
PS:找不到 fritzing 的 Lumex part, 又不會自己畫,只好直接用 fritzing 內建的 part. 但基本上 ezDisplay 內建serial 溝通用的 Tx/Rx 及 5Vcc 和 GND,並附接線,所以也很簡單上手的。
LOD-H12864GP-x-UR
Lumex LOD-H12864GP-x-UR


軟體架構

我使用的是ATOM 的IDE,它是個相當不錯的open source 套件。
再加上 PlatformIO 的外掛功能,就能讓你可以直接使用類似 Arduino IDE 的功能,也就是除了寫程式碼,還能連MCU 的console 和 debug。相當的方便好用。

當在PlatformIO 開啟project 時,板子選 NodeMCU 1.0(ESP-12E Module), Framework 選用 Arduino 即可。
但內建的函式庫並無 ping 的功能,因此要再到GitHub 上下載 ESP8266Ping 的 library 來達到我們ping 的目的。下載下來的 ESP8266Ping library 要放在PlatformIO 的你的專案裡的lib 目錄,這樣它才找得到。
其餘的照下列程式碼去執行就可以了。

幾點注意事項要特別提醒:

  • Lumex 佛心來著,另外提供了 SiLabs 的CP2102 的UART 板子。使用的和 NodeMCU 是一樣CP2102. 但一開始它的 mac driver 有問題,會造成我的MacBook Air 一直當機。最後這一版 V5 才正常。
  • SoftwareSerial 的library 用的 NodeMCU pin 腳是對應 GOIP 數字的,而非板子上 Dx 的數字,一開始搞不清楚,弄得NodeMCU 一直reboot.
  • 部分function copy 自 Lumex  的 Arduino sample 檔,但會忘了把 sample 檔內原 Serial 改成自己的虛擬 MySerial ,也弄得 NodeMCU 一直reboot.
  • 那個wifi 連接圖 是透過 lumex 內建的 畫圓功能(因為它並沒提供畫弧線的功能),一條一條畫上去的。再加上左三角和右三角,將我們不要的圓遮蔽掉,就形成 wifi 連結的動畫圖。
看那堆程式碼前,先來看看結果吧~~

2017/9/22




2017年6月12日 星期一

Google Video Intelligence 學習筆記

Google Video Intelligence 學習筆記

除了前面所述 Google Cloud vision,透過靜態照片來由Google Cloud Vision AI 來辨識之外,Google另一項影像辨識功能,即透過影片(video) 來進行影像辨識。

在這篇有介紹如何進行Google Video Intelligence 的 快速入門。

在這篇的範例,需要先安裝Google Cloud SDK,再請先安裝。

開啟一個專案

  1. 在Google Cloud Platform 內,先建立一個新的專案。
  2. 到 API 管理介面,啟用 Google Video Intelligence API
  3. 然後在 API 管理介面內,點選「憑證」,建立API 金鑰,它會跳出一個視窗,告訴你在使用此API 時,用 key=API_KEY 來傳送金鑰,所以記得要先把你的API 金鑰剪下來,貼在你的文字檔裡。如下圖:

設定授權

在你安裝好 Google Cloud SDK 後,在其目錄下就會有 cloud 這支程式:
  1. 建立一個服務帳號,叫 quickstart:
  1. gcloud iam service-accounts create quickstart

  2. 建立 service account key file,其中your-project-123 是你剛才建立的專案名稱全名(即加上 - 後面的數字串):
  3. gcloud iam service-accounts keys create key.json \
    --iam-account quickstart@your-project-123.iam.gserviceaccount.com
  4. 授權給你的service account,並把你的service account key file 位置傳送過去
  5. gcloud auth activate-service-account --key-file key.json
  6. 取得授權,並將它顯示在螢幕上:
  7. gcloud auth print-access-token
你會看到程式大概五秒之後會顯示一長串亂碼,那其實就是你的key,也就是上面圖示顯示的那塊。

進行 Video 辨識請求

  1. 用文字編輯器,將下列文字copy 到你的文字檔裡,用來產生一個 JSON 要求檔,就叫 request.json。但要記得,將你的檔案copy 到你的 google cloud storage 內。目前google video intelligence 只支援將影片上傳到其google cloud storage 內做辨識,並不支援其他任何方式(如 streaming)。像下面的範例就是在cloud storage 建立一個 video 目錄,再把 chicago.mp4 上傳到這個video 目錄下。
  2. {
       "inputUri":"gs://cloud-ml-sandbox/video/chicago.mp4",
       "features": [
           "LABEL_DETECTION"
       ]
    }
    
  3. 利用 curl 指令來產生影像辨識要求 (videos:annotate),將下方的 ACCESS_TOKEN h利用剛才印出的那一長串亂碼(即access token) 取代,其餘照抄(下面這指令全在同一行內, \表連續行):
  4. curl -s -k -H 'Content-Type: application/json' \
        -H 'Authorization: Bearer ACCESS_TOKEN' \
        'https://videointelligence.googleapis.com/v1beta1/videos:annotate' \
        -d @request.json
    Video intelligence API 會產生一個 operation 以處理你的辨識要求。上述指令下完會,會顯示它產生的 operation name:
      {
        "name": "us-west-18358601230245040268"
      }
      
    1. 這時Google 已經開始在分析你的影片,你可以傳遞operation name 並透過 v1.operations  來取得分析結果:
    2. curl -s -k -H 'Content-Type: application/json' \
          -H 'Authorization: Bearer ACCESS_TOKEN' \
          'https://videointelligence.googleapis.com/v1/operations/OPERATION_NAME'
      你會看到Google 回應你分析的結果,如果分析尚未完成,那在 done 這個值會是 false:
      {
        "name": "OPERATION_NAME",
        "metadata": {
          "@type": "type.googleapis.com/google.cloud.videointelligence.v1beta1.Annota
      tionProgressMetadata",
          "progressMetadata": [
            {
              "inputUri": "gs://cloud-ml-sandbox/video/chicago.mp4",
              "progressPercent": 0,
              "startTime": "2016-09-22T21:41:56.766091Z",
              "lastUpdateTime": "2016-09-22T21:42:03.889743Z"
            }
          ]
        },
        "done": false,
        ...
      }
      
    在分析完所有資料後,你再下一次上述指令,就可以看到Google Video intelligence 對你的影片做的完整分析內容。其中 description 欄表示它判斷可能的物件名稱,confidence 表信心值,或簡單說,它猜此物件的正確率大約是多少。

    結論


    基本上Google Video intelligence 可以辨識出的物件仍然有限,尤其影片不清楚時,所以它的信心值都不高。它目前可以做物件分析,臉部分析,標題分析和場景變換的辨認。它可以用來判斷你的影片內出現了那些東西,但無法很精確地辨識出人臉或(誰)出現在影片裡。但Apple 的 照片 程式可以將類似的人臉整合成一個群組,再給人類進行更正確的判斷是否為同一個人。我想,應該要做到這樣的程度才夠吧。而且,影片還要上傳到google storage才能辨識,還不夠方便。不過,Google 也強調,這是beta,還沒算正試的服務,就玩玩看囉。