〔兩行哥〕OpenCV4Android教程之算法系列(一):直線繪制算法

你知道一條簡單的直線是怎么顯示在計算機屏幕上嗎肤寝?有人說育苟,就是一個個像素點啊筹裕,將一個個像素點連起來就顯示為一條直線了醋闭。但是這些點是如何排布的呢?通過什么樣的算法展示給坐在電腦前面的你呢朝卒?讓我們一起來研究一下证逻。
有能力的同志可以先參考:維基百科-Bresenham's line algorithm,看不懂沒關(guān)系抗斤,兩行哥帶你一步一步分析囚企。

一、計算機是如何顯示直線的

在屏幕上我們看到了一條直線瑞眼,但是它真的是一條直線嗎龙宏?我們用最簡單的方法驗證一下:
打開Windows的畫圖,畫一條直線伤疙,看到了什么银酗?讓我們看看圖1。


圖1 一條直線

貌似是一條直線掩浙,但是我們放大后看看花吟,看到了什么秸歧?如圖2所示厨姚,這并不是一條直線,而是一條條較短的水平線键菱,近似地拼接成一條直線谬墙。


圖2 放大后的直線

為什么會出現(xiàn)上面的現(xiàn)象?讓我們再次放大這條直線经备,如圖3所示:
圖3 直線柵格化

圖中的黑色直線是我們理想中要顯示的目標直線(實際上并沒有被繪制出來)拭抬。但是在柵格圖像中(即位圖,比如我們的計算機顯示屏)侵蒙,像素點是一個個分割的小型區(qū)域(如圖中的方格)造虎,如果想要表示一條直線,只能通過一定的算法纷闺,確定要顯示的目標區(qū)域(如圖中的標記為灰色的方格)算凿,這些目標區(qū)域就組成了一條近似直線的圖像,展示給用戶犁功,這就是我們真正看到的直線氓轰。

顯然,如果我們確定了直線的起始點startPoint和結(jié)束點endPoint浸卦,我們還需要一定的算法來確定這兩點中間還有哪些方格需要顯示(變成灰色)署鸡,哪些方格不需要顯示(依舊為白色),然后再繪制一條“類直線”,接下來靴庆,我們就來建立數(shù)學模型时捌,看看這個算法是怎么實現(xiàn)的。

二撒穷、基本Bresenham直線算法

Bresenham直線算法是用來描繪由兩點所決定的直線的算法匣椰,它會算出一條線段在n維光柵上最接近的點。這個算法只會用到較為快速的整數(shù)加法端礼、減法和位元移位禽笑,常用于繪制電腦畫面中的直線。是計算機圖形學中最先發(fā)展出來的算法蛤奥。

這里就引入我們計算機圖形學中Bresenham算法佳镜,讓我們以上圖3為基礎(chǔ),從建立坐標系開始凡桥,一點一點進行推演蟀伸。


圖4 建立柵格坐標系

如圖4所示,我們建立了左上角為原點的坐標系(通常計算機圖形學中的原點位于左上角)缅刽,x軸正方向向右啊掏,y軸正方向向下。以每個小方格(柵格)的中心(如圖4中柵格中的紅色點)作為標準點衰猛,以每個柵格的邊長為坐標軸單位長度迟蜜,即每個柵格的邊長計為1。
觀察上述建立的坐標系啡省,原點為左上角第一個柵格的中心娜睛。假設(shè)目標直線的起始點坐標為圖中的(x1,y1)卦睹,終點為圖中的(x2畦戒,y2),并且斜率介于0與1之間结序,即與x軸正方向的夾角小于45°(圖中用深黑色直線表示)障斋,那么如何確定出與這條直線擬合度最大的柵格呢(圖中的灰色方塊們)?
觀察圖中的相鄰柵格B1(x3徐鹤,y3)以及柵格B2(x4垃环,y4),兩者滿足x3=x4且y4-y3=1凳干。在Y軸方向上晴裹,柵格B1與目標直線的偏差為d1,柵格B2與目標直線的偏差為d2救赐。比較d1與d2的大小涧团,若d1<=d2只磷,則認為距離目標直線最近的柵格為B1,若d1>d2泌绣,則認為距離目標直線最近的柵格為B2钮追。

在目標直線穿過的所有柵格中,取目標直線在X軸方向的投影阿迈,對于投影區(qū)間[x1元媚,x2]中的每個x的值,在Y軸方向上都有一個或多個柵格與之對應(yīng)苗沧。如果僅有一個柵格與之對應(yīng)刊棕,顯示該柵格(圖中即標灰色),如果有多個柵格與之對應(yīng)待逞,取距離目標直線最近的柵格顯示(標記為灰色)甥角。

還以上文的相鄰柵格B1(x3,y3)與柵格B2(x4识樱,y4)為例嗤无,B1、B2都被目標直線穿過怜庸,并且它們擁有同樣的X軸坐標(兩者滿足x3=x4且y4-y3=1)当犯。此時對于目標直線在X軸方向上的投影區(qū)間[x1,x2]中的x3值而言割疾,有兩個不同y值的柵格B1(x3嚎卫,y3)和B2(x4,y4)與之對應(yīng)杈曲。我們?nèi)【嚯x目標直線較近的柵格來顯示驰凛。上文我們已經(jīng)假設(shè)過胸懈,每個柵格的邊長為1(坐標軸單位長度為1)担扑,在Y軸方向上,如果柵格(x趣钱,y)中心與目標直線的偏差d<=1/2涌献,即顯示該柵格,如果柵格中心與目標直線的偏差d>1/2首有,即顯示Y軸方向的下一個柵格燕垃,即柵格(x,y+1)井联,此柵格的中心與目標直線在Y軸方向上的偏差為d - 1卜壕。而對于目標直線在X軸方向上的投影區(qū)間[x1,x2]中的x1值只有一個y值為y1的柵格(x1烙常,y1)與之對應(yīng)轴捎,直接顯示該柵格即可。
弄清楚原理之后,可以開始我們的推演了侦副。在圖4的坐標系中侦锯,目標直線對應(yīng)的代數(shù)表達式為:

y = ( y2 - y1 ) / ( x2 - x1 ) * ( x - x1 ) + y1

下面我們用偽代碼來繪制這些最擬合目標直線的柵格們。
假定drawPoint(x秦驯,y)為繪制該柵格的方法尺碰,drawLine(startX,startY译隘,endX亲桥,endY)為繪制直線的方法。

//startX:直線的起點坐標x
//startY:直線的起點坐標y
//endX:直線的終點坐標x
//endY:直線的終點坐標y
function drawLine(startX固耘,startY两曼,endX,endY){
    int diffX = endX - startX;
    int diffY = endY - startY;
    float error = 0F;//記錄柵格中心與目標直線在Y軸方向上的偏差值玻驻,如果error > 0表示直線在該柵格中心的下方悼凑,如果error < 0,表示直線在該柵格中心的上方
    int y = startY;
    for( x = startX ; x <= endX ; x++){
        draw( x , y );
        error  = error + diffY / diffX;
        if( error > 0.5F ){
             //如果在Y軸方向上璧瞬,該柵格中心與目標直線的偏差大于0.5F户辫,就使用Y軸方向上的下一個柵格,即y = y + 1
             y = y + 1;
             //因為使用了Y軸方向上的下一個柵格嗤锉,所以需要重新計算柵格中心與目標直線的偏差值
             error = error - 1.0F;
        }
    }
}

三渔欢、Bresenham直線算法擴展

上面我們假設(shè)目標直線是斜率介于0與1之間,并且由左上至右下的直線瘟忱,現(xiàn)對其進行擴展奥额。

1.對于從右下到左上(繪制直線的方向與之前相反):

此時判斷是否startX大于endX,如果是访诱,將目標直線的起始點和終點進行交換即可垫挨;

2.對于負斜率(介于-1與0之間):

此時目標直線的startX > endX,我們把偏差大于0.5F時y = y + 1改為y = y - 1即可触菜;

3.對于斜率絕對值大于1:

我們不直接使用斜率大于1的目標直線九榔,而是使用目標直線關(guān)于y = x對稱的直線,此直線的斜率小于1涡相。此時我們將計算過程中的x與y調(diào)換哲泊,同時將drawPoint()方法的參數(shù)調(diào)換。
新增定義取value絕對值的方法abs(value)催蝗,交換兩個變量值的方法swap( a 切威,b),此時的偽代碼如下:

//startX:直線的起點坐標x
//startY:直線的起點坐標y
//endX:直線的終點坐標x
//endY:直線的終點坐標y
function drawLine(startX丙号,startY先朦,endX且预,endY){
    boolean needExchange = abs(endY - startY) > abs(endX - startX)
    if( needExchange ){//交換x與y
        swap( x1 , y1 );
        swap( x2 , y2 );
    }
    if( x1 > x2 ){//交換開始點與結(jié)束點
        swap( x1 , x2 );
        swap( y1 , y2);
    }
    int diffX = endX - startX;
    int diffY = abs( endY - startY );
    float error = 0F;
    int y = startY;
    for( x = startX ; x <= endX ; x++){
        if( needExchange ){
            draw( y , x );
        }else{
            draw( x , y );
        }
        error  = error + diffY / diffX;
        if( error > 0.5F ){
            if( y1 <= y2 ){
                y = y + 1; 
            }else{
                y = y - 1;
            }
            error = error - 1.0F;
        }
    }
}

至此,已經(jīng)實現(xiàn)了完整的Bresenham直線算法烙无。

四锋谐、Bresenham直線算法整數(shù)化

電腦處理浮點運算的速度比較慢,而error的計算是浮點運算截酷。此外涮拗,error的值經(jīng)過多次浮點數(shù)加法之后,可能有累積誤差(計算機中浮點運算是不精確的)迂苛。使用整數(shù)運算可令算法更快三热、更準確。只要將以上所有的分數(shù)數(shù)值乘以diffX三幻,我們就可以用整數(shù)來表示它們就漾。唯一的問題是程序中的常數(shù)0.5F—我們可以通過改變error的初始方法,以及將error的計算由遞增改為遞減來解決念搬。整數(shù)化后的邏輯如下:

//startX:直線的起點坐標x
//startY:直線的起點坐標y
//endX:直線的終點坐標x
//endY:直線的終點坐標y
function drawLine(startX抑堡,startY,endX朗徊,endY){
    boolean needExchange = abs(endY - startY) > abs(endX - startX)
    if( needExchange ){//交換x與y
        swap( x1 , y1 );
        swap( x2 , y2 );
    }
    if( x1 > x2 ){//交換開始點與結(jié)束點
        swap( x1 , x2 );
        swap( y1 , y2);
    }
    int diffX = endX - startX;
    int diffY = abs( endY - startY );
    float error = diffX / 2;
    int y = startY;
    for( x = startX ; x <= endX ; x++){
        if( needExchange ){
            draw( y , x );
        }else{
            draw( x , y );
        }
        error  = error - diffY;
        if( error < 0 ){
            if( y1 <= y2 ){
                y = y + 1; 
            }else{
                y = y - 1;
            }
            error = error + diffX;
        }
    }
}

有個疑問首妖,為什么error的初始值取為diffX / 2呢?其實我們先將error由遞增改為了遞減爷恳,error取值0.5F有缆,如果error遞減后的值小于0,則對y進行相應(yīng)操作温亲。而我們的目的是將0.5F化為整數(shù)棚壁,因此將error乘以diffX,即error = diffX / 2栈虚。

至此袖外,我們的Bresenham直線算法已經(jīng)講解完畢,這與我們OpenCV中的LineType參數(shù)有很大的關(guān)聯(lián)节芥。

五在刺、LineType參數(shù)詳解

OpenCV中LineType有三種類型:

LINE_AA = 16
LINE_8 = 8
LINE_4 = 4

先講LINE_4和LINE_8逆害。LINE_4和LINE_8表示繪制的直線為4連通直線或8連通直線头镊。要理解4連通直線或8連通直線,我們先引入鄰域概念魄幕,若存在柵格點P(x相艇,y):

1.四領(lǐng)域
圖5 四鄰域

如圖5,像素P(x纯陨,y)的四鄰域是:(x+1坛芽,y)留储、(x-1,y)咙轩、(x获讳,y+1)、(x活喊,y-1)丐膝,即圖中的4個黃色柵格。

2.對角鄰域
圖6 對角鄰域

如圖6钾菊,像素P(x帅矗,y)的對角鄰域是:(x+1,y+1)煞烫;(x+1浑此,y-1);(x-1滞详,y+1)凛俱;(x-1,y-1)料饥,即圖中的4個黃色柵格最冰。

3.八鄰域
圖7 八鄰域

如圖7,像素P(x稀火,y)的八鄰域是其四鄰域和對角鄰域的和暖哨,即圖中的8個黃色柵格。
那么LINE_4和LINE_8代表的直線繪制上有什么不同呢凰狞?觀察圖8和圖9:


圖8 四連通直線LINE_4

圖9 八連通直線LINE_8

圖8中篇裁,點A的下一個連接點B位于點A的四鄰域中,因此直線是四連通直線赡若。圖9中达布,點A的下一個連接點B位于點A的八鄰域中,因此直線是八連通直線逾冬。這里留意一下黍聂,因為八鄰域已經(jīng)包含了四鄰域,所以通常我們認為:點A的下一個連接點B位于點A的對角鄰域中身腻,此直線為八連通直線产还,而在四鄰域中(在四鄰域中一定在八鄰域中)的話,我們依舊認為直線為四連通直線嘀趟。
那么我們上文分析的直線繪制方法繪制出來的是什么直線呢脐区?很明顯是八連通直線。如何將八連通直線算法改造為四連通直線算法呢她按?留給讀者自己思考(提示:y = y + 1時牛隅,x是否需要 + 1炕柔?)
至于LINE_AA,則是抗鋸齒直線媒佣,采用了高斯濾波匕累,本文就不詳細介紹了,如果考慮性能默伍,我們一般使用LINE_4和LINE8哩罪。

經(jīng)過上文的分析,相信大家會對LineType參數(shù)的理解更深巡验。我們下次再會际插。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市显设,隨后出現(xiàn)的幾起案子框弛,更是在濱河造成了極大的恐慌,老刑警劉巖捕捂,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瑟枫,死亡現(xiàn)場離奇詭異,居然都是意外死亡指攒,警方通過查閱死者的電腦和手機慷妙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來允悦,“玉大人膝擂,你說我怎么就攤上這事∠冻冢” “怎么了架馋?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長全闷。 經(jīng)常有香客問我叉寂,道長,這世上最難降的妖魔是什么总珠? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任屏鳍,我火速辦了婚禮,結(jié)果婚禮上局服,老公的妹妹穿的比我還像新娘钓瞭。我一直安慰自己,他們只是感情好腌逢,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布降淮。 她就那樣靜靜地躺著,像睡著了一般搏讶。 火紅的嫁衣襯著肌膚如雪佳鳖。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天媒惕,我揣著相機與錄音系吩,去河邊找鬼。 笑死妒蔚,一個胖子當著我的面吹牛穿挨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播肴盏,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼科盛,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了菜皂?” 一聲冷哼從身側(cè)響起贞绵,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎恍飘,沒想到半個月后榨崩,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡章母,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年母蛛,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乳怎。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡彩郊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蚪缀,到底是詐尸還是另有隱情焦辅,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布椿胯,位于F島的核電站筷登,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏哩盲。R本人自食惡果不足惜前方,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望廉油。 院中可真熱鬧惠险,春花似錦、人聲如沸抒线。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嘶炭。三九已至抱慌,卻和暖如春逊桦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背抑进。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工强经, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人寺渗。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓匿情,卻偏偏與公主長得像,于是被迫代替她去往敵國和親信殊。 傳聞我的和親對象是個殘疾皇子炬称,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容