基於用戶的協同過濾來構建推薦系統

1.概述

之前介紹了如何構建一個推薦系統,今天給大家介紹如何基於用戶的協同過濾來構建推薦的實戰篇。

2.內容

協同過濾技術在推薦系統中應用的比較廣泛,它是一個快速發展的研究領域。它比較常用的兩種方法是基於內存(Memory-Based)和基於模型(Model-Based)。

  • 基於內存:主要通過計算近似度來進行推薦,比如基於用戶(Used-Based)和基於物品(Item-Based)的協同過濾,這兩個模式中都會首先構建用戶交互矩陣,然後矩陣的行向量和列向量可以用來表示用戶和物品,然後計算用戶和物品的相似度來進行推薦;
  • 基於模型:主要是對交互矩陣進行填充,預測用戶購買某個物品的可能性。

為了解決這些問題,可以通過建立協同過濾模型,利用購買數據向客戶推薦產品。下面,我們通過基於用戶的協同過濾(基於內存),通過實戰來一步步實現其中的細節。基於用戶的系統過濾體現在具有相似特徵的人擁有相似的喜好。比如,用戶A向用戶B推薦了物品C,而B購買過很多類似C的物品,並且評價也高。那麼,在未來,用戶B也會有很大的可能會去購買物品C,並且用戶B會基於相似度度量來推薦物品C。

2.1 基於用戶與用戶的協同過濾

這種方式識別與查詢用戶相似的用戶,並估計期望的評分為這些相似用戶評分的加權平均值。實戰所使用的Python語言,這裏需要依賴的庫如下:

  • pandas
  • numpy
  • sklearn

Python環境:

  • 版本3.7.6
  • Anaconda3

2.2 評分函數

這裏給非個性化協同過濾(不包含活躍用戶的喜歡、不喜歡、以及歷史評分),返回一個以用戶U和物品I作為輸入參數的分數。該函數輸出一個分數,用於量化用戶U喜歡 / 偏愛物品I的程度。這通常是通過對與用戶相似的人的評分來完成的。涉及的公式如下:

 

 這裏其中s為預測得分,u為用戶,i為物品,r為用戶給出的評分,w為權重。在這種情況下,我們的分數等於每個用戶對該項目的評價減去該用戶的平均評價再乘以某個權重的總和,這個權重表示該用戶與其他用戶有多少相似之處,或者對其他用戶的預測有多少貢獻。這是用戶u和v之間的權重,分數在0到1之間,其中0是最低的,1是最高的。理論上看起來非常完美,那為啥需要從每個用戶的評分中減去平均評分,為啥要使用加權平均而不是簡單平均?這是因為我們所處理的用戶類型,首先,人們通常在不同的尺度上打分,用戶A可能是一個积極樂觀的用戶,會給用戶A自己喜歡的電影平均高分(例如4分、或者5分)。而用戶B是一個不樂觀或者對評分標準比較高的用戶,他可能對最喜歡的電影評分為2分到5分之間。用戶B的2分對應到用戶A的4分。改進之處是可以通過規範化用戶評分來提高算法效率。一種方法是計算s(u,i)的分數,它是用戶對每件物品的平均評價加上一些偏差。通過使用餘弦相似度來計算上述公式中給出的權重,同時,按照上述方式對數據進行歸一化,在pandas中進行一些數據分析。

2.2.1 導入Python依賴包

import pandas as pd
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.metrics import pairwise_distances

2.2.2 加載數據源

加載數據示例代碼如下所示:

movies = pd.read_csv("data/movies.csv")
Ratings = pd.read_csv("data/ratings.csv")
Tags = pd.read_csv("data/tags.csv")

結果預覽如下:

print(movies.head())
print(Ratings.head())
print(Tags.head())

 

 構建數據:

Mean = Ratings.groupby(by="userId", as_index=False)['rating'].mean()
Rating_avg = pd.merge(Ratings, Mean, on='userId')
Rating_avg['adg_rating'] = Rating_avg['rating_x'] - Rating_avg['rating_y']
print(Rating_avg.head())

結果如下:

 

2.3 餘弦相似度

 對於上面的公式,我們需要找到有相似想法的用戶。找到一個喜歡和不喜歡的用戶聽起來很有意思,但是我們如何找到相似性呢?那麼這裏我們就需要用到餘弦相似度,看看用戶有多相似。它通常是根據用戶過去的評分來計算的。

這裏使用到Python的的sklearn的cosine_similarity函數來計算相似性,並做一些數據預處理和數據清洗。實例代碼如下:

check = pd.pivot_table(Rating_avg,values='rating_x',index='userId',columns='movieId')
print(check.head())
final = pd.pivot_table(Rating_avg,values='adg_rating',index='userId',columns='movieId')
print(final.head())

結果如下:

上圖中包含了很多NaN的值,這是因為每個用戶都沒有看過所有的電影,所以這種類型的矩陣被稱為稀疏矩陣。類似矩陣分解的方法被用來處理這種稀疏性,接下來,我們來對這些NaN值做相關替換。

這裏通常有兩種方式:

  1. 使用行上的用戶平均值;
  2. 用戶在列上的電影平均值

代碼如下:

# Replacing NaN by Movie Average
final_movie = final.fillna(final.mean(axis=0))
print(final_movie.head())

# Replacing NaN by user Average
final_user = final.apply(lambda row: row.fillna(row.mean()), axis=1)
print(final_user.head())

結果如下:

 

 接着,我們開始計算用戶之間的相似性,代碼如下:

# user similarity on replacing NAN by item(movie) avg
cosine = cosine_similarity(final_movie)
np.fill_diagonal(cosine, 0)
similarity_with_movie = pd.DataFrame(cosine, index=final_movie.index)
similarity_with_movie.columns = final_user.index
# print(similarity_with_movie.head())

# user similarity on replacing NAN by user avg
b = cosine_similarity(final_user)
np.fill_diagonal(b, 0 )
similarity_with_user = pd.DataFrame(b,index=final_user.index)
similarity_with_user.columns=final_user.index
# print(similarity_with_user.head())

結果如下:

 

 然後,我們來檢驗一下我們的相似度是否有效,代碼如下:

def get_user_similar_movies( user1, user2 ):
    common_movies = Rating_avg[Rating_avg.userId == user1].merge(
    Rating_avg[Rating_avg.userId == user2],
    on = "movieId",
    how = "inner" )
    return common_movies.merge( movies, on = 'movieId' )

a = get_user_similar_movies(370,86309)
a = a.loc[ : , ['rating_x_x','rating_x_y','title']]
print(a.head())

結果如下:

 

 從上圖中,我們可以看出產生的相似度幾乎是相同的,符合真實性。

2.4 相鄰用戶

在2.3中計算了所有用戶的相似度,但是在大數據領域,推薦系統與大數據相結合是至關重要的。以電影推薦為例子,構建一個矩陣(862 * 862),這個與實際的用戶數據(百萬、千萬或者更多)相比,這是一個很小的矩陣。因此在計算任何物品的分數時,如果總是查看所有其他用戶將不是一個好的解決方案或者方法。因此,採用相鄰用戶的思路,對於特定用戶,只取K個類似用戶的集合。

下面,我們對K取值30,所有的用戶都有30個相鄰用戶,代碼如下:

def find_n_neighbours(df,n):
    order = np.argsort(df.values, axis=1)[:, :n]
    df = df.apply(lambda x: pd.Series(x.sort_values(ascending=False)
           .iloc[:n].index, 
          index=['top{}'.format(i) for i in range(1, n+1)]), axis=1)
    return df

# top 30 neighbours for each user
sim_user_30_u = find_n_neighbours(similarity_with_user,30)
print(sim_user_30_u.head())

sim_user_30_m = find_n_neighbours(similarity_with_movie,30)
print(sim_user_30_m.head())

結果如下:

 

 2.5 計算最後得分

實現代碼如下所示:

def User_item_score(user,item):
    a = sim_user_30_m[sim_user_30_m.index==user].values
    b = a.squeeze().tolist()
    c = final_movie.loc[:,item]
    d = c[c.index.isin(b)]
    f = d[d.notnull()]
    avg_user = Mean.loc[Mean['userId'] == user,'rating'].values[0]
    index = f.index.values.squeeze().tolist()
    corr = similarity_with_movie.loc[user,index]
    fin = pd.concat([f, corr], axis=1)
    fin.columns = ['adg_score','correlation']
    fin['score']=fin.apply(lambda x:x['adg_score'] * x['correlation'],axis=1)
    nume = fin['score'].sum()
    deno = fin['correlation'].sum()
    final_score = avg_user + (nume/deno)
    return final_score

score = User_item_score(320,7371)
print("score (u,i) is",score)

結果如下:

 

這裏我們算出來的預測分數是4.25,因此可以認為用戶(370),可能喜歡ID(7371)的電影。接下來,我們給用戶(370)做電影推薦,實現代碼如下:

Rating_avg = Rating_avg.astype({"movieId": str})
Movie_user = Rating_avg.groupby(by = 'userId')['movieId'].apply(lambda x:','.join(x))

def User_item_score1(user):
    Movie_seen_by_user = check.columns[check[check.index==user].notna().any()].tolist()
    a = sim_user_30_m[sim_user_30_m.index==user].values
    b = a.squeeze().tolist()
    d = Movie_user[Movie_user.index.isin(b)]
    l = ','.join(d.values)
    Movie_seen_by_similar_users = l.split(',')
    Movies_under_consideration = list(set(Movie_seen_by_similar_users)-set(list(map(str, Movie_seen_by_user))))
    Movies_under_consideration = list(map(int, Movies_under_consideration))
    score = []
    for item in Movies_under_consideration:
        c = final_movie.loc[:,item]
        d = c[c.index.isin(b)]
        f = d[d.notnull()]
        avg_user = Mean.loc[Mean['userId'] == user,'rating'].values[0]
        index = f.index.values.squeeze().tolist()
        corr = similarity_with_movie.loc[user,index]
        fin = pd.concat([f, corr], axis=1)
        fin.columns = ['adg_score','correlation']
        fin['score']=fin.apply(lambda x:x['adg_score'] * x['correlation'],axis=1)
        nume = fin['score'].sum()
        deno = fin['correlation'].sum()
        final_score = avg_user + (nume/deno)
        score.append(final_score)
    data = pd.DataFrame({'movieId':Movies_under_consideration,'score':score})
    top_5_recommendation = data.sort_values(by='score',ascending=False).head(5)
    Movie_Name = top_5_recommendation.merge(movies, how='inner', on='movieId')
    Movie_Names = Movie_Name.title.values.tolist()
    return Movie_Names

user = int(input("Enter the user id to whom you want to recommend : "))
predicted_movies = User_item_score1(user)
print(" ")
print("The Recommendations for User Id : 370")
print("   ")
for i in predicted_movies:
    print(i)

結果如下:

 

3.總結

基於用戶的協同過濾,流程簡述如下:

  1. 採集數據 & 存儲數據
  2. 加載數據
  3. 數據建模(數據預處理 & 數據清洗)
  4. 計算相似性(餘弦相似度、相鄰計算)
  5. 得分預測(預測和最終得分計算)
  6. 物品推薦

4.結束語

這篇博客就和大家分享到這裏,如果大家在研究學習的過程當中有什麼問題,可以加群進行討論或發送郵件給我,我會盡我所能為您解答,與君共勉!

另外,博主出書了《Kafka並不難學》和《Hadoop大數據挖掘從入門到進階實戰》,喜歡的朋友或同學, 可以在公告欄那裡點擊購買鏈接購買博主的書進行學習,在此感謝大家的支持。關注下面公眾號,根據提示,可免費獲取書籍的教學視頻

,

這篇博客就和大家分享到這裏,如果大家在研究學習的過程當中有什麼問題,可以加群進行討論或發送郵件給我,我會盡我所能為您解答,與君共勉!

另外,博主出書了《Kafka並不難學》和《Hadoop大數據挖掘從入門到進階實戰》,喜歡的朋友或同學, 可以在公告欄那裡點擊購買鏈接購買博主的書進行學習,在此感謝大家的支持。關注下面公眾號,根據提示,可免費獲取書籍的教學視頻

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※幫你省時又省力,新北清潔一流服務好口碑

※別再煩惱如何寫文案,掌握八大原則!

程序員不能說自己不行啊

二哥,最近我剛進了一家公司,之前跟你說過,培訓出身剛剛畢業,打算在北京打拚。最近進公司,給安排了工作,今天第一次沒人帶,自己上手搞代碼,搞不出,明明挺簡單的功能,自己還是做不出,不知道從哪裡學習,想趕快熟悉工作,可是自己的能力不行,在地鐵上常看二哥原創的作品,平常积極在看,超級希望能自己學到本事,但自己的能力真的有點問題,工作搞不完,害怕被問,害怕任務完不成被辭退。

以上是讀者西瓜向我提的一個問題,我覺得挺具有代表性的,所以決定拉出來單獨寫一篇文章答疑解惑一下。

可以肯定的一點是,任何時候都要說自己不行啊,尤其是男性同胞,可以認慫,但是“不行”這個兩個字千萬不要輕易說出口,為什麼?你懂吧?

人的能力各有不同,但如果你自己都不自信,那又能做好什麼事情呢?心理建設非常重要。

記得之前看一個短片,一個小男孩跳了無數次,都無法越過障礙物,但是呢,他身邊的同學一直為他加油吶喊,小男孩呢,也從來沒有放棄的打算,最後的結果我都快看哭了,他真的跳過去了,他出色地完成了自我挑戰。

他的成功,離不開同學們的鼓勵,但更重要的是他鍥而不舍的精神,心裏素質比一般的成年人都要強大。

我現在已經為人父了,雖然我一直標榜自己只有 18 歲,但叫二叔的讀者真的越來越多,我已經逆來順受了。在我的教育觀念里,我覺得我家女兒最優秀的一點品質,就是,如果她喜歡一件事,她就會主動去鑽研,去摸索,在沒有任何外人的幫助下。

你比如說,現在比較流行的平衡車,就是不帶腳踏板和鏈條的自行車。一開始,我想給她報個班,至少有個老師教教,對吧?

但是,她的表現完全出乎我的意料,我只要把車買回來,放到她的面前,怎麼騎,完全靠她自己去體驗。一開始小心翼翼,很保本,但她不滿足於現狀,就找一些小坡騎,然後是再大一點的坡,就這樣,挑戰一次又一次,自己就完全掌握了騎行的技術。

對於我來說,我沒有騎平衡車的經驗,小時候也沒有這玩意。我能做的除了買車,就是給她鼓勵,摔倒了沒事,哭了也沒事,有些事情,痛苦的同時,伴隨着挑戰和突破。

對於我們成年人來說,其實道理都懂的,但人與人之間的差距之所以拉開,除了選擇的正確有否,最大的因素我想就是,你有沒有自己主動去做

西瓜說自己明明很簡單的功能,就是做不出來。這種感覺我也有過,即便是現在有了十年多的編程經驗,仍然在某些時刻感到举手無措,無從下手。

對,這就是為什麼人要終身學習的原因啊。我們做不出來,除了思維上、認知上的局限性,另外一個重要的點就在於,你有沒有經驗。

對於新人來說,經驗肯定是欠缺的,這點毫無疑問,對吧?但是只要公司招你進去了,無論是不是培訓班出身,負責任的公司都會給你充足的時間和空間去進步,就看你自己有沒有主動。

我大三出去實習的時候,公司要求我做一個計算器,那時候覺得好難啊,因為加減乘除,再帶上小括號,運算是有優先級的,還要考慮到小括號的自動補齊,對於那時候菜得一筆的我來說,真特么難啊。

但能怎麼辦?做不出來就意味着要被辭退,那只有一個辦法,就是上網搜,找別人的例子模仿,拆分,融化,把它變成是自己的。

那時候,我還不會玩 GitHub、碼雲和開源中國,私下里主動學習的地方只有一個,好像是叫編程入門網,現在已經沒有了。我就是照着上面的例子,一個個手敲,當你例子敲多了,很多編程知識就融會貫通了。

現在好了,優秀的案例數不勝數。我的兩個好朋友,macrozheng 開源了他的電商平台 mall,江南一點雨開源了他的微人事系統 vhr,這兩個開源項目我一直強烈推薦新手去下載到本地,去學習。

很多時候,對於編程天賦一般的我們來說,不需要主動去造輪子,我們只需要去發現輪子,對吧?

我在一開始做 Web 管理系統的時候,找了一個企業級的開源系統,叫做 DWZ,不知道有沒有讀者朋友用過,當年非常火,我們公司的後台管理系統現在還在用,雖然說界面已經很古董了,但對於我們公司來說,足夠用了。

這套 DWZ 就封裝了很多前端組件,對於我一個 Java 程序員來說,非常友好,直接可以上手操作,如果一些組件不滿足,我就去改造。改造的過程中,就積攢了大把解決問題的實戰經驗,這是彌足珍貴的。

我在《Web全棧開發進階之路》這本書里,就借鑒了不少 DWZ 的優秀思想。不要覺得不會造輪子是可恥的,會用輪子也是真本領啊。

就西瓜來說,平常喜歡看我的原創文,那我文章涉及到的例子有沒有去敲呢?如果你敲了,你就會發現,文章里涉及到的例子能解決大部分新人在工作中遇到的問題,直接把這些作為自己的工具包,下次遇到拿來即用就可以了。

對於 Java 程序員來說,JDK 的原生 API 不能滿足需求的話,還有很多第三方的類庫,比如說 Apache 的,封裝了大量常用的工具類和方法。前提條件是,你必須得知道有這些東西,如果不知道的話,那就無從下手了,對吧?

那怎麼見多識廣呢?這就回到了之前所說的,你得去練,動手去練,無論是書本里的,還是文章里的,還是開源項目里的,你得去手操一遍,不要眼高手低,敲多了,自然就形成了自己解決問題的思路和方法。

擔心自己被辭退是一件好事,這會督促我們前進,對吧?有的人,有自驅力,不需要外力的干預就能奮發圖強,有的人,就需要一條看不見的鞭子抽打着,才會有前進的動力。

別懷疑自己,真的,人嘛,總是有能力強弱之分的,要學會接納自己,像二哥一樣自信點,腳踏實地,一點一點去進步,當你取得一點成績的時候就把這些當做是里程碑,隨着時間的推移,你就會發現,自己變禿了,不不不,變強了。

加油,西瓜!

如果覺得文章對你有點幫助,請微信搜索「 沉默王二 」第一時間閱讀。

本文已收錄 GitHub,傳送門~ ,裏面更有大廠面試完整考點,歡迎 Star。

我是沉默王二,一枚有顏值卻靠才華苟且的程序員。關注即可提升學習效率,別忘了三連啊,點贊、收藏、留言,我不挑,嘻嘻

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

線性表的鏈式存儲–單鏈表

Java之線性表的鏈式存儲——單鏈表

我們都知道,線性表的存儲結構分為兩種,順序存儲結構和鏈式存儲結構,線性表的分類可以參考下圖來學習記憶。今天我們主要來學習一下鏈式存儲結構。

一、鏈式存儲介紹

“鏈式存儲結構,地址可以連續也可以不連續的存儲單元存儲數據元素”——來自定義。

其實,你可以想象這樣一個場景,你想找一個人(他的名字叫小譚),於是你首先去問 A , A 說他不知道,但是他說 B 可能知道,並告訴了你 B 在哪裡,於是你找到 B ,B 說他不知道,但是他說 C 可能知道,並告訴了你 C 的地址,於是你去找到 C ,C 真的知道小譚在何處。

上面場景其實可以幫助我們去理解鏈表,其實每一個鏈表都包含多個節點,節點又包含兩個部分,一個是數據域(儲存節點含有的信息),一個是指針域(儲存下一個節點或者上一個節點的地址),而這個指針域就相當於你去問B,B知道C的地址,這個指針域就是存放的 C 的地址。

鏈表下面其實又細分了3種:單鏈表、雙向鏈表和循環鏈表。今天我們先講單鏈表。

二、單鏈表介紹

什麼是單鏈表呢?單鏈表就是每一個節點只有一個指針域的鏈表。如下圖所示,就是一個帶頭節點的單鏈表。下面我們需要知道什麼是頭指針,頭節點和首元節點。

頭指針:指向鏈表節點的第一個節點的指針

頭節點:指在鏈表的首元節點之前附設的一個節點

首元節點:指在鏈表中存儲第一個實際數據元素的節點(比如上圖的 a1 節點)

三、單鏈表的創建

單鏈表的創建有兩種方式,分別是頭插法和尾插法。

1、頭插法

頭插法,顧名思義就是把新元素插入到頭部的位置,每次新加的元素都作為鏈表的第一個節點。那麼頭插入法在Java中怎麼實現呢。首先我們需要定義一個節點,如下

public class ListNode {
  public int val; //數據域
  public ListNode next;//指針域
}

然後我們就創建一個頭指針(不帶頭節點)

//元素個數
int n = 5;
//創建一個頭指針
ListNode headNode = new ListNode();
//頭插入法
headNode= createHead(headNode, n);

然後創建一個私有方法去實現頭插法,這裏我們插入5個新元素,頭插入的核心是要先斷開首元節點和頭指針的連接,也就是需要先將原來首元節點的地址存放到新節點的指針域里,也就是 newNode.next = headNode.next,然後再讓頭指針指向新的節點 headNode.next = newNode,這兩步是頭插入的核心,一定要理解。

/**
 * 頭插法
 * 新的節點放在頭節點的後面,之前的就放在新節點的後面
 * @param headNode 頭指針
 * @return
 */
private static ListNode createHead(ListNode headNode, int n) {
  //插入5個新節點
  for (int i = 1; i <= n; i++) {
    ListNode newNode = new ListNode();
    newNode.val = i;
    //將之前的所有節點指向新的節點(也就是新節點指向之前的所有節點)
    newNode.next = headNode.next;
    //將頭指針指向新的節點
    headNode.next = newNode;
  }
  return headNode;
}

最後我把鏈表打印輸出一下(其實也是單鏈表的遍歷),判斷條件就是只有當指針域為空的時候才是最後一個節點。

private static void printLinkedList(ListNode headNode) {
  int countNode = 0;
  while (headNode.next != null){
    countNode++;
    System.out.println(headNode.next.val);
    headNode = headNode.next;
  }
  System.out.println("該單鏈表的節點總數:" +countNode);
}

最後的輸出結果顯然是逆序,因為沒一個新的元素都是從頭部插入的,自然第一個就是最後一個,最後一個就是第一個:

2、尾插法

尾插法,顧名思義就是把新元素插入到尾部的位置(也就是最後一個位置),每次新加的元素都作為鏈表的第最後節點。那麼尾插法在 Java 中怎麼實現呢,這裏還是採用不帶頭節點的實現方式,頭節點和頭指針和頭插入的實現方式一樣,這裏我就直接將如何實現:

/**
 * 尾插法
 * 找到鏈表的末尾結點,把新添加的數據作為末尾結點的後續結點
 * @param headNode
 */
private static ListNode createByTail(ListNode headNode, int n) {
  //讓尾指針也指向頭指針
  ListNode tailNode = headNode;
  for (int i = 1; i <= n; i++) {
    ListNode newNode = new ListNode();
    newNode.val = i;
    newNode.next = null;

    //插入到鏈表尾部
    tailNode.next = newNode;
    //指向新的尾節點,tailer永遠存儲最後一個節點的地址
    tailNode = newNode;

  }
  return headNode;
}

和頭插入不同的是,我們需要聲明一個尾指針來輔助我們實現,最開始,尾指針指向頭指針,每插入一個元素,尾指針就后移一下,這裏我們來講一下原理:每次往末尾新加一個節點,我們就需要把原來的連接斷開,那怎麼斷開呢,我們首先需要讓尾指針指向新的節點,也就是 tailNode.next = newNode; 然後再讓尾指針后移一個位置,讓尾指針指向最後一個節點。也就是尾指針始終指向最後一個節點,最後將頭指針返回,輸出最後結果:

四、單鏈表的刪除

既然單鏈表創建好了,怎麼在鏈表裡面刪除元素呢,單鏈表的刪除,我分為了兩種情況刪除,分別是刪除第i個節點和刪除指定元素的節點。

1、刪除第i個節點

我們可以先來理一下思路:在單鏈表裡,節點與節點之間都是通過指針域鏈接起來的,所以如果我們想實現刪除的操作,實際上是需要我們去改變相應指針域對應得地址的。當想去刪除第i個元素的時候,比如要刪除上圖的第3個元素(也就是3),實際上我們要做的就是要讓2號元素指向4號元素(其實就是需要修改2號元素的指針域,讓2號元素的指針域存儲4號元素)。那麼怎麼做才能實現這一步呢?很顯然,要實現這個步驟,我們必須要找到4號元素和2號元素,但是再仔細想一下,其實我們只需要找到2號元素就可以了,因為4號元素的地址存儲再2號的下一個元素的指針域裏面。

所以綜上所述分析我們可以得出刪除的兩個核心步驟:

1.刪除第i個節點,需要先找到第 i-1 個個節點,也就是第i個節點的前一個節點;

2.然後讓第 i-1 個節點指向第 i-1 個節點的下下個節點

下面的代碼具體實現了怎麼刪除第i個元素。

/**
 * 刪除第i個節點
 * 1,2 4,4,5
 * 刪除之後應該是1,2,4,5
 * @param headNode
 * @param index
 * @return
 */
public static ListNode deleteNodeByIndex(ListNode headNode, int index) {
  int count = 1;
  //將引用給它
  ListNode preNode = headNode;
  //看計數器是不是到了i-1,如果到了i-1,就找到了第i-1個節點
  while (preNode.next != null && count <= index -1){
    //尋找要刪除的當前節點的前一個節點
    count++;
    preNode = preNode.next;
  }
  if (preNode != null){
    preNode.next = preNode.next.next;
  }
  return headNode;
}

2、刪除指定元素的那個節點

刪除指定元素節點的實現方法有兩種,第一種就是先找到指定元素對應的鏈表的位置( index ),然後再按照刪除第 i 個節點的思路刪除即可。實現方法如下圖所示:

/**
 * 刪除鏈表指定數值的節點
 * @param headNode
 * @param val
 * @return
 */
private static ListNode deleteNodeByNum(ListNode headNode, int val) {
  ListNode deleteOne = headNode;
  int countByDeleteOne = 1;
  while (deleteOne.next != null){
    if (deleteOne.next.val == val){
      deleteOne = deleteOne.next;
      break;
    }
    countByDeleteOne ++;
    deleteOne = deleteOne.next;
  }
  return deleteNodeByIndex(headNode, countByDeleteOne);
}

第二種方法的實現就很精妙(前提是此節點不是尾節點)

public void deleteNode(ListNode node) {
  //刪除node即通過將後面的值賦給node,然後更改node的指針指向下下一個結點即可
  node.val = node.next.val;
  node.next = node.next.next;
}

五、單鏈表的查詢(及修改)

單鏈表的查詢實現很簡單,就是遍歷當前單鏈表,然後用一個計數器累加到當前下標,那麼當前的這個節點就是要查詢的那個節點,然後再返回即可,當然需要判斷傳過來的這個下標是否合法。當然如果需要修改,就需要把當前找到的節點的數據域重新賦上需要修改的值即可,這裏就不上代碼了。具體實現如下:

private static ListNode searchLinkedList(ListNode headNode, int index) {
  //如果下標是不合法的下標就表示找不到
  if (index < 1 || index > getLinkedListLength(headNode)){
      return null;
  }
  for (int i = 0; i < index; i++) {
    headNode = headNode.next;
  }
  return headNode;
}

獲取單鏈表的長度(注意我這裏定義的 headNode 是頭指針不是頭節點)

/**
 * 求單鏈表長度
 * @param headNode
 * @return
 */
private static int getLinkedListLength(ListNode headNode) {
  int countNode = 0;
  while (headNode.next != null){
    countNode++;
    headNode = headNode.next;
  }
 return countNode;
}

六、小結

單鏈表的相關操作就講解完了,其實通過上面對單鏈表的相關操作,我們不難發現,單鏈表的刪除和插入其實很方便,只需要改變指針的指向就可以完成,但是查找元素的時候就比較麻煩,因為在查找的時候,需要把整個鏈表從頭到尾遍歷一次。

公眾號:良許Linux

有收穫?希望老鐵們來個三連擊,給更多的人看到這篇文章

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

特斯拉與 BMW 談合作 可望發展碳纖維與電池技術

特斯拉(Tesla)明星執行長 Elon Musk 爆料,德國豪華車品牌 BMW 與特斯拉曾洽商合作,範圍涵蓋電池與輕量化車體零件。   Musk 在接受德國媒體 Der Spiegel 專訪時表示,對 BMW 以碳纖維材料強化車體感到非常有興趣,認為相當具成本效益,雙方在非正式場合會面時,有討論過合作的可能,但尚未達成決議。   據路透社報導,BMW 為發展碳纖維原料而成立合資公司 SGL,i3 電動車與 i8 油電混合動力跑車的駕駛艙與後蓋零件均採用到碳纖維材料。除此之外,Musk 還透露有意與 BMW 一起搞電池科技,以及電動車充電站,他甚至於計畫 5~6 年內在德國蓋一座電池廠。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

新北清潔公司,居家、辦公、裝潢細清專業服務

※教你寫出一流的銷售文案?

不需充電的零排放氫燃料電池機車 北市府試用評估成效

    繼新北市電動機車 E-bike 於 10 月上路後,台北市政府也採用能源局補助業者研發旳氫燃料電池機車,每台配備 2 支金屬儲氫罐,交換費用 30 元,行駛中排水,但二氧化碳排放為 0,續航力定速時可達 86 公里,不過,若在行駛市區時,遇上紅綠燈走走停停,續航力會降為 50 公里。環保局表示,廠商提供試騎的 15 輛氫燃料電池機車將用於環保稽查、工地巡查、土地丈量等業務。   環保局表示,氫燃料電池首創用於機車,金屬容器包覆氫氧化物粉末,相較氣體狀較安全,業者提供的 15 部試騎機車將停於於市府公務停車場,也會提供交換氣體,試用 3 個月後評估成效。   負責研發的亞太燃料電池科技專案經理陳建豪表示,氣燃料電池不須充電,而是利用能源轉換,將氫氣透過觸媒轉換成電能,轉換過程僅會排放水,該款氫燃料電池機車時速最快可達 60 公里。業者說,量產後機車售價約 7 萬元,但免燃料稅。陳建豪表示,全球各國多有氫燃料電池的安全使用規範,但台灣沒有,盼政府能協助立法。     (Source:)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※幫你省時又省力,新北清潔一流服務好口碑

※別再煩惱如何寫文案,掌握八大原則!

2015第六屆廣州國際新能源汽車工業展覽會

綠色科技   領航未來

時間:2015年5月16-18日

地點:廣州琶洲保利世貿博覽館

主辦單位:廣州中汽國際展覽有限公司

  • 專業的組織機構——展會是中汽國際展覽有限公司按照“專業化、國際化、品牌化”原則舉辦的新能源汽車及配套設施行業國際品牌盛會。國內外多家單位協辦,既有政府支持,又有行業權威的參與。展會舉行期間,將有商務部及省市領導、業界權威人士蒞臨展會參觀指導並出席開幕剪綵。
  • 龐大採購團隊——盛會將邀請來自中國、美國、德國、法國、英國、義大利、巴西、墨西哥、西班牙、俄羅斯、瑞典、捷克、匈牙利、中東、日本、韓國、印度、土耳其、新加坡、越南、泰國、臺灣、香港、澳門等國家和地區眾多新能源汽車及配套設施進出口貿易商、代理商、經銷商及國際著名相關採購協會組織率團到會參觀採購。
  • 高端論壇——展會將邀請新能源汽車及配套設施行業權威專家,討論中外新能源汽車及配套設施行業最新動態、發展趨勢、分工格局、相關對策等多個熱門議題。就中國新能源汽車及配套設施行業發展現狀及所面臨的問題作深入報告,針對中國新能源汽車及配套設施行業市場行情作研究報告,並對產品開發、技術創新等作細緻的演講。屆時,將安排我們認為在國際新能源汽車及配套設施領域,具有影響力的企業介紹產品概況、最新技術動向等進行講座和交流。擬邀請中國政府高級官員,有關專家、學者,國際著名機構代表,著名新能源汽車及配套設施產品供應商、採購商,中國新能源汽車及配套設施市場企業代表和其他專業觀眾出席,其行業的引導性和權威性令人期待,是中國新能源汽車及配套設施產業發展和國際交流合作的風向標,將是獲取新能源汽車及配套設施行業資訊和把握國際市場的最佳平臺。

展會介紹

在能源匱乏的時代,綠色、節能、環保成為經濟發展的核心主題,新能源汽車具有良好的環保性能和燃料經濟性好、運行成本低等優勢,既可以保護環境,又可以緩解能源的短缺並能調整能源的結構,保障能源的安全。

21世紀是一個面臨能源和環境巨大挑戰的世紀,傳統燃油汽車將向高效低排放的電動汽車及混合動力車方向發展。大力發展新能源汽車是能源與環境的必然要求,而且,中國發展新能源汽車的壓力更為緊迫。根據國家《汽車與新能源汽車產業發展規劃》(2011-2020),將在“十二五”期間重點發展清潔能源汽車,未來十年僅中央財政就投入上千億元用來支持以純電動車、混合動力汽車為代表的節能與新能源汽車的研發與推廣。2014年多地出臺補貼政策,2014年5月24日上午,國家主席習近平在上海汽車集團考察時強調,發展新能源汽車是我國從汽車大國邁向汽車強國的必由之路,要加大研發力度,認真研究市場,用好用活政策,開發適應各種需求的產品,使之成為一個強勁的增長點。可以預見,未來我國新能源汽車將會快速發展。

廣州是廣東省會,改革開放前沿城市,中國對外貿易的重要視窗,經濟實力雄厚,市場潛力巨大。廣東是泛珠三角經濟區域的中心,毗鄰港、澳、台,輻射東南亞,海、陸、空交通便利,市場輻射面廣,經濟發達。隨著CEPA的實施,粵、港、澳以及泛珠三角(9+2)區域合作與發展的良性互動,必將給新能源汽車及配套設施產業創造無限美好的發展前景。為順應高速發展的新能源汽車及配套設施行業,廣州中汽國際展覽有限公司聯合行業權威機構定于2015年6月8-10日在廣州琶洲保利世貿博覽館舉辦“2015第三屆廣州國際新能源汽車工業展覽會”(NEA CHINA 2015),展會將深化活動內涵,秉乘推動行業發展、為企業服務的宗旨,為商家提供一個拓展業務、技術交流、展示實力、獲取資訊、結交客戶、推廣新產品、尋找合作夥伴的國際商貿平臺。

我們將以“突出品牌、開拓創新、注重實效、強化服務”的辦展宗旨,憑藉獨特的創意,科學的組織管理和卓越的服務,以全新的理念為廣大中外參展商提供一個“高水準、高品味、高品質”的展示交流平臺,為全球新能源汽車及配套設施行業提供更多的合作機會,有力推動中國新能源汽車及配套設施產品全面進入全球採購體系,與世界各國新能源汽車及配套設施產業協調合作、互利共贏、共同發展進步。

展品範圍

  • 純電動車:轎車、大巴、公車、各旅行車、各種純電動特種車(環衛車、電力車、郵政車、小型客貨車、高爾夫車、房車、叉車、搬運車、旅遊觀光車、醫療車、警用車、摩托車、三輪車等);
  • 混合動力車:轎車、大巴、公車、各型旅行車等;
  • 其他能源車:超級電容、燃料電池、氫能、生物燃料、太陽能及氫能源、天然氣等各種新能源、清潔燃料及低排放、環保節能型車等;
  • 零部件:低排放節能型發動機、混合動力發動機及清潔燃料發動機;動力電池與管理系統;整車匯流排與控制系統;電機與電控系統;充電裝置;儲能裝置等;能源管理系統;電力電容器、飛輪、逆變器、電熱泵、電動助力轉向、電動空調、功率模組等;相關材料、工藝、技術;相關檢測、監控、試驗、安全防護裝備;維修、製造設備和工具等;
  • 充電設施:充電站智慧型網路專案規劃及成果展示,加油站擴建充(換)電站、加油充電綜合服務站展示,太陽能、風能互補新能源汽車充電站技術產品,充電站充電機、充電樁、配電設備、變壓器、更換設備、電能、監控系統、有源濾波裝置、配電櫃、電覽、直接充電設備、管理輔助設備、充換電池及電池管理系統、停車場充電設施、智慧監控、充電站供電解決方案、充電站等。
  • 其他:新能源汽車的整車及系統控制設計等。

目標觀眾

主辦單位將重點邀請的目標觀眾包括:

1、商務部、發改委、科技部、工信部、國家環保局等各局、司、中心、所領導;
2、全國各省市主管部門領導、大型企事業、機關單位領導;
3、全國各高校、科研單位、設計院、研究院、協(學)會領導;
4、公交、出租、環衛、郵政等單位負責人;車站、機場、碼頭、房地產、大型物業公司、高爾夫球場、旅遊景點、公園、體育場館、大專院校、醫院、療養院、度假村等單位負責人;
5、國內外著名生產、代理、經銷商、貿易公司等業內人士參觀、參展、技術交流。

展會日程
報到布展:2015年5月14-15日
展出時間:2015年5月16-18日
撤展時間:2015年5月18日下午

歡迎業界同仁踴躍報名參展,現正接受申請,請速與組委會聯繫,索取參展申請表及展位平面圖!
充分利用NEA CHINA 2015,鞏固您的市場地位!

Official website/大會官方網站:

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

桃園縣大力推廣電動機車 購買補助額度全國第一

隨著環保意識抬頭,越來越多地方政府推政策因應環保趨勢。桃園縣環保局鼓勵民眾使用電動機車,除了購車補助額度位居全國第一,更推出免費試騎 7 天的活動,讓民眾能夠體驗電動機車的高續航力及充電迅速等便利性,增加使用環保運具的意願。

桃園縣政府環境保護局為改善空氣品質,積極推廣使用電動機車等低污染交通工具,環保局局長陳世偉表示,電動車輛具有零加油、零廢氣、低保養、低噪音及低充電費等特性,是最環保的交通工具;尤其現在電池技術成熟,使得電動車輛的續航力大增,壽命也有效延長保固。

  (Source:)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

這 10 行比較字符串相等的代碼給我整懵逼了,不信你也來看看|原創版

抱歉用這種標題吸引你點進來了,不過你不妨看完,看看能否讓你有所收穫。​(有收穫,請評論區留個言,沒收穫,下周末我直播吃**,哈哈,這你也信)

補充說明:微信公眾號改版,對各個號主影響還挺大的。目前從後台數據來看,對我影響不大,因為我這反正都是小號,閱讀量本身就少的可憐,真相了,狗頭(剛從交流群學會的表情)。

先直接上代碼:

boolean safeEqual(String a, String b) { if (a.length() != b.length()) { return false; } int equal = 0; for (int i = 0; i < a.length(); i++) { equal |= a.charAt(i) ^ b.charAt(i); } return equal == 0; } 

上面的代碼是我根據原版(Scala)翻譯成 Java的,Scala 版本(最開始吸引程序猿石頭注意力的代碼)如下:

def safeEqual(a: String, b: String) = { if (a.length != b.length) { false } else { var equal = 0 for (i <- Array.range(0, a.length)) { equal |= a(i) ^ b(i) } equal == 0 } } 

剛開始看到這段源碼感覺挺奇怪的,這個函數的功能是比較兩個字符串是否相等,首先“長度不等結果肯定不等,立即返回”這個很好理解。

再看看後面的,稍微動下腦筋,轉彎下也能明白這其中的門道:通過異或操作1^1=0, 1^0=1, 0^0=0,來比較每一位,如果每一位都相等的話,兩個字符串肯定相等,最後存儲累計異或值的變量equal必定為 0,否則為 1。

再細想一下呢?

for (i <- Array.range(0, a.length)) { if (a(i) ^ b(i) != 0) // or a(i) != b[i] return false } 

我們常常講性能優化,從效率角度上講,難道不是應該只要中途發現某一位的結果不同了(即為1)就可以立即返回兩個字符串不相等了嗎?(如上所示)

這其中肯定有……

再再細想一下呢?

結合方法名稱 safeEquals 可能知道些眉目,與安全有關。

本文開篇的代碼來自playframewok 里用來驗證cookie(session)中的數據是否合法(包含簽名的驗證),也是石頭寫這篇文章的由來。

以前知道通過延遲計算等手段來提高效率的手段,但這種已經算出結果卻延遲返回的,還是頭一回!

我們來看看,JDK 中也有類似的方法,如下代碼摘自 java.security.MessageDigest

public static boolean isEqual(byte[] digesta, byte[] digestb) { if (digesta == digestb) return true; if (digesta == null || digestb == null) { return false; } if (digesta.length != digestb.length) { return false; } int result = 0; // time-constant comparison for (int i = 0; i < digesta.length; i++) { result |= digesta[i] ^ digestb[i]; } return result == 0; } 

看註釋知道了,目的是為了用常量時間複雜度進行比較。

但這個計算過程耗費的時間不是常量有啥風險? (腦海里響起了背景音樂:“小朋友,你是否有很多問號?”)

真相大白

再深入探索和了解了一下,原來這麼做是為了防止計時攻擊(Timing Attack)。(也有人翻譯成時序攻擊​)​

計時攻擊(Timing Attack)

計時攻擊是邊信道攻擊(或稱”側信道攻擊”, Side Channel Attack, 簡稱SCA) 的一種,邊信道攻擊是一種針對軟件或硬件設計缺陷,走“歪門邪道”的一種攻擊方式。

這種攻擊方式是通過功耗、時序、電磁泄漏等方式達到破解目的。在很多物理隔絕的環境中,往往也能出奇制勝,這類新型攻擊的有效性遠高於傳統的密碼分析的數學方法(某百科上說的)。

這種手段可以讓調用 safeEquals("abcdefghijklmn", "xbcdefghijklmn") (只有首位不相同)和調用 safeEquals("abcdefghijklmn", "abcdefghijklmn") (兩個完全相同的字符串)的所耗費的時間一樣。防止通過大量的改變輸入並通過統計運行時間來暴力破解出要比較的字符串。

舉個,如果用之前說的“高效”的方式來實現的話。假設某個用戶設置了密碼為 password,通過從a到z(實際範圍可能更廣)不斷枚舉第一位,最終統計發現 p0000000 的運行時間比其他從任意a~z的都長(因為要到第二位才能發現不同,其他非 p 開頭的字符串第一位不同就直接返回了),這樣就能猜測出用戶密碼的第一位很可能是p了,然後再不斷一位一位迭代下去最終破解出用戶的密碼。

當然,以上是從理論角度分析,確實容易理解。但實際上好像通過統計運行時間總感覺不太靠譜,這個運行時間對環境太敏感了,比如網絡,內存,CPU負載等等都會影響。

但安全問題感覺更像是 “寧可信其有,不可信其無”。為了防止(特別是與簽名/密碼驗證等相關的操作)被 timing attack,目前各大語言都提供了相應的安全比較函數。各種軟件系統(例如 OpenSSL)、框架(例如 Play)的實現也都採用了這種方式。

例如 “世界上最好的編程語言”(粉絲較少,評論區應該打不起架來)—— php中的:

// Compares two strings using the same time whether they're equal or not. // This function should be used to mitigate timing attacks; // for instance, when testing crypt() password hashes. bool hash_equals ( string $known_string , string $user_string ) //This function is safe against timing attacks. boolean password_verify ( string $password , string $hash ) 

其實各種語言版本的實現方式都與上面的版本差不多,將兩個字符串每一位取出來異或(^)並用或(|)保存,最後通過判斷結果是否為 0 來確定兩個字符串是否相等。

如果剛開始沒有用 safeEquals 去實現,後續的版本還會通過打補丁的方式去修復這樣的安全隱患。

例如 JDK 1.6.0_17 中的Release Notes[1]中就提到了MessageDigest.isEqual 中的bug的修復,如下圖所示:

MessageDigest timing attack vulnerabilities

大家可以看看這次變更的的詳細信息openjdk中的 bug fix diff[2]為:

MessageDigest.isEqual計時攻擊

Timing Attack 真的可行嗎?

我覺得各大語言的 API 都用這種實現,肯定還是有道理的,理論上應該可以被利用的。 這不,學術界的這篇論文就宣稱用這種計時攻擊的方法破解了 OpenSSL 0.9.7 的RSA加密算法了。關於 RSA 算法的介紹可以看看之前本人寫的這篇文章。

這篇Remote Timing Attacks are Practical[3] 論文中指出(我大致翻譯下摘要,感興趣的同學可以通過文末鏈接去看原文):

計時攻擊往往用於攻擊一些性能較弱的計算設備,例如一些智能卡。我們通過實驗發現,也能用於攻擊普通的軟件系統。本文通過實驗證明,通過這種計時攻擊方式能夠攻破一個基於 OpenSSL 的 web 服務器的私鑰。結果證明計時攻擊用於進行網絡攻擊在實踐中可行的,因此各大安全系統需要抵禦這種風險。

最後,本人畢竟不是專研完全方向,以上描述是基於本人的理解,如果有不對的地方,還請大家留言指出來。感謝。

補充說明2:感謝正在閱讀文章的你,讓我還有動力繼續堅持更新原創。

本人發文不多,但希望寫的文章能達到的目的是:佔用你的閱讀時間,就盡量能夠讓你有所收穫。

如果你覺得我的文章有所幫助,還請你幫忙轉發分享,另外請別忘了點擊公眾號右上角加個星標,好讓你別錯過後續的精彩文章(微信改版了,或許我發的文章都不能推送到你那了)。

​原創真心不易,希望你能幫我個小忙唄,如果本文內容你覺得有所啟發,有所收穫,請幫忙點個“在看”唄,或者轉發分享讓更多的小夥伴看到。 ​ 參考資料:

  • Timing Attacks on RSA: Revealing Your Secrets through the Fourth Dimension
  • Remote Timing Attacks are Practical

參考資料

[1] Release Notes: http://www.oracle.com/technetwork/java/javase/6u17-141447.html

[2] openjdk中的 bug fix diff: http://hg.openjdk.java.net/jdk6/jdk6/jdk/rev/562da0baf70b

[3] Remote Timing Attacks are Practical: http://crypto.stanford.edu/~dabo/papers/ssl-timing.pdf

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

新北清潔公司,居家、辦公、裝潢細清專業服務

※推薦評價好的iphone維修中心

【JAVA8新的時間與日期 API】- 傳統時間格式化的線程安全問題

Java8之前的日期和時間API,存在一些問題,最重要的就是線程安全的問題。這些問題都在Java8中的日期和時間API中得到了解決,而且Java8中的日期和時間API更加強大。

傳統時間格式化的線程安全問題

示例:

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;

public class TestOldSimpleDateFormat {
    public static void main(String[] args) throws Exception {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        Callable<Date> task = new Callable<Date>() {
            @Override
            public Date call() throws Exception {
                return sdf.parse("2020-01-01");
            }
        };
        ExecutorService pool = Executors.newFixedThreadPool(10);
        List<Future<Date>> list = new ArrayList<>();
        for (int i=0;i<10;i++){
            Future<Date> future = pool.submit(task);
            list.add(future);
        }
        for (Future<Date> future : list){
            System.out.println(future.get());
        }

     pool.shutdown(); } }

以上代碼運行會報錯:

 

 

報錯緣由:取部分源碼解釋

    /**
     * SimpleDateFormat 類的 parse 方法 部分源碼
     */
    public Date parse(String source) throws ParseException
    {
        ParsePosition pos = new ParsePosition(0);
        Date result = parse(source, pos); 
        if (pos.index == 0)
            throw new ParseException("Unparseable date: \"" + source + "\"" ,
                pos.errorIndex);
        return result;
    }

public Date parse(String text, ParsePosition pos)
    {
        // 省略上面諸多代碼
        Date parsedDate;

        CalendarBuilder calb = new CalendarBuilder();
        try {
            //這裏這個 calendar 對象是 SimpleDateFormat 類的父類 DateFormat 中的屬性  : protected Calendar calendar;
            parsedDate = calb.establish(calendar).getTime();//這個 calb.establish(calendar) 方法中,這個方法中的主要步驟不是原子操作,並且會對 calendar 對象進行修改,所以在多線程環境下就會出現線程安全問題。
            // 省略下面面諸多代碼
        }
        catch (IllegalArgumentException e) {
            //省略.........................
            return null;
        }
        return parsedDate;
    }
 Calendar establish(Calendar cal) {
        boolean weekDate = isSet(WEEK_YEAR)
                            && field[WEEK_YEAR] > field[YEAR];
        if (weekDate && !cal.isWeekDateSupported()) {
            // Use YEAR instead
            if (!isSet(YEAR)) {
                set(YEAR, field[MAX_FIELD + WEEK_YEAR]);
            }
            weekDate = false;
        }

        cal.clear();
        // Set the fields from the min stamp to the max stamp so that
        // the field resolution works in the Calendar.
        for (int stamp = MINIMUM_USER_STAMP; stamp < nextStamp; stamp++) {
            for (int index = 0; index <= maxFieldIndex; index++) {
                if (field[index] == stamp) {
                    cal.set(index, field[MAX_FIELD + index]);
                    break;
                }
            }
        }

        if (weekDate) {
            int weekOfYear = isSet(WEEK_OF_YEAR) ? field[MAX_FIELD + WEEK_OF_YEAR] : 1;
            int dayOfWeek = isSet(DAY_OF_WEEK) ?
                                field[MAX_FIELD + DAY_OF_WEEK] : cal.getFirstDayOfWeek();
            if (!isValidDayOfWeek(dayOfWeek) && cal.isLenient()) {
                if (dayOfWeek >= 8) {
                    dayOfWeek--;
                    weekOfYear += dayOfWeek / 7;
                    dayOfWeek = (dayOfWeek % 7) + 1;
                } else {
                    while (dayOfWeek <= 0) {
                        dayOfWeek += 7;
                        weekOfYear--;
                    }
                }
                dayOfWeek = toCalendarDayOfWeek(dayOfWeek);
            }
            cal.setWeekDate(field[MAX_FIELD + WEEK_YEAR], weekOfYear, dayOfWeek);
        }
        return cal;
    }

綜上,我們可以看到 SimpleDateFormat 類中的parse 方法,調用了 CalendarBuilder 的 establish(calendar) 方法,並在方法中,對 calendar 對象進行了各種判斷及修改,並且這些操作都不是原子操作或同步操作,而這個calendar 對象又是 SimpleDateFormat 的父類 DateFormat 的一個實例變量,所以,在多線程同時調用SimpleDateFormat 的 parse 方法的時候,就會出現線程安全問題。


針對以上異常,JAVA8之前的解決辦法:

1. 將 SimpleDateFormat 對象定義成局部變量。

2. 加鎖。

3. 使用ThreadLocal,每個線程都擁有自己的SimpleDateFormat對象副本。

示例(加鎖):

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        Callable<Date> task = new Callable<Date>() {
            @Override
            public synchronized Date call() throws Exception {//加個同步,解決問題
                return sdf.parse("2020-01-01");
//                return DateFormatThreadLocal.convert("2020-01-01");
            }
        };
        ExecutorService pool = Executors.newFixedThreadPool(10);
        List<Future<Date>> list = new ArrayList<>();
        for (int i=0;i<10;i++){
            Future<Date> future = pool.submit(task);
            list.add(future);
        }
        for (Future<Date> future : list){
            System.out.println(future.get());
        }
        pool.shutdown();

 

示例(ThreadLocal):

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateFormatThreadLocal {
    private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd");
        }
    };
    public static Date convert(String source) throws Exception {
        return df.get().parse(source);
    }
}


////////////////////////////////////////////////////////////////

public class TestOldSimpleDateFormat {
    public static void main(String[] args) throws Exception {
//        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        Callable<Date> task = new Callable<Date>() {
            @Override
            public Date call() throws Exception {
//                return sdf.parse("2020-01-01");
                return DateFormatThreadLocal.convert("2020-01-01");
            }
        };
        ExecutorService pool = Executors.newFixedThreadPool(10);
        List<Future<Date>> list = new ArrayList<>();
        for (int i=0;i<10;i++){
            Future<Date> future = pool.submit(task);
            list.add(future);
        }
        for (Future<Date> future : list){
            System.out.println(future.get());
        }
     pool.shutdown();
} }

 

JAVA8的解決辦法:使用新的API(DateTimeFormatter  和 LocalDate )

示例:

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

public class TestOldSimpleDateFormat {
    public static void main(String[] args) throws Exception {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

        Callable<LocalDate> task = new Callable<LocalDate>() {
            @Override
            public LocalDate call() throws Exception {
//                return sdf.parse("2020-01-01");
                return LocalDate.parse("2020-01-01",formatter);
            }
        };
        ExecutorService pool = Executors.newFixedThreadPool(10);
        List<Future<LocalDate>> list = new ArrayList<>();
        for (int i=0;i<10;i++){
            Future<LocalDate> future = pool.submit(task);
            list.add(future);
        }
        for (Future<LocalDate> future : list){
            System.out.println(future.get());
        }
        pool.shutdown();
    }
}

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準

Halcon斑點分析官方示例講解

官方示例中有許多很好的例子可以幫助大家理解和學習Halcon,下面舉幾個經典的斑點分析例子講解一下

Crystals

圖中显示了在高層大氣中採集到的晶體樣本的圖像。任務是分析對象以確定特定形狀的頻率。重要的對象之一是六角形。

首先,使用read_image從文件中讀取圖像。由於晶體的對比度相對較低且結合了不均勻的背景,因此使用局部閾值執行對象的分割。該輪次由平均過濾器mean_image確定。選擇濾光罩的尺寸,使其具有暗區寬度的大約三倍。 dyn_threshold現在將平滑的和原始的灰色進行比較,選擇那些通過8個灰度值的對比而變暗的像素。connection將對象分為連接的組件。下圖显示了此初始分割的結果。

read_image (Image, 'crystal')
mean_image (Image, ImageMean, 21, 21)
dyn_threshold (Image, ImageMean, RegionDynThresh, 8, 'dark')
connection (RegionDynThresh, ConnectedRegions)

現在的任務是僅選擇六邊形的晶體。為此,首先變成他們的凸包,這就像在每個區域周圍都使用橡皮筋。在這些區域中,選擇那些具有較大的(select_shape)並具有給定灰度值分佈(select_gray)的對象。確定選擇的參數,以便僅保留相關的晶體如下圖。

shape_trans (ConnectedRegions, ConvexRegions, 'convex')
select_shape (ConvexRegions, LargeRegions, 'area', 'and', 600, 2000)
select_gray (LargeRegions, Image, Crystals, 'entropy', 'and', 1, 5.6)

源程序

* crystal.hdev: extraction of hexagonally shaped crystals via local thresholding and region post-processing
* 
dev_close_window ()
dev_update_window ('off')
* ****
* step: acquire image獲取圖像
* ****
read_image (Image, 'crystal')
get_image_size (Image, Width, Height)
dev_open_window_fit_image (Image, 0, 0, Width, Height, WindowID)
set_display_font (WindowID, 12, 'mono', 'true', 'false')
dev_set_draw ('margin')
dev_set_line_width (2)
dev_display (Image)
disp_continue_message (WindowID, 'black', 'true')
stop ()
* ****
* step: segment image分割圖像
* ****
* -> using a local threshold
mean_image (Image, ImageMean, 21, 21)
dyn_threshold (Image, ImageMean, RegionDynThresh, 8, 'dark')
* -> extract connected components
connection (RegionDynThresh, ConnectedRegions)
dev_display (ConnectedRegions)
disp_continue_message (WindowID, 'black', 'true')
stop ()
* ****
* step: process regions處理區域
* ****
shape_trans (ConnectedRegions, ConvexRegions, 'convex')
select_shape (ConvexRegions, LargeRegions, 'area', 'and', 600, 2000)
select_gray (LargeRegions, Image, Crystals, 'entropy', 'and', 1, 5.6)
dev_display (Image)
dev_display (Crystals)
Atoms

專業顯微鏡能夠確定單個原子的大致位置,這對於例如分析PN結晶體的晶格變化很有用,使用分水嶺方法在這類圖片上細分效果很好。在這裏,每個暗區作為單個區域返回。因為在圖像的外部原子僅部分可見,第一個任務是僅提取那些不靠近圖像邊界的原子。最後提取不規則,這是通過尋找形狀(被擠壓)的異常原子實現的。

gauss_filter (Image, ImageGauss, 5)
watersheds (ImageGauss, Basins, Watersheds)

select_shape (Basins, SelectedRegions1, 'column1', 'and', 2, Width - 1)
select_shape (SelectedRegions1, SelectedRegions2, 'row1', 'and', 2, Height - 1)
select_shape (SelectedRegions2, SelectedRegions3, 'column2', 'and', 1, Width - 3)
select_shape (SelectedRegions3, Inner, 'row2', 'and', 1, Height - 3)
select_shape (Inner, Irregular, ['moments_i1','moments_i1'], 'or', [0,9.5e8], [1.5e8,1e10])

分水嶺方法劃分圖像

結果圖

源程序

* atoms.hdev: Locates irregularities in an atomic grid structure
* 
dev_close_window ()
dev_update_window ('off')
* ****
* Acquire image獲取圖像
* ****
read_image (Image, 'atoms')
get_image_size (Image, Width, Height)
crop_rectangle1 (Image, Image, Height / 2, 0, Height - 1, Width - 1)
get_image_size (Image, Width, Height)
dev_open_window_fit_image (Image, 0, 0, -1, -1, WindowID)
set_display_font (WindowID, 14, 'mono', 'true', 'false')
dev_set_draw ('margin')
dev_set_line_width (2)
dev_display (Image)
disp_message (WindowID, 'Original image', 'window', 12, 12, 'black', 'true')
disp_continue_message (WindowID, 'black', 'true')
stop ()
* ****
* Segment image分割圖像
* ****
* -> Using watershed
gauss_filter (Image, ImageGauss, 5)
watersheds (ImageGauss, Basins, Watersheds)
dev_display (Image)
dev_set_colored (12)
dev_display (Watersheds)
disp_message (WindowID, 'Watersheds', 'window', 12, 12, 'black', 'true')
disp_continue_message (WindowID, 'black', 'true')
stop ()
* ****
* Process regions處理區域
* ****
* -> Skip regions at the border of the image
smallest_rectangle1 (Basins, Row1, Column1, Row2, Column2)
select_shape (Basins, SelectedRegions1, 'column1', 'and', 2, Width - 1)
select_shape (SelectedRegions1, SelectedRegions2, 'row1', 'and', 2, Height - 1)
select_shape (SelectedRegions2, SelectedRegions3, 'column2', 'and', 1, Width - 3)
select_shape (SelectedRegions3, Inner, 'row2', 'and', 1, Height - 3)
* -> Select irregularly shaped atoms
select_shape (Inner, Irregular, ['moments_i1','moments_i1'], 'or', [0,9.5e8], [1.5e8,1e10])
dev_display (Image)
dev_set_line_width (1)
dev_set_color ('white')
dev_display (Inner)
dev_set_line_width (3)
dev_set_color ('red')
dev_display (Irregular)
disp_message (WindowID, 'Defects', 'window', 12, 12, 'black', 'true')
Analyzing Particles

本示例的任務是分析液體中的顆粒。此應用程序的主要困難是存在兩種類型的物體:大的明亮物體和對比度低的小物體。此外,還存在噪音干擾。

該程序使用兩種不同的方法分別對兩類對象進行分段:全局閾值和局部閾值。通過附加的后處理,可以以可靠的方式提取小顆粒。

threshold (Image, Large, 110, 255)
dilation_circle (Large, LargeDilation, 7.5)

complement (LargeDilation, NotLarge)
reduce_domain (Image, NotLarge, ParticlesRed)
mean_image (ParticlesRed, Mean, 31, 31)
dyn_threshold (ParticlesRed, Mean, SmallRaw, 3, 'light')
opening_circle (SmallRaw, Small, 2.5)
connection (Small, SmallConnection)

源程序

* particle.hdev: Measurement of small particles
* 
dev_update_off ()
dev_close_window ()
dev_open_window (0, 0, 512, 512, 'black', WindowID)
set_display_font (WindowID, 14, 'mono', 'true', 'false')
read_image (Image, 'particle')
dev_display (Image)
dev_disp_text ('Original image', 'window', 12, 12, 'black', [], [])
dev_disp_text ('Press Run (F5) to continue', 'window', 'bottom', 'right', 'black', [], [])
stop ()
threshold (Image, Large, 110, 255)
* Dilate regions with a circular structuring element
dilation_circle (Large, LargeDilation, 7.5)
dev_display (Image)
dev_set_draw ('margin')
dev_set_line_width (3)
dev_set_color ('red')
dev_display (LargeDilation)
dev_set_draw ('fill')
dev_disp_text ('Exclude large areas from processing', 'window', 12, 12, 'black', [], [])
dev_disp_text ('Press Run (F5) to continue', 'window', 'bottom', 'right', 'black', [], [])
stop ()
* Continue to calculate small regions
* Return the complement of a region
complement (LargeDilation, NotLarge)
reduce_domain (Image, NotLarge, ParticlesRed)
mean_image (ParticlesRed, Mean, 31, 31)
* Segment the image using a local threshold
dyn_threshold (ParticlesRed, Mean, SmallRaw, 3, 'light')
opening_circle (SmallRaw, Small, 2.5)
connection (Small, SmallConnection)
dev_display (Image)
dev_set_colored (12)
dev_display (SmallConnection)
dev_disp_text ('Extracted small particles', 'window', 12, 12, 'black', [], [])
dev_disp_text ('Press Run (F5) to continue', 'window', 'bottom', 'right', 'black', [], [])
stop ()
* Continue to select several regions and to get information
dev_set_color ('green')
dev_display (Image)
dev_set_draw ('margin')
dev_display (SmallConnection)
Button := 1
* Define limits for the displayed message at the end of the while-loop.
MaxRow := 450
MaxColumn := 440
MinRow := 40
MinColumn := 100
while (Button == 1)
    dev_disp_text (['Select object with left mouse button','Right button to quit'], 'window', 12, 12, 'black', 'box_color', '#fce9d4dd')
    dev_set_color ('green')
    get_mbutton (WindowID, Row, Column, Button)
    dev_display (Image)
    dev_display (SmallConnection)
    dev_set_color ('red')
    select_region_point (SmallConnection, SmallSingle, Row, Column)
    dev_display (SmallSingle)
    count_obj (SmallSingle, NumSingle)
    if (NumSingle == 1)
        intensity (SmallSingle, Image, MeanGray, DeviationGray)
        area_center (SmallSingle, Area, Row, Column)
        * Limit the message so that it is displayed entirely inside the graphics window.
        if (Row > MaxRow)
            Row := MaxRow
        endif
        if (Column > MaxColumn)
            Column := MaxColumn
        endif
        if (Row < MinRow)
            Row := MinRow
        endif
        if (Column < MinColumn)
            Column := MinColumn
        endif
        dev_disp_text (['Area = ' + Area,'Intensity = ' + MeanGray$'.3'], 'image', Row + 10, Column - 90, 'black', 'box_color', '#fce9d4dd')
    endif
endwhile
dev_set_line_width (1)
dev_update_on ()
Extracting Forest Features from Color Infrared Image

本示例的任務是在圖中所示的彩色紅外圖像中檢測不同的對象類別:樹(針恭弘=叶 恭弘和落恭弘=叶 恭弘),草地和道路

圖像數據是彩色紅外圖像,由於其特定的顏色,可以非常輕鬆地提取道路。需要做到那樣的話,要將多通道圖像拆分為單通道。

read_image (Forest, 'forest_air1')
decompose3 (Forest, Red, Green, Blue)
threshold (Blue, BlueBright, 80, 255)
connection (BlueBright, BlueBrightConnection)
select_shape (BlueBrightConnection, Path, 'area', 'and', 100, 100000000)

山毛櫸樹根據其在紅色通道中的強度和最小大小進行分割

threshold (Red, RedBright, 120, 255)
connection (RedBright, RedBrightConnection)
select_shape (RedBrightConnection, RedBrightBig, 'area', 'and', 1500, 10000000)
closing_circle (RedBrightBig, RedBrightClosing, 7.5)
opening_circle (RedBrightClosing, RedBrightOpening, 9.5)
connection (RedBrightOpening, RedBrightOpeningConnection)
select_shape (RedBrightOpeningConnection, BeechBig, 'area', 'and', 1000, 100000000)
select_gray (BeechBig, Blue, Beech, 'mean', 'and', 0, 59)

草地具有相似的光譜特性,但亮度略高

union1 (Beech, BeechUnion)
complement (BeechUnion, NotBeech)
difference (NotBeech, Path, NotBeechNotPath)
reduce_domain (Red, NotBeechNotPath, NotBeechNotPathRed)
threshold (NotBeechNotPathRed, BrightRest, 150, 255)
connection (BrightRest, BrightRestConnection)
select_shape (BrightRestConnection, Meadow, 'area', 'and', 500, 1000000)

使用分水嶺方法提取針恭弘=叶 恭弘樹,並在盆地內部增加閾值

union2 (Path, RedBrightClosing, BeechPath)
smooth_image (Red, RedGauss, 'gauss', 4.0)
invert_image (RedGauss, Invert)
watersheds (Invert, SpruceRed, Watersheds)
select_shape (SpruceRed, SpruceRedLarge, 'area', 'and', 100, 5000)
select_gray (SpruceRedLarge, Red, SpruceRedInitial, 'max', 'and', 100, 200)
gen_empty_obj (LocalThresh)
count_obj (SpruceRedInitial, NumSpruce)
dev_update_var ('off')
dev_update_pc ('off')
for i := 1 to NumSpruce by 1
    select_obj (SpruceRedInitial, SingleSpruce, i)
    min_max_gray (SingleSpruce, Red, 50, Min, Max, Range)
    reduce_domain (Red, SingleSpruce, SingleSpruceRed)
    threshold (SingleSpruceRed, SingleSpruceBright, Min, 255)
    connection (SingleSpruceBright, SingleSpruceBrightCon)
    select_shape_std (SingleSpruceBrightCon, MaxAreaSpruce, 'max_area', 70)
    concat_obj (MaxAreaSpruce, LocalThresh, LocalThresh)
endfor
opening_circle (LocalThresh, FinalSpruce, 1.5)

源程序

dev_close_window ()
dev_update_window ('off')
read_image (Forest, 'forest_air1')
get_image_size (Forest, Width, Height)
dev_open_window (0, 0, Width, Height, 'black', WindowHandle)
decompose3 (Forest, Red, Green, Blue)
dev_display (Red)
threshold (Blue, BlueBright, 80, 255)
connection (BlueBright, BlueBrightConnection)
select_shape (BlueBrightConnection, Path, 'area', 'and', 100, 100000000)
dev_set_color ('red')
dev_set_draw ('margin')
dev_display (Path)
disp_continue_message (WindowHandle, 'black', 'true')
stop ()
threshold (Red, RedBright, 120, 255)
connection (RedBright, RedBrightConnection)
select_shape (RedBrightConnection, RedBrightBig, 'area', 'and', 1500, 10000000)
closing_circle (RedBrightBig, RedBrightClosing, 7.5)
opening_circle (RedBrightClosing, RedBrightOpening, 9.5)
connection (RedBrightOpening, RedBrightOpeningConnection)
select_shape (RedBrightOpeningConnection, BeechBig, 'area', 'and', 1000, 100000000)
select_gray (BeechBig, Blue, Beech, 'mean', 'and', 0, 59)
dev_display (Red)
dev_display (Beech)
disp_continue_message (WindowHandle, 'black', 'true')
stop ()
union1 (Beech, BeechUnion)
complement (BeechUnion, NotBeech)
difference (NotBeech, Path, NotBeechNotPath)
reduce_domain (Red, NotBeechNotPath, NotBeechNotPathRed)
threshold (NotBeechNotPathRed, BrightRest, 150, 255)
connection (BrightRest, BrightRestConnection)
select_shape (BrightRestConnection, Meadow, 'area', 'and', 500, 1000000)
dev_display (Red)
dev_display (Meadow)
disp_continue_message (WindowHandle, 'black', 'true')
stop ()
union2 (Path, RedBrightClosing, BeechPath)
smooth_image (Red, RedGauss, 'gauss', 4.0)
invert_image (RedGauss, Invert)
watersheds (Invert, SpruceRed, Watersheds)
select_shape (SpruceRed, SpruceRedLarge, 'area', 'and', 100, 5000)
select_gray (SpruceRedLarge, Red, SpruceRedInitial, 'max', 'and', 100, 200)
gen_empty_obj (LocalThresh)
count_obj (SpruceRedInitial, NumSpruce)
dev_update_var ('off')
dev_update_pc ('off')
for i := 1 to NumSpruce by 1
    select_obj (SpruceRedInitial, SingleSpruce, i)
    min_max_gray (SingleSpruce, Red, 50, Min, Max, Range)
    reduce_domain (Red, SingleSpruce, SingleSpruceRed)
    threshold (SingleSpruceRed, SingleSpruceBright, Min, 255)
    connection (SingleSpruceBright, SingleSpruceBrightCon)
    select_shape_std (SingleSpruceBrightCon, MaxAreaSpruce, 'max_area', 70)
    concat_obj (MaxAreaSpruce, LocalThresh, LocalThresh)
endfor
opening_circle (LocalThresh, FinalSpruce, 1.5)
dev_set_line_width (2)
dev_set_color ('red')
dev_display (Red)
dev_display (FinalSpruce)
dev_set_color ('green')
dev_display (Beech)
dev_set_color ('yellow')
dev_display (Meadow)
Checking a Boundary for Fins

本示例的任務是檢查塑料零件的外邊界。在這種情況下,某些對象會显示鰭

程序首先提取背景區域(鰭显示為壓痕)

binary_threshold (Fin, Background, 'max_separability', 'light', UsedThreshold)

然後使用形態學運算符關閉背景區域中的壓痕

 closing_circle (Background, ClosedBackground, 250)

封閉區域與原始區域之間的顯著差異是鰭

 difference (ClosedBackground, Background, RegionDifference)
 opening_rectangle1 (RegionDifference, FinRegion, 5, 5)

源程序

* fin.hdev: Detection of a fin
* 
dev_update_window ('off')
read_image (Fins, 'fin' + [1:3])
get_image_size (Fins, Width, Height)
dev_close_window ()
dev_open_window (0, 0, Width[0], Height[0], 'black', WindowID)
set_display_font (WindowID, 14, 'mono', 'true', 'false')
for I := 1 to 3 by 1
    select_obj (Fins, Fin, I)
    dev_display (Fin)
    binary_threshold (Fin, Background, 'max_separability', 'light', UsedThreshold)
    dev_set_color ('blue')
    dev_set_draw ('margin')
    dev_set_line_width (4)
    dev_display (Background)
    disp_continue_message (WindowID, 'black', 'true')
    stop ()
    closing_circle (Background, ClosedBackground, 250)
    dev_set_color ('green')
    dev_display (ClosedBackground)
    disp_continue_message (WindowID, 'black', 'true')
    stop ()
    difference (ClosedBackground, Background, RegionDifference)
    opening_rectangle1 (RegionDifference, FinRegion, 5, 5)
    dev_display (Fin)
    dev_set_color ('red')
    dev_display (FinRegion)
    area_center (FinRegion, FinArea, Row, Column)
    if (I < 3)
        disp_continue_message (WindowID, 'black', 'true')
        stop ()
    endif
endfor
Bonding Balls

本示例的任務是檢查圖中PCB板所示的球形鍵合直徑

球形鍵的提取有兩個步驟:首先,通過分割亮區來定位裸片,然後將它們轉換為最小的矩形

threshold (Bond, Bright, 100, 255)
shape_trans (Bright, Die, 'rectangle2')

現在,使用reduce_domain處理模具內部的區域。在此ROI中,程序檢查與線材相對應的深色區域

reduce_domain (Bond, Die, DieGrey)
threshold (DieGrey, Wires, 0, 50)
fill_up_shape (Wires, WiresFilled, 'area', 1, 100)

刪除不相關的結構,並按預定順序排列鍵提取所需的特徵

opening_circle (WiresFilled, Balls, 15.5)
connection (Balls, SingleBalls)
select_shape (SingleBalls, IntermediateBalls, 'circularity', 'and', 0.85, 1.0)
sort_region (IntermediateBalls, FinalBalls, 'first_point', 'true', 'column')
smallest_circle (FinalBalls, Row, Column, Radius)

源代碼

* ball.hdev: Inspection of Ball Bonding
* 
dev_update_window ('off')
dev_close_window ()
dev_open_window (0, 0, 728, 512, 'black', WindowID)
read_image (Bond, 'die/die_03')
dev_display (Bond)
set_display_font (WindowID, 14, 'mono', 'true', 'false')
disp_continue_message (WindowID, 'black', 'true')
stop ()
threshold (Bond, Bright, 100, 255)
shape_trans (Bright, Die, 'rectangle2')
dev_set_color ('green')
dev_set_line_width (3)
dev_set_draw ('margin')
dev_display (Die)
disp_continue_message (WindowID, 'black', 'true')
stop ()
reduce_domain (Bond, Die, DieGrey)
threshold (DieGrey, Wires, 0, 50)
fill_up_shape (Wires, WiresFilled, 'area', 1, 100)
dev_display (Bond)
dev_set_draw ('fill')
dev_set_color ('red')
dev_display (WiresFilled)
disp_continue_message (WindowID, 'black', 'true')
stop ()
opening_circle (WiresFilled, Balls, 15.5)
dev_set_color ('green')
dev_display (Balls)
disp_continue_message (WindowID, 'black', 'true')
stop ()
connection (Balls, SingleBalls)
select_shape (SingleBalls, IntermediateBalls, 'circularity', 'and', 0.85, 1.0)
sort_region (IntermediateBalls, FinalBalls, 'first_point', 'true', 'column')
dev_display (Bond)
dev_set_colored (12)
dev_display (FinalBalls)
disp_continue_message (WindowID, 'black', 'true')
stop ()
smallest_circle (FinalBalls, Row, Column, Radius)
NumBalls := |Radius|
Diameter := 2 * Radius
meanDiameter := mean(Diameter)
minDiameter := min(Diameter)
dev_display (Bond)
disp_circle (WindowID, Row, Column, Radius)
dev_set_color ('white')
disp_message (WindowID, 'D: ' + Diameter$'.4', 'image', Row - 2 * Radius, Column, 'white', 'false')
dev_update_window ('on')
Surface Scratches

本示例檢測金屬表面上的划痕
分割的主要困難是背景不均勻以及划痕是薄的結構。可以使用局部閾值解決這兩個問題。即算子mean_image和dyn_threshold,在connection后,將小對象(主要是噪聲)移除

mean_image (Image, ImageMean, 7, 7)
dyn_threshold (Image, ImageMean, DarkPixels, 5, 'dark')
connection (DarkPixels, ConnectedRegions)
select_shape (ConnectedRegions, SelectedRegions, 'area', 'and', 10, 1000)

選擇的一部分是划痕,但是如果我們仔細觀察,就會發現它們被部分分割了。為了解決這個問題,我們將所有分割部分再次合併到一個大區域中。通過應用dilation_circle將具有給定最大距離的物體組合在一起。最終獲得正確形狀的划痕。由於膨脹的緣故,使用skeleton將形狀變薄到一個像素的寬度

union1 (SelectedRegions, RegionUnion)
dilation_circle (RegionUnion, RegionDilation, 3.5)
skeleton (RegionDilation, Skeleton)
connection (Skeleton, Errors)

最後一步是區分表面上的小點和划痕。這是通過使用大小作為特徵的select_shape實現的。

select_shape (Errors, Scratches, 'area', 'and', 50, 10000)
select_shape (Errors, Dots, 'area', 'and', 1, 50)

源代碼

* This programm shows the extraction of surface scratches via
* local thresholding and morphological post-processing
* 
dev_update_off ()
dev_close_window ()
* 
* Step 1: Acquire image
* 
read_image (Image, 'surface_scratch')
get_image_size (Image, Width, Height)
dev_open_window_fit_image (Image, 0, 0, Width, Width, WindowID)
set_display_font (WindowID, 16, 'mono', 'true', 'false')
dev_set_draw ('margin')
dev_set_line_width (4)
dev_display (Image)
Message := 'This program shows the extraction of'
Message[1] := 'surface scratches via local thresholding'
Message[2] := 'and morphological post-processing'
disp_message (WindowID, Message, 'window', 12, 12, 'black', 'true')
disp_continue_message (WindowID, 'black', 'true')
stop ()
* 
* Step 2: Segment image
* 
* Using a local threshold
mean_image (Image, ImageMean, 7, 7)
dyn_threshold (Image, ImageMean, DarkPixels, 5, 'dark')
* 
* Extract connected components
connection (DarkPixels, ConnectedRegions)
dev_set_colored (12)
dev_display (Image)
dev_display (ConnectedRegions)
Message := 'Connected components after image segmentation'
Message[1] := 'using a local threshold.'
disp_message (WindowID, Message, 'window', 12, 12, 'black', 'true')
disp_continue_message (WindowID, 'black', 'true')
stop ()
* 
* Step 3: Process regions
* 
* Select large regions
select_shape (ConnectedRegions, SelectedRegions, 'area', 'and', 10, 1000)
dev_display (Image)
dev_display (SelectedRegions)
disp_message (WindowID, 'Large Regions', 'window', 12, 12, 'black', 'true')
disp_continue_message (WindowID, 'black', 'true')
stop ()
* 
* Visualize fractioned scratch
open_zoom_window (0, round(Width / 2), 2, 303, 137, 496, 3, WindowHandleZoom)
dev_set_color ('blue')
dev_display (Image)
dev_display (SelectedRegions)
set_display_font (WindowHandleZoom, 16, 'mono', 'true', 'false')
disp_message (WindowHandleZoom, 'Fractioned scratches', 'window', 12, 12, 'black', 'true')
disp_continue_message (WindowHandleZoom, 'black', 'true')
stop ()
* 
* Merge fractioned scratches via morphology
union1 (SelectedRegions, RegionUnion)
dilation_circle (RegionUnion, RegionDilation, 3.5)
dev_display (Image)
dev_display (RegionDilation)
Message := 'Region of the scratches after dilation'
disp_message (WindowHandleZoom, Message, 'window', 12, 12, 'black', 'true')
disp_continue_message (WindowHandleZoom, 'black', 'true')
stop ()
skeleton (RegionDilation, Skeleton)
connection (Skeleton, Errors)
dev_set_colored (12)
dev_display (Image)
dev_display (Errors)
Message := 'Fractioned scratches merged via morphology'
disp_message (WindowHandleZoom, Message, 'window', 12, 12, 'black', 'true')
disp_continue_message (WindowHandleZoom, 'black', 'true')
stop ()
* 
* Distinguish small and large scratches
close_zoom_window (WindowHandleZoom, Width, Height)
select_shape (Errors, Scratches, 'area', 'and', 50, 10000)
select_shape (Errors, Dots, 'area', 'and', 1, 50)
dev_display (Image)
dev_set_color ('red')
dev_display (Scratches)
dev_set_color ('blue')
dev_display (Dots)
Message := 'Extracted surface scratches'
Message[1] := 'Not categorized as scratches'
disp_message (WindowID, Message, 'window', 440, 310, ['red','blue'], 'true')

靈感來源於官方文檔

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

新北清潔公司,居家、辦公、裝潢細清專業服務