# مهندسی نرم افزار > مباحث مرتبط با مهندسی نرم‌افزار > پردازش تصویر (Image Processing) >  الگوريتم canny

## asefy2008

سلام 
كسي از دوستان كد الگوريتم canny رو داره بزاره ممنون ميشم؟(حتي المكان با سي شارپ)

----------


## مصطفی ساتکی

سلام کدشو به دلفی واست گذاشتم که زمانیکه canny رو از Delphi به C#‎‎ می بره عملکردشو هم یاد بگیری


unit GBlur2;

interface

uses
  Windows, Graphics;

type
  PRGBTriple = ^TRGBTriple;
  TRGBTriple = packed record
    b: byte; {easier to type than rgbtBlue}
    g: byte;
    r: byte;
  end;
  PRow = ^TRow;
  TRow = array[0..1000000] of TRGBTriple;
  PPRows = ^TPRows;
  TPRows = array[0..1000000] of PRow;

const
  MaxKernelSize = 100;

type
  TKernelSize = 1..MaxKernelSize;
  TKernel = record
    Size: TKernelSize;
    Weights: array[-MaxKernelSize..MaxKernelSize] of single;
  end;
  {the idea is that when using a TKernel you ignore the Weights except
  for Weights in the range -Size..Size.}

procedure GBlur(theBitmap: TBitmap; radius: double);

implementation

uses
  SysUtils;

procedure MakeGaussianKernel(var K: TKernel; radius: double; MaxData, DataGranularity: double);
{makes K into a gaussian kernel with standard deviation = radius. For the current application
you set MaxData = 255 and DataGranularity = 1. Now the procedure sets the value of K.Size so
that when we use K we will ignore the Weights that are so small they can't possibly matter. (Small
Size is good because the execution time is going to be propertional to K.Size.)}
var
  j: integer;
  temp, delta: double;
  KernelSize: TKernelSize;
begin
  for j := Low(K.Weights) to High(K.Weights) do
  begin
    temp := j / radius;
    K.Weights[j] := exp(-temp * temp / 2);
  end;
  {now divide by constant so sum(Weights) = 1:}
  temp := 0;
  for j := Low(K.Weights) to High(K.Weights) do
    temp := temp + K.Weights[j];
  for j := Low(K.Weights) to High(K.Weights) do
    K.Weights[j] := K.Weights[j] / temp;
  {now discard (or rather mark as ignorable by setting Size) the entries that are too small to matter.
  This is important, otherwise a blur with a small radius will take as long as with a large radius...}
  KernelSize := MaxKernelSize;
  delta := DataGranularity / (2 * MaxData);
  temp := 0;
  while (temp < delta) and (KernelSize > 1) do
  begin
    temp := temp + 2 * K.Weights[KernelSize];
    dec(KernelSize);
  end;
  K.Size := KernelSize;
  {now just to be correct go back and jiggle again so the sum of the entries we'll be using is exactly 1}
  temp := 0;
  for j := -K.Size to K.Size do
    temp := temp + K.Weights[j];
  for j := -K.Size to K.Size do
    K.Weights[j] := K.Weights[j] / temp;
end;

function TrimInt(Lower, Upper, theInteger: integer): integer;
begin
  if (theInteger <= Upper) and (theInteger >= Lower) then
    result := theInteger
  else if theInteger > Upper then
    result := Upper
  else
    result := Lower;
end;

function TrimReal(Lower, Upper: integer; x: double): integer;
begin
  if (x < upper) and (x >= lower) then
    result := trunc(x)
  else if x > Upper then
    result := Upper
  else
    result := Lower;
end;

procedure BlurRow(var theRow: array of TRGBTriple; K: TKernel; P: PRow);
var
  j, n, LocalRow: integer;
  tr, tg, tb: double; {tempRed, etc}
  w: double;
begin
  for j := 0 to High(theRow) do
  begin
    tb := 0;
    tg := 0;
    tr := 0;
    for n := -K.Size to K.Size do
    begin
      w := K.Weights[n];
      {the TrimInt keeps us from running off the edge of the row...}
      with theRow[TrimInt(0, High(theRow), j - n)] do
      begin
        tb := tb + w * b;
        tg := tg + w * g;
        tr := tr + w * r;
      end;
    end;
    with P[j] do
    begin
      b := TrimReal(0, 255, tb);
      g := TrimReal(0, 255, tg);
      r := TrimReal(0, 255, tr);
    end;
  end;
  Move(P[0], theRow[0], (High(theRow) + 1) * Sizeof(TRGBTriple));
end;

procedure GBlur(theBitmap: TBitmap; radius: double);
var
  Row, Col: integer;
  theRows: PPRows;
  K: TKernel;
  ACol: PRow;
  P: PRow;
begin
  if (theBitmap.HandleType <> bmDIB) or (theBitmap.PixelFormat <> pf24Bit) then
    raise exception.Create('GBlur only works for 24-bit bitmaps');
  MakeGaussianKernel(K, radius, 255, 1);
  GetMem(theRows, theBitmap.Height * SizeOf(PRow));
  GetMem(ACol, theBitmap.Height * SizeOf(TRGBTriple));
  {record the location of the bitmap data:}
  for Row := 0 to theBitmap.Height - 1 do
    theRows[Row] := theBitmap.Scanline[Row];
  {blur each row:}
  P := AllocMem(theBitmap.Width * SizeOf(TRGBTriple));
  for Row := 0 to theBitmap.Height - 1 do
    BlurRow(Slice(theRows[Row]^, theBitmap.Width), K, P);
  {now blur each column}
  ReAllocMem(P, theBitmap.Height * SizeOf(TRGBTriple));
  for Col := 0 to theBitmap.Width - 1 do
  begin
    {first read the column into a TRow:}
    for Row := 0 to theBitmap.Height - 1 do
      ACol[Row] := theRows[Row][Col];
    BlurRow(Slice(ACol^, theBitmap.Height), K, P);
    {now put that row, um, column back into the data:}
    for Row := 0 to theBitmap.Height - 1 do
      theRows[Row][Col] := ACol[Row];
  end;
  FreeMem(theRows);
  FreeMem(ACol);
  ReAllocMem(P, 0);
end;

end.



Example:


procedure TForm1.Button1Click(Sender: TObject);
var
  b: TBitmap;
begin
  if not openDialog1.Execute then
    exit;
  b := TBitmap.Create;
  b.LoadFromFile(OpenDialog1.Filename);
  b.PixelFormat := pf24Bit;
  Canvas.Draw(0, 0, b);
  GBlur(b, StrToFloat(Edit1.text));
  Canvas.Draw(b.Width, 0, b);
  b.Free;
end;



راستی اگه می خوای رو موضوع خاصی کار کنی بایستی سیگما که مربوط به kernel گوس و threshold1,threshold2 رو مشخص کنی البته یه روشی واسه تعیین threshold نرمال هم وجود داره اگه خواستی تاپیک بزن.اگه کار عمومیت داره کانی به دردت نمی خوره بایستی hybrid کار کنی یعنی با NN یاFuzzy ترکیبش کنی

----------


## مصطفی ساتکی

اون بالا فقط guassian blur چون برای انجام لبه canny ابتدا با رو تصویر اونو اعمال کنی
function canny( b: TBitmap ): TBitmap;
var
  kv, kh            : array[0..2, 0..2] of real;
  i, j, k, l        : integer;
  p                 : array of PArrRGB;
  mag, gx, gy       : array of array of real;
  bg, br            : TBitmap;
  dir               : real;
  tmp, dx, dy       : integer;
  pr                : ParrRGB;
  ap                : array of TPoint;
  edge              : array of array of boolean;
  pt                : array of tpoint;
  thrlow, thrhigh   : real;

  function trace( x, y: integer ): boolean;
  var
    i, j            : integer;

    function ok( x, y: integer ): boolean;
    begin
      result := ( x >= 0 ) and ( y >= 0 ) and ( x < b.Width ) and ( y < b.Height );
    end;
  begin
    result := false;
    if not edge[y][x] then exit;

    edge[y][x] := false;
    result := mag[y][x] >= thrhigh;
    for j := -1 to 1 do
      for i := -1 to 1 do
        if ok( x + i, y + j ) then
          result := trace( x + i, y + j ) or result;

    //jika hasil rekursi menghasilkan true
    //(terhubung dengan piksel yang nilainya lebih besar dari hi-threshold)
    if result then begin
      pr := br.ScanLine[y];
      pr[x] := rgb_hitam;//citra yang baru berwarna latar putih
    end;
  end;
begin
  bg := gaussian( b, 1 );
  result := citra_create( b.Width, b.Height );//buat citra(bitmap) baru 

  setlength( p, b.Height );
  for j := 0 to high( p ) do
    p[j] := bg.ScanLine[j];

  setlength( gx, b.Height );
  for j := 0 to high( gx ) do
    setlength( gx[j], b.Width );

  setlength( gy, b.Height );
  for j := 0 to high( gx ) do
    setlength( gy[j], b.Width );

  setlength( mag, b.Height );
  for j := 0 to high( mag ) do
    setlength( mag[j], b.Width );

  setlength( edge, b.Height );
  for j := 0 to high( edge ) do
    setlength( edge[j], b.Width );

  //hitung gradien gx dan gy menggunakan operator sobel
  for j := 1 to b.Height - 2 do begin
    for i := 1 to b.Width - 2 do begin
      gy[j][i] := ( p[j + 1][i - 1].r - p[j - 1][i - 1].r ) + 2 * ( p[j + 1][i].r - p[j - 1][i].r ) + ( p[j + 1][i + 1].r - p[j - 1][i + 1].r );
      gx[j][i] := ( p[j - 1][i + 1].r - p[j - 1][i - 1].r )
        + 2 * ( p[j][i + 1].r - p[j][i - 1].r )
        + ( p[j + 1][i + 1].r - p[j + 1][i - 1].r );
      mag[j][i] := sqrt( gx[j][i] * gx[j][i] + gy[j][i] * gy[j][i] );
    end;
  end;

  //non-maximal suppression
  for j := 1 to b.Height - 2 do begin
    for i := 1 to b.Width - 2 do begin
      dir := arctan2( gy[j][i], gx[j][i] );
      dx := round( cos( dir ) );
      dy := round( sin( dir ) );

      edge[j][i] := ( mag[j][i] >= mag[j + dy][i + dx] ) and ( mag[j][i] >= mag[j - dy][i - dx] );
      if edge[j][i] then begin
        edge[j + dy][i + dx] := false;
        edge[j - dy][i - dx] := false;
      end;
    end;
  end;

  //apply hysteresis
  thrlow := 10;
  thrhigh := 90;
  br := result;
  for j := 1 to b.Height - 2 do begin
    for i := 1 to b.Width - 2 do begin
      if edge[j][i] then trace( i, j );//trace melakukan edge-linking
    end;
  end;
end;

----------


## مصطفی ساتکی

کانی اول اینو روی سیگنال یه بعدی پیاده کرد و بعد آوردش رو 2 بعد(تصویر).به این خاطر از فیلتر پایین گذر گوس استفاده کرد تا نویز موجود در تصویرو از بین ببره.ابتدا تصویرتو gray scale کن روش gaussian blur رو اعمال کن بعد اون بایستی از متشق مرتبه اول یا فیلتر sobel استفاده کنی 
gx=[-1,-2,-1
0,0,0
1,2,1
gy=[-1,0,1
-2,0,2
-1,0,1
gx, gy رو کانولوشن می کنی رو هر نقطه پس gx ,gy بدست می آید حالا بایستی شدت رو محاسبه کنی      G=sqrt(sqr(gx)+sqr(gy))
g همون مقدار شدت محاسبه شده است.
برای بدست آوردن زوایه مابین gx,gy از                              teta=arctan(gy/gx) می کنه این زوایه شدت در هر نقطه بهت می ده .سپس با یستی این زوایایه بدست آمده رو round کنی خلاصه کلام چون kernel 3*3 می خایم تعیین کنیم زوایه در راستای کدوم چهر جهت اصلیه.
0و90و180 یا 270
پس از پیدا کردن بایست عمل non-maximal suppression رو انجام بدی همون طور که از اسمش پیدا یعنی هر چی غیر از جهت شدت باشه حذف می کنه بعد این بایستی hysteresis رو اعمال کنی چون تا الان اگه تصویر رو ببینی لبه خیلی ضخیمن پس بایستی لبه ها رو نازک کنی. مثل یه trace ساده نو 8 خونه مجاور یه نقطه. اون 2 تا Threshold که گفتم اگه یادت نرفته اینجا به دردت می خوره هر نقطه که پایین تر از threshold1 باشه 0 (BackColor) و هر نقطه بالاتر از threshold2 باشه 255 (EdgeColor) می شه هر نقطه هم که مابین این دو threshold باشه اگر با یک نقطه ای شدتش بزرگتر مساوی threshold2 به 255 تبدیل می شه و گرنه صفر می شه
اینو می گن الگوریتم لبه کانی به همین راحتی

----------


## asefy2008

با یک دنیا تشکر از راهنمایی هاتون ولی یه سوال داشتم :
gaussian blur رو چطور میشه اعمل کرد (توضیح  :خجالت: )

----------


## مصطفی ساتکی

gaussian blur ابتدا یک kernel تولید میکنه و سپس این kernel رو بروی کل تصویر اعمال می کنه کد اول مربوط به اونه .این kernel در حالت 3 بعدی به شکل کلاه مکزیکیه.در واقع به این خاطر از این فیلتر پایین گذر استفاده می کنه که نویز موجود در تصویر رو کاهش بده. اگر شما بخاید Realtime کار کنید حتماً نبایست از Kernel گوس استفاده کنید یه فیلتر معمولی هم میشه استفاده کرد .
خبر جدید : من فیلتر لبه ای رو با شبکه SOFM تولید کردم که جواب خیلی خوبی رو نسبت به canny می ده و Realtime عمل می کنه. Canny به چندین پارامتر وابسته است و این مشکل threshold1,threshold2 و همین Sigma که میزان تاثیر فیلتر پایین گذر رو مشخص می کنه.Canny در همهئ شرایط نمی تونه جواب مطلوب رو به شما بده.

----------


## asefy2008

نمی دونم درست فهمیدم یا نه. میگم  اگر اشتباه بود لطف کن و اصلاح کن :
gaussian blur یک آرایه 3*3 به ما میده که ما اون رو کل تصویر اعمال کنیم . 
این آرایه از طریق مشتق گیری از آرایه زیر بدست میاد:
1  2   1
2  4  2
1  2   1

اگر اشتباه گفتم لطفا کامل توضیح بده چون سورس gaussian blur رو به زبان سی شارپ دانلود کردم بازم متوجه نشدم (البته سورسش اجرا نمی شد ولی شبیه کد شما بود.)

----------


## asefy2008

راستی یادم رفت بگم دارم روی تشخیص هویت از طریق عنبیه کار می کنم.

----------


## مصطفی ساتکی

نه الزاماً 3*3 می تونه هر ابعادی داشته باشه و این مقدار بستگی به Sigma داره که میزان انتشار رو مشخص مینکه.بعلت محاسبات زمانگیر Guassian blur مقدار اون را از یک حد به بالاتر نادیده می گیرند چون این اعداد حذف شده اعشاری بوده و محاسباتشون زمانگیر و البته تاثیر زیادی هر مبر خروجی نداره .این کدی رو که در بالا براتون گذاشتم برای اینکه سرعت محاسبات بالاتر باشه مثل FFT عمل میکنه.یعنی بجای اینکه درجا به صورت 2 بعدی عمل کنه 2 بار به صورت یک بعدی عمل میکنه.یکبار Kernel مد نظر مون که یک آرایه متقارن بروی سطر ها اعمال میکنه و بار دیگه بروی ستون ها.شما برای اینکه راحتر متوجه بشید Function مربوط Gblur رو تیکه تیکه اجرا کند تا خروجی هر قسمت رو بروی تصویر مقصدتون ببینید. مقادیر آرایه Kernel از قبل تعیین شده نیست و با مشخص نمودن sigma محاسبه میشه

----------


## alimooghashang

> gaussian blur ابتدا یک kernel تولید میکنه و سپس این kernel رو بروی کل تصویر اعمال می کنه کد اول مربوط به اونه .این kernel در حالت 3 بعدی به شکل کلاه مکزیکیه.در واقع به این خاطر از این فیلتر پایین گذر استفاده می کنه که نویز موجود در تصویر رو کاهش بده. اگر شما بخاید Realtime کار کنید حتماً نبایست از Kernel گوس استفاده کنید یه فیلتر معمولی هم میشه استفاده کرد .
> خبر جدید : من فیلتر لبه ای رو با شبکه SOFM تولید کردم که جواب خیلی خوبی رو نسبت به canny می ده و Realtime عمل می کنه. Canny به چندین پارامتر وابسته است و این مشکل threshold1,threshold2 و همین Sigma که میزان تاثیر فیلتر پایین گذر رو مشخص می کنه.Canny در همهئ شرایط نمی تونه جواب مطلوب رو به شما بده.


این روشی که شما معرفی کردید از canny بهتره؟
من اطلاعاتی رو در مورد canny میخوام، چون با OpenCV میخوام کار کنم و توسط الگوریتم canny لبه گیری میکنه ولی من نمیدونم پارامتر های threshold رو چی بدم که جوابی که میخوام رو بهم بده!
ممنونم میشم در این زمینه کمکم کنید!

----------


## مصطفی ساتکی

تعیین threshold بستگی به میزان روشنایی و نویز بستگی داره که با توجه به فیلدی که در اون کار می کنید این پارامتر set میشه .اگر مایلید که این پارامتر ها به صورت هوشمندانه تعیین شوند یعنی با توجه به نور محیط  میزان نویز با یستی از cnn استفاده کنید که در opencv همچین چیزی وجود نداره اگر هم کارتون تجاریه با من تماس بگیرید.
موفق باشید

----------


## hanieh sarbazi

سلام بچه ها من فیلترهای پردازش تصویر مثل سابل و گاوسین و لاپلاس رو نیاز دارم میشه خواهش کنم کدهاش رو بذارید

----------


## مصطفی ساتکی

البته سوالي كه شما پرسيد بايستي براش يه تاپيك ايجاد مي كرديد.

اين 3 مورد رو كه شما نياز داريد تحت عنوان فيلتر در image processing و singnal processing استفاده  ميشن.در واقع با اعمال فيلتر ها در تصوير ماهيتشون رو به گونه اي تغيير ميدم كه بتونيم از تصوير بدست آمده feature هاي مورد نياز رو extract كنيم.
در واقع براي اعمال فيلتر شما بايستي يك ماتريس رو در كل تصوير ضرب كنيد كه به اين عمل convolution گفته ميشه.
اين ماتريسي كه در تصوير اصلي ضرب ميشه اصطلاحا kernel نام داره كه به ابعاد اون support size گفته ميشه.كه در اغلب كاربردها اين ابعاد برابره مثل 3*3 يا 5*5 هر چقدر اين اندازه بزرگتر باشه شما neighbor هاي بيشتري رو در بدست آوردن مقدار intensity پيكسل جاري شركت ميديد.
مثلاً فيلتر اولي كه گفتيد sobel در 2 جهت بر روي تصوير اعمال ميشه x,y .
فيلتر در واقع همون مشتق مرتبه اول در راستاي x و y .(گراديان) مشتق در واقع ميزان اختلاف رو به ما نشون ميده.
لاپلاسين يعني حاصل جمع  مشتق مرتبه دوم در راستاي x و y.
شما در لبه اشيا ميزان intensity اختلاف زيادي داره كه در واقع با اعمال گراديان و لاپلاسين اين لبه ها نمايان ميشه.
البته نا گفته نمونه كه شما مي تونيد از orientation و magnitude گراديان هم استفاده كنيد. در ضمن گراديان و لاپلاسين بالاگذرند
يه فيلتر ديگه كه فرموديد gaussian هستش كه يه فيلتر پاپپن گذر .در واقع يكجور هموار كننده كه عمل smooth كردن رو انجام ميده. در مواردي كه شما در تصويرتون نويز داريد با اعمال gaussian اين نويز ها در تصوير از بين ميرن. البته اين كاربردش در تصويره در signal هم كاربردهاي متنوعي داره.
مثلا يه مثال signal براتون بزنم شما زمانيكه در يك جريان صوتي صداي foreground قطع شده صداي background مثل
صداي باد به گوش ميرسه شما كافي اين سيگنال رو gaussian بگيريد و از خودش كسر كنيد صداي باد از بين ميره.

براي اعمال فيلتر ها به صورت استاتيك و در support size مشخص مثلاً 3*3 مي تونيد به صورت زير عمل كنيد.
sobelHorizontal[3][3] = {1, 2, 1, 0, 0, 0, -1, -2, -1 };
sobelVertical[3][3] = {1, 0, -1, 2, 0, -2, 1, 0, -1 }
gaussian[3][3] = {1, 2, 1, 2, 4, 2, 1, 2, 1}
Laplacian [3][3] = {-1, -1, -1, -1, 8, -1, -1, -1, -1 };

كه توضيحاتي كه در بالا ارائه شد در حالت spatial بود كه همين فيلتر ها رو تو حوزه
فركانس هم ميشه اعمال كرد

----------

