整理智能車中使用到的攝像頭圖像處理算法
19年的智能車競賽給我留下了很深遠的影響,不管是算法思路還是解決問題的方法對我來說都受益匪淺。今天整理資料時又翻出這些代碼,回憶起做車時不分日夜地在賽道上調試,回憶起盡管小車已經完整跑完賽道卻依舊想着如何去優化得更快更好的自己。
現在把自己的整理分享給有用的人,希望能少走寫彎路吧。
注意單片機性能有限,在執行一些算法時效率會很低。
S_CV.h
#ifndef _S_CV_H
#define _S_CV_H
#include "include.h"
//圖像大小定義
/*
#define S_img_H 120
#define S_img_W 188*/
#define S_img_H 60
#define S_img_W 94
#define S_S_Custom 0xAA
void S_BinaryImage(uint8_t in_IMG[S_img_H][S_img_W], uint8_t out_IMG[S_img_H][S_img_W], uint8_t ThresholdV);
void S_SE_Operation(uint8_t in_IMG[S_img_H][S_img_W], uint8_t out_IMG[S_img_H][S_img_W]);
void S_SE_OperationBIG(uint8_t in_IMG[120][188], uint8_t out_IMG[120][188]);
/**********************************形态學處理**************************************/
//腐蝕
void S_Erode(uint8_t in_IMG[S_img_H][S_img_W], uint8_t out_IMG[S_img_H][S_img_W]);
//膨脹
void S_Dilate(uint8_t in_IMG[S_img_H][S_img_W], uint8_t out_IMG[S_img_H][S_img_W]);
//開操作
void S_Open(uint8_t in_IMG[S_img_H][S_img_W], uint8_t out_IMG[S_img_H][S_img_W]);
//關操作
void S_Close(uint8_t in_IMG[S_img_H][S_img_W], uint8_t out_IMG[S_img_H][S_img_W]);
/**********************************圖像顯示**************************************/
#define S_MINI 0
#define S_BIG 1
//顯示小圖像到TFT子產品
void S_Draw_TFT_MINI(uint8_t in_IMG[S_img_H][S_img_W]);
//60*94圖像顯示成188*120
void S_DisplayMINI_Big(uint8_t in_IMG[PixMini_H][PixMini_W]);
//顯示大圖像到TFT子產品
void S_Draw_TFT_BIG(uint8_t in_IMG[IMAGEH][IMAGEW]);
//顯示灰階圖像60*90 可選大小
void S_Gray_DisplayMINI(uint8_t in_IMG[PixMini_H][PixMini_W], u8 size);
//顯示灰階圖像120*188
void S_Gray_Display(uint8_t in_IMG[IMAGEH][IMAGEW]);
//三線圖顯示
void S_DisplayRunWay(RunWay_* in_Array);
//三線圖顯示 大圖
void S_DRW_Big(RunWay_* in_Array);
/**********************************其他操作**************************************/
// 圖像 抽取壓縮188X120 to 94X60
void S_Image_zip(uint8_t in_IMG[IMAGEH][IMAGEW], uint8_t out_IMG[S_img_H][S_img_W]);
// 圖像 填充放大 94X60 to 188X120
void S_Image_larger(uint8_t in_IMG[S_img_H][S_img_W], uint8_t out_IMG[IMAGEH][IMAGEW]);
//三線圖檢測
void DetectionLine(uint8_t in_IMG[PixMini_H][PixMini_W], RunWay_ * out_Array, u8 LMP, u8 Mod);
//三線圖定制
void DetectionLine_Custom(uint8_t in_IMG[PixMini_H][PixMini_W], RunWay_* out_Array, u8 LMP);
/*********************************求門檻值***************************************/
//大津法求門檻值
uint8_t S_GetOSTU(uint8_t tmImage[S_img_H][S_img_W]); //修改
uint8 S_Other_OSTU(int width, int height, uint8* Image);
//均值比例求門檻值
u8 S_Get_01_Value(uint8_t tmImage[S_img_H][S_img_W]);
/*********************************濾波***************************************/
//權重濾波
void S_WeightedFiltering(uint8_t in_IMG[S_img_H][S_img_W], uint8_t out_IMG[S_img_H][S_img_W]);
//注入填充算法(Flood Fill Algorithm)
void S_FloodSeedFill(uint8_t in_IMG[S_img_H][S_img_W], u8 x, u8 y, u8 old_color, u8 new_color);
//修改注入填充算法 使用for循環 非遞歸 不溢出
void SX_FloodSeedFill(uint8_t in_IMG[40][63], u8 x, u8 y, u8 old_color, u8 new_color);
//在一幅圖像中判定 橫穿圖像的白塊 未實作
u8 S_Linear(u8 height, u8 width, uint8* in_IMG);
/**********************************圖像邊緣檢測算法**************************************/
//Robert算子
void S_Robert(uint8_t in_IMG[PixMini_H][PixMini_W], uint8_t out_IMG[PixMini_H][PixMini_W]);
//S_Sobel算子
void S_Sobel(uint8_t in_IMG[PixMini_H][PixMini_W], uint8_t out_IMG[PixMini_H][PixMini_W], u8 Threshold);
//sobel 定制
void S_Sobel_Custom(uint8_t in_IMG[PixMini_H][PixMini_W], uint8_t out_IMG[PixMini_H][PixMini_W], u8 Threshold);
#endif
S_CV.c
//#include <cv.h>
//#include <highgui.h>
#include "S_CV.h"
u8 temp_IMG[S_img_H][S_img_W]; //過渡圖像
/***************************************************************
*
* 函數名稱:void S_BinaryImage(uint8_t tmImage[IMAGEH][IMAGEW], uint8_t ThresholdV)
* 功能說明:圖像資料二值化
* 參數說明:tmImage 二值化資料存儲、 ThresholdV 門檻值
* 函數傳回:void
* 修改時間:2019年4月6日
* 備 注:
***************************************************************/
void S_BinaryImage(uint8_t in_IMG[S_img_H][S_img_W], uint8_t out_IMG[S_img_H][S_img_W], uint8_t ThresholdV)
{
int i = 0, j = 0;
for (i = 0; i < S_img_H; i++)
for (j = 0; j < S_img_W; j++)
{
if (in_IMG[i][j] > ThresholdV)
out_IMG[i][j] = 1;
else
out_IMG[i][j] = 0;
}
}
//SE 操作
// 1
// 1 X 1
// 1
void S_SE_Operation(uint8_t in_IMG[S_img_H][S_img_W], uint8_t out_IMG[S_img_H][S_img_W])
{
u8 i, j;
//限制長寬避免溢出 使用圖像小一圈
u8 img_H = S_img_H - 1;
u8 img_W = S_img_W - 1;
u8 S_UP, S_DN, S_LL, S_RR;
//輸出資料初始化
for (i = 0; i < S_img_H; i++)
for (j = 0; j < S_img_W; j++)
out_IMG[i][j] = 0;
for (i = 1; i < img_H; i++)
{
S_UP = i - 1;
S_DN = i + 1;
for (j = 1; j < img_W; j++)
{
S_LL = j - 1;
S_RR = j + 1;
if (in_IMG[i][j])
{
out_IMG[i][j]++;
out_IMG[S_UP][j]++; //UP
out_IMG[S_DN][j]++; //DN
out_IMG[i][S_LL]++; //LL
out_IMG[i][S_RR]++; //RR
}
}
}
}
void S_SE_OperationBIG(uint8_t in_IMG[120][188], uint8_t out_IMG[120][188])
{
u8 i, j;
//限制長寬避免溢出 使用圖像小一圈
u8 img_H = 120 - 1;
u8 img_W = 188 - 1;
u8 S_UP, S_DN, S_LL, S_RR;
//輸出資料初始化
for (i = 0; i < 120; i++)
for (j = 0; j < 188; j++)
out_IMG[i][j] = 0;
for (i = 1; i < img_H; i++)
{
S_UP = i - 1;
S_DN = i + 1;
for (j = 1; j < img_W; j++)
{
S_LL = j - 1;
S_RR = j + 1;
if (in_IMG[i][j])
{
out_IMG[i][j]++;
out_IMG[S_UP][j]++; //UP
out_IMG[S_DN][j]++; //DN
out_IMG[i][S_LL]++; //LL
out_IMG[i][S_RR]++; //RR
}
}
}
}
//腐蝕
void S_Erode(uint8_t in_IMG[S_img_H][S_img_W], uint8_t out_IMG[S_img_H][S_img_W])
{
S_SE_Operation(in_IMG, out_IMG);
S_BinaryImage(out_IMG, out_IMG, 3); //交集
}
//膨脹
void S_Dilate(uint8_t in_IMG[S_img_H][S_img_W], uint8_t out_IMG[S_img_H][S_img_W])
{
S_SE_Operation(in_IMG, out_IMG);
S_BinaryImage(out_IMG, out_IMG, 0); //并集
}
//開操作
void S_Open(uint8_t in_IMG[S_img_H][S_img_W], uint8_t out_IMG[S_img_H][S_img_W])
{
S_Erode(in_IMG, temp_IMG); //腐蝕
S_Dilate(temp_IMG, out_IMG); //膨脹
}
//關操作
void S_Close(uint8_t in_IMG[S_img_H][S_img_W], uint8_t out_IMG[S_img_H][S_img_W])
{
S_Dilate(in_IMG, temp_IMG); //膨脹
S_Erode(temp_IMG, out_IMG); //腐蝕
}
//顯示大圖像到TFT子產品
void S_Draw_TFT_BIG(uint8_t in_IMG[IMAGEH][IMAGEW])
{
u8 i = 0, j = 0;
//二值化圖像顯示
TFTSPI_Set_Pos(0, 0, 160 - 1, 120 - 1); //定位字元顯示區域
for (i = 0; i < 120; i++)
{
for (j = 0; j < 160; j++)
{
if (in_IMG[i][j])
TFTSPI_Write_Word(0xffff);
else
TFTSPI_Write_Word(0x0000);
}
}
}
//顯示小圖像到TFT子產品
void S_Draw_TFT_MINI(uint8_t in_IMG[S_img_H][S_img_W])
{
u8 i, j;
//二值化圖像顯示
TFTSPI_Set_Pos(0, 0, S_img_W - 1, S_img_H - 1); //定位字元顯示區域
for (i = 0; i < S_img_H; i++)
{
for (j = 0; j < S_img_W; j++)
{
if (in_IMG[i][j])
TFTSPI_Write_Word(0xffff);
else
TFTSPI_Write_Word(0x0000);
}
}
}
// 圖像 抽取壓縮188X120 to 94X60
void S_Image_zip(uint8_t in_IMG[IMAGEH][IMAGEW], uint8_t out_IMG[S_img_H][S_img_W])
{
u8 i, j;
for (i = 0; i < S_img_H; i++) //120行,每2行采集一行,
for (j = 0; j < S_img_W; j++) //188,
out_IMG[i][j] = in_IMG[i * 2][j * 2];
}
// 圖像 填充放大 94X60 to 188X120
void S_Image_larger(uint8_t in_IMG[S_img_H][S_img_W], uint8_t out_IMG[IMAGEH][IMAGEW])
{
u8 i, j, iq, jq;
for (i = 0; i < S_img_H; i++)
{
iq = i * 2;
for (j = 0; j < S_img_W; j++)
{
jq = j * 2;
out_IMG[iq][jq] = out_IMG[iq + 1][jq + 1] =
out_IMG[iq][jq + 1] = out_IMG[iq + 1][jq] =
in_IMG[i][j];
}
}
}
/***************************************************************
*
* 函數名稱:uint8_t GetOSTU(uint8_t tmImage[S_img_H][S_img_W])
* 功能說明:求門檻值大小
* 參數說明:
* 函數傳回:門檻值大小
* 修改時間:2018年3月27日
* 備 注:
參考:https://blog.csdn.net/zyzhangyue/article/details/45841255
https://www.cnblogs.com/moon1992/p/5092726.html
https://www.cnblogs.com/zhonghuasong/p/7250540.html
Ostu方法又名最大類間差方法,通過統計整個圖像的直方圖特性來實作全局門檻值T的自動選取,其算法步驟為:
1) 先計算圖像的直方圖,即将圖像所有的像素點按照0~255共256個bin,統計落在每個bin的像素點數量
2) 歸一化直方圖,也即将每個bin中像素點數量除以總的像素點
3) i表示分類的門檻值,也即一個灰階級,從0開始疊代
4) 通過歸一化的直方圖,統計0~i 灰階級的像素(假設像素值在此範圍的像素叫做前景像素) 所占整幅圖像的比例w0,并統計前景像素的平均灰階u0;統計i~255灰階級的像素(假設像素值在此範圍的像素叫做背景像素) 所占整幅圖像的比例w1,并統計背景像素的平均灰階u1;
5) 計算前景像素和背景像素的方差 g = w0*w1*(u0-u1) (u0-u1)
6) i++;轉到4),直到i為256時結束疊代
7)将最大g相應的i值作為圖像的全局門檻值
缺陷:OSTU算法在處理光照不均勻的圖像的時候,效果會明顯不好,因為利用的是全局像素資訊。
***************************************************************/
uint8_t S_GetOSTU(uint8_t tmImage[S_img_H][S_img_W])
{
int16_t i, j;
uint32_t Amount = 0;
uint32_t PixelBack = 0;
uint32_t PixelIntegralBack = 0;
uint32_t PixelIntegral = 0;
int32_t PixelIntegralFore = 0;
int32_t PixelFore = 0;
double OmegaBack, OmegaFore, MicroBack, MicroFore, SigmaB, Sigma; // 類間方差;
int16_t MinValue, MaxValue;
uint8_t Threshold = 0;
uint8_t HistoGram[256]; //
for (j = 0; j < 256; j++) HistoGram[j] = 0; //初始化灰階直方圖
for (j = 0; j < S_img_H; j++)
{
for (i = 0; i < S_img_W; i++)
{
HistoGram[tmImage[j][i]]++; //統計灰階級中每個像素在整幅圖像中的個數
}
}
for (MinValue = 0; MinValue < 256 && HistoGram[MinValue] == 0; MinValue++); //擷取最小灰階的值
for (MaxValue = 255; MaxValue > MinValue && HistoGram[MinValue] == 0; MaxValue--); //擷取最大灰階的值
if (MaxValue == MinValue) return MaxValue; // 圖像中隻有一個顔色
if (MinValue + 1 == MaxValue) return MinValue; // 圖像中隻有二個顔色
for (j = MinValue; j <= MaxValue; j++) Amount += HistoGram[j]; // 像素總數
PixelIntegral = 0;
for (j = MinValue; j <= MaxValue; j++)
{
PixelIntegral += HistoGram[j] * j;//灰階值總數
}
SigmaB = -1;
for (j = MinValue; j < MaxValue; j++)
{
PixelBack = PixelBack + HistoGram[j]; //前景像素點數
PixelFore = Amount - PixelBack; //背景像素點數
OmegaBack = (double)PixelBack / Amount;//前景像素百分比
OmegaFore = (double)PixelFore / Amount;//背景像素百分比
PixelIntegralBack += HistoGram[j] * j; //前景灰階值
PixelIntegralFore = PixelIntegral - PixelIntegralBack;//背景灰階值
MicroBack = (double)PixelIntegralBack / PixelBack; //前景灰階百分比
MicroFore = (double)PixelIntegralFore / PixelFore; //背景灰階百分比
Sigma = OmegaBack * OmegaFore * (MicroBack - MicroFore) * (MicroBack - MicroFore);//計算類間方差
if (Sigma > SigmaB) //周遊最大的類間方差g //找出最大類間方差以及對應的門檻值
{
SigmaB = Sigma;
Threshold = j;
}
}
return Threshold; //傳回最佳門檻值;
}
//按照均值的比例進行二值化
u8 S_Get_01_Value(uint8_t tmImage[S_img_H][S_img_W])
{
int i = 0, j = 0;
u8 GaveValue;
u32 tv = 0;
//累加
for (i = 0; i < S_img_H; i++)
{
for (j = 0; j < S_img_W; j++)
{
tv += tmImage[i][j]; //累加
}
}
GaveValue = tv / S_img_H / S_img_W; //求平均值,光線越暗越小,全黑約35,對着螢幕約160,一般情況下大約100
//按照均值的比例進行二值化
GaveValue = GaveValue * 8 / 10 + 10; //此處門檻值設定,根據環境的光線來設定
return GaveValue;
}
void S_WeightedFiltering(uint8_t in_IMG[S_img_H][S_img_W], uint8_t out_IMG[S_img_H][S_img_W])
{
s8 i, j;
s8 UP, DN, L, R;
for (i = 0; i < S_img_H; i++)
{
//防溢出
UP = i - 1;
if (UP < 0) UP = 0;
DN = i + 1;
if (DN > S_img_H) DN = S_img_H;
for (j = 0; j < S_img_W; j++)
{
//防溢出
L = j - 1;
if (L < 0) UP = 0;
R = j + 1;
if (R > S_img_W) DN = S_img_W;
if ((in_IMG[i][j]) && (in_IMG[i][L] + in_IMG[i][R] + in_IMG[UP][j] + in_IMG[DN][j] +
in_IMG[UP][L] + in_IMG[UP][R] + in_IMG[DN][L] + in_IMG[DN][R] > 4))
out_IMG[i][j] = 1;
else
out_IMG[i][j] = 0;
}
}
}
extern u8 SS_ZipF[40][63]; //壓縮圖像數組 濾波使用
/*
---------------------
作者:吹泡泡的小貓
來源:CSDN
原文:https://blog.csdn.net/orbit/article/details/7323090
版權聲明:本文為部落客原創文章,轉載請附上博文連結!*/
typedef struct tagDIRECTION
{
int x_offset;
int y_offset;
}DIRECTION;
DIRECTION direction_8[] = { {-1, 0}, {-1, 1}, {0, 1}, {1, 1}, {1, 0}, {1, -1}, {0, -1}, {-1, -1} };
//DIRECTION direction_4[] = { {-1, 0}, {0, 1}, {1, 0}, {0, -1} };
DIRECTION direction_4[] = { {-1, 0}, {1, 0}, {0, 1}, {0, -1} };
#define directionXX direction_4
//注入填充算法(Flood Fill Algorithm)
void S_FloodSeedFill(uint8_t in_IMG[S_img_H][S_img_W], u8 x, u8 y, u8 old_color, u8 new_color)
{
if (x > 58 || y > 92 || x < 1 || y < 1) return; //避免溢出
if (in_IMG[x][y] == old_color)
{
in_IMG[x][y] = new_color;
//ImgZip_FLU[x][y] = 1;
for (int i = 0; i < 4; i++) // i <COUNT_OF(directionXX)
{
S_FloodSeedFill(in_IMG, x + directionXX[i].x_offset,
y + directionXX[i].y_offset, old_color, new_color);
}
}
}
//不使用遞歸 不會溢出版本
void SX_FloodSeedFill(uint8_t in_IMG[40][63], u8 x, u8 y, u8 old_color, u8 new_color)
{
u8 i, j, k;
u8 return_sign;
in_IMG[x][y] = 255; //過渡顔色
for (;;)
{
return_sign = 0;
for (i=0;i<40;i++)
for (j = 0; j < 63; j++)
{
if (in_IMG[i][j] == 255) //過渡顔色
{
in_IMG[i][j] = new_color; //實際指派
SS_ZipF[i][j] = 1;
for (k = 0; k < 4; k++)
{
x = i + directionXX[k].x_offset; y = j + directionXX[k].y_offset;
if (x < 40 && y < 63 && x >= 0 && y >= 0) //避免溢出
if (in_IMG[x][y] == old_color)
{
in_IMG[x][y] = 255; //過渡顔色
return_sign = 1;
}
}
}
}
if (return_sign == 0) return;
}
}
//在一幅圖像中判定 橫穿圖像的白塊 未實作
u8 S_Linear(u8 height, u8 width, uint8* in_IMG)
{
u8 i, j, m, n, up, dn;
u8 getONE = 0;
u8 LL_h, LL_w;
u8 RR_h, RR_w;
for (i=20;i< height-20;i++)
{
j = 0;
while (j<5) //在5點内尋得白點
{
if (in_IMG[i * width + j++])
{
LL_h = i; LL_w = j - 1; //記錄左點坐标
up = i - 7; dn = i + 7;
for (m = up; m < dn; m++)
{
n = width - 1;
while (n > width + 5) //在5點内尋得白點
{
if (in_IMG[m * width + n--])
{
RR_h = m; RR_w = n + 1; //記錄右點坐标
float kk, bb;
kk = (float)(LL_h - RR_h) / (LL_w - RR_w);
bb = (float)(LL_h - kk * LL_w);
u8 ww, ww_COUNT = 0;
for (ww = LL_w + 10; ww < RR_w - 10; ww += 10)
{
if (!in_IMG[(u8)(kk * ww + bb) * width + ww]) //如果黑
ww_COUNT = 1;
}
if (ww_COUNT == 0)
return 1;
break;
}
}
}
break;
}
}
}
return 0;
}
//參數解釋:寬 高 圖像指針 起始行 起始列 處理行大小 處理列大小
#define GrayScale 256 //frame灰階級
uint8 pixel[GrayScale];
uint8 S_Other_OSTU(int width, int height, uint8* Image)
{
int threshold = 0;
int32_t sum_gray = 0;
int32_t sum_pix_num = 0;
int32_t pl_pix_num = 0;
int32_t p2_pix_mum = 0;
int32_t p1_sum_gray = 0;
float m1 = 0;
float m2 = 0;
float V = 0;
float variance = 0;
int i, j, k = 0;
for (i = 0; i < 256; i++)
pixel[i] = 0;
//統計每個灰階級中像素的個數
for (i = 0; i < height; i++)
{
for (j = 0; j < width; j++)
{
pixel[(int)Image[i * width + j]]++;
}
}
for (k = 0; k < GrayScale; k++)
{
sum_gray += k * pixel[k];//灰階直方圖品質矩
sum_pix_num += pixel[k];//總像素個數
}
for (k = 0; k < GrayScale - 1; k++)
{
pl_pix_num += pixel[k];//第一部分像素個數
p2_pix_mum = sum_pix_num - pl_pix_num;//第二部分像素個數
p1_sum_gray += k * pixel[k]; //第一部分品質矩
m1 = (float)p1_sum_gray / pl_pix_num;//第一部分灰階均值
m2 = (float)(sum_gray - p1_sum_gray) / p2_pix_mum;//第二部分灰階均值
V = pl_pix_num * p2_pix_mum * (m1 - m2) * (m1 - m2);
if (V > variance)//将類間方差較大時的灰階值作為門檻值
{
variance = V;
threshold = k;
}
}
return threshold;
}
/************************************Robert算子************************************
** Gx={ {1, 0}, Gy={ { 0, 1},
** {0,-1}} {-1, 0}}
** 最右、最底邊緣舍去
** G(x,y)=abs(f(x,y)-f(x+1,y+1))+abs(f(x,y+1)-f(x+1,y))
***********************************************************************************/
#define Robert_G(addr, y, x) (abs(addr[y, x] - addr[DN, RR]) + abs(addr[y, RR] - addr[DN, x]))
void S_Robert(uint8_t in_IMG[PixMini_H][PixMini_W], uint8_t out_IMG[PixMini_H][PixMini_W])
{
u8 i, j;
u8 DN, RR;
for (i = 0; i < 59; i++)
{
DN = i + 1;
for (j = 0; j < 93; j++)
{
RR = j + 1;
out_IMG[i][j] = Robert_G(in_IMG, i, j);
}
}
}
/************************************Sobel算子************************************
** Gx={ {-1, 0, 1}, Gy={ { 1, 2, 1},
** {-2, 0, 2}, { 0, 0, 0},
** {-1, 0, 1}} { -1, -2, -1}}
** 最上下左右邊緣舍去
**
** Gx = (-1)*f(x-1, y-1) + 0*f(x,y-1) + 1*f(x+1,y-1)
** +(-2)*f(x-1,y) + 0*f(x,y)+2*f(x+1,y)
** +(-1)*f(x-1,y+1) + 0*f(x,y+1) + 1*f(x+1,y+1)
** = [f(x+1,y-1)+2*f(x+1,y)+f(x+1,y+1)]-[f(x-1,y-1)+2*f(x-1,y)+f(x-1,y+1)]
**
** Gy =1* f(x-1, y-1) + 2*f(x,y-1)+ 1*f(x+1,y-1)
** +0*f(x-1,y) 0*f(x,y) + 0*f(x+1,y)
** +(-1)*f(x-1,y+1) + (-2)*f(x,y+1) + (-1)*f(x+1, y+1)
** = [f(x-1,y-1) + 2f(x,y-1) + f(x+1,y-1)]-[f(x-1, y+1) + 2*f(x,y+1)+f(x+1,y+1)]
** 若Threshold=0則輸出灰階圖 其他則二值化
***********************************************************************************/
#define Sobel_Gx(addr, y, x) (addr[UP][RR]+2*addr[y][RR]+addr[DN][RR]-(addr[UP][LL]+2*addr[y][LL]+addr[DN][LL]))
#define Sobel_Gy(addr, y, x) (addr[UP][LL]+2*addr[UP][x]+addr[UP][RR]-(addr[DN][LL]+2*addr[DN][x]+addr[DN][RR]))
#define Sobel_G(addr, y, x) (abs(Sobel_Gx(addr, y, x)) + abs(Sobel_Gy(addr, y, x)))
void S_Sobel(uint8_t in_IMG[PixMini_H][PixMini_W], uint8_t out_IMG[PixMini_H][PixMini_W],u8 Threshold)
{
u8 i, j;
u8 UP, DN, LL, RR;
if (Threshold == 0)
{
for (i = 1; i < 59; i++)
{
DN = i + 1; UP = i - 1;
for (j = 1; j < 93; j++)
{
RR = j + 1; LL = j - 1;
out_IMG[i][j] = Sobel_G(in_IMG, i, j);
}
}
}
else
{
for (i = 1; i < 59; i++)
{
DN = i + 1; UP = i - 1;
for (j = 1; j < 93; j++)
{
RR = j + 1; LL = j - 1;
out_IMG[i][j] = (Sobel_G(in_IMG, i, j) >= Threshold ? 1 : 0);
}
}
}
}
//sobel 定制
//在已有二值化的圖像中标記邊緣
void S_Sobel_Custom(uint8_t in_IMG[PixMini_H][PixMini_W], uint8_t out_IMG[PixMini_H][PixMini_W], u8 Threshold)
{
u8 i, j;
u8 UP, DN, LL, RR;
u16 TempVal;
for (i = 1; i < 58; i++) //HIGH 隻計算前50行
{
DN = i + 1; UP = i - 1;
for (j = 1; j < 93; j++) //WIDE
{
RR = j + 1; LL = j - 1;
TempVal = Sobel_G(in_IMG, i, j);
if (TempVal >= Threshold)
{
out_IMG[i][j] = S_S_Custom;
}
}
}
}
/******************************60*94圖像顯示成188*120***********************************
**
*******************************************************************************************/
void S_DisplayMINI_Big(uint8_t in_IMG[PixMini_H][PixMini_W])
{
u8 i, j, k;
u16 color;
TFTSPI_Set_Pos(0, 0, 188 - 1, 120 - 1); //定位字元顯示區域
for (i = 0; i < PixMini_H; i++)
{
for (k = 0; k < 2; k++)
for (j = 0; j < PixMini_W; j++)
{
if (in_IMG[i][j] == 1)
color = u16WHITE;
else if (in_IMG[i][j] == S_S_Custom)
color = u16PURPLE;
else
color = u16BLACK;
TFTSPI_Write_Word(color);
TFTSPI_Write_Word(color);
}
}
}
/******************************60*94圖像顯示灰階圖***********************************
** size==S_BIG 顯示大圖 **
** size==S_MINI 顯示小圖 **
***************************************************************************************/
void S_Gray_DisplayMINI(uint8_t in_IMG[PixMini_H][PixMini_W], u8 size)
{
u8 i, j, k;
u16 color, temp;
if (size== S_MINI) //顯示小圖
{
TFTSPI_Set_Pos(0, 0, 94 - 1, 60 - 1); //定位字元顯示區域
for (i = 0; i < 60; i++)
{
for (j = 0; j < 94; j++)
{
temp = in_IMG[i][j];
color = (0x001f & ((temp) >> 3)) << 11;
color = color | (((0x003f) & ((temp) >> 2)) << 5);
color = color | (0x001f & ((temp) >> 3));
TFTSPI_Write_Word(color);
}
}
}
else //顯示大圖
{
TFTSPI_Set_Pos(0, 0, 188 - 1, 120 - 1); //定位字元顯示區域
for (i = 0; i < 60; i++)
{
for (k = 0; k < 2; k++)
for (j = 0; j < 94; j++)
{
temp = in_IMG[i][j];
color = (0x001f & ((temp) >> 3)) << 11;
color = color | (((0x003f) & ((temp) >> 2)) << 5);
color = color | (0x001f & ((temp) >> 3));
TFTSPI_Write_Word(color); TFTSPI_Write_Word(color);
}
}
}
}
/******************************120*188圖像顯示灰階圖************************************
** **
**************************************************************************************/
void S_Gray_Display(uint8_t in_IMG[IMAGEH][IMAGEW])
{
u8 i, j;
u16 color, temp;
TFTSPI_Set_Pos(0, 0, 188 - 1, 120 - 1); //定位字元顯示區域
for (i = 0; i < 120; i++)
{
for (j = 0; j < 188; j++)
{
temp = in_IMG[i][j];
color = (0x001f & ((temp) >> 3)) << 11;
color = color | (((0x003f) & ((temp) >> 2)) << 5);
color = color | (0x001f & ((temp) >> 3));
TFTSPI_Write_Word(color);
}
}
}
/*********************************三線圖檢測識别**************************************
** Mod=0 尋二值化 Mod=2 尋sobel **
***************************************************************************************/
void DetectionLine( uint8_t in_IMG[PixMini_H][PixMini_W], //輸入圖像
RunWay_* out_Array, //輸出數組
u8 LMP, //輸入中點
u8 Mod) //檢測模式
{
u8 i, j;
u8 M_Point, L_Point, R_Point;
u8 get_Point;
M_Point = LMP; //取輸入的底部中點
for (i = 58; i > 0; i--) //行數
{
/**/
//尋左點
j = M_Point + 5;
if (j < 99 && j>92) j = 92;
while (!((!in_IMG[i][j + Mod]) && in_IMG[i][j + 1]) && (j > 1)) { j--; }
L_Point = j;
//尋右點
j = M_Point - 5;
if (j > 99) j = 1;
while (!((!in_IMG[i][j - Mod]) && in_IMG[i][j - 1]) && (j < 92)) { j++; }
R_Point = j;
/*
//尋左點
get_Point = 0;
for (j = M_Point + 5; j > 0; j--)
{
//防溢出
//if (j < 0) j = 0;
//if (j > 187) j = 187;
if (in_IMG[i][j]== Mod)
{
L_Point = j;
get_Point = 1; //取得點
break;
}
}
if (get_Point==0) //取最左邊的黑點
{
L_Point = 0;
}
//尋右點
get_Point = 0;
for (j = M_Point - 5; j < 59; j++)
{
//防溢出
//if (j < 0) j = 0;
//if (j > 187) j = 187;
if (in_IMG[i][j] == Mod)
{
R_Point = j;
get_Point = 1; //取得點
break;
}
}
if (get_Point == 0) //取最右邊的黑點
{
R_Point = 93;
}
*/
M_Point = (L_Point + R_Point) / 2; //取上一行中點
out_Array->M[i] = M_Point;
out_Array->L[i] = L_Point;
out_Array->R[i] = R_Point;
/**/
//歸線
if (i <= 55)
{
//斷開丢線x
u8 fg_no_c = 0;
if (i <= 30)
if (abs(M_Point - out_Array->M[i + 1]) >= 5) //連續兩行中點相差5點以上
{
if (M_Point >= 50)
fg_no_c = 1;
else if (M_Point <= 45)
fg_no_c = 2;
}
if ((M_Point < 25) || (fg_no_c == 2))
{
for (; i > 0; i--)
{
out_Array->M[i] = 3;
out_Array->L[i] = 3;
out_Array->R[i] = 3;
}
break;
}
else if ((M_Point > 69) || (fg_no_c == 1))
{
for (; i > 0; i--)
{
out_Array->M[i] = 90;
out_Array->L[i] = 90;
out_Array->R[i] = 90;
}
break;
}
}
}
//TFTSPI_P6X8Int(20, 5, L_Point, u16WHITE, u16BLACK);
//TFTSPI_P6X8Int(20, 6, R_Point, u16WHITE, u16BLACK);
//S_DisplayRunWay(out_Array);
}
/******************************三線圖檢測識别結合sobel定制********************************
** **
***************************************************************************************/
void DetectionLine_Custom( uint8_t in_IMG[PixMini_H][PixMini_W], //輸入圖像
RunWay_* out_Array, //輸出數組
u8 LMP) //輸入中點
{
u8 i, j;
u8 M_Point, L_Point, R_Point;
u8 get_Point;
M_Point = LMP; //取輸入的底部中點
for (i = 57; i > 0; i--) //行數
{
/**/
//尋左點
j = M_Point + 2;
if (j < 99 && j>90) j = 90; //圖像右側2點像素不可用
while (!(in_IMG[i][j] == S_S_Custom) && (j > 1)) { j--; }
L_Point = j;
//尋右點
j = M_Point - 2;
if (j > 99) j = 1;
while (!(in_IMG[i][j] == S_S_Custom) && (j < 92)) { j++; }
R_Point = j;
/*
//尋左點
get_Point = 0;
for (j = M_Point + 5; j > 0; j--)
{
//防溢出
//if (j < 0) j = 0;
//if (j > 187) j = 187;
if (in_IMG[i][j]== Mod)
{
L_Point = j;
get_Point = 1; //取得點
break;
}
}
if (get_Point==0) //取最左邊的黑點
{
L_Point = 0;
}
//尋右點
get_Point = 0;
for (j = M_Point - 5; j < 59; j++)
{
//防溢出
//if (j < 0) j = 0;
//if (j > 187) j = 187;
if (in_IMG[i][j] == Mod)
{
R_Point = j;
get_Point = 1; //取得點
break;
}
}
if (get_Point == 0) //取最右邊的黑點
{
R_Point = 93;
}
*/
M_Point = (L_Point + R_Point) / 2; //取上一行中點
out_Array->M[i] = M_Point;
out_Array->L[i] = L_Point;
out_Array->R[i] = R_Point;
/**/
//歸線
if (i <= 55)
{
//斷開丢線x
u8 fg_no_c = 0;
if (i <= 30)
if (abs(M_Point - out_Array->M[i + 1]) >= 5) //連續兩行中點相差5點以上
{
if (M_Point >= 50)
fg_no_c = 1;
else if (M_Point <= 45)
fg_no_c = 2;
}
if ((M_Point < 25) || (fg_no_c == 2))
{
for (; i > 0; i--)
{
out_Array->M[i] = 3;
out_Array->L[i] = 3;
out_Array->R[i] = 3;
}
break;
}
else if ((M_Point > 69) || (fg_no_c == 1))
{
for (; i > 0; i--)
{
out_Array->M[i] = 90;
out_Array->L[i] = 90;
out_Array->R[i] = 90;
}
break;
}
}
}
//TFTSPI_P6X8Int(20, 5, L_Point, u16WHITE, u16BLACK);
//TFTSPI_P6X8Int(20, 6, R_Point, u16WHITE, u16BLACK);
//S_DisplayRunWay(out_Array);
}
RunWay_ TempArray;
void S_DisplayRunWay(RunWay_* in_Array)
{
u8 i, j;
for (i = 0; i < 60; i++)
{
if (i % 10 == 0) //畫線标記 刻度
{
TFTSPI_Draw_Dot(0, i, u16RED);
TFTSPI_Draw_Dot(1, i, u16RED);
TFTSPI_Draw_Dot(2, i, u16RED);
}
//抹上次點
TFTSPI_Draw_Dot(TempArray.M[i], i, 0x0000);
TFTSPI_Draw_Dot(TempArray.L[i], i, 0x0000);
TFTSPI_Draw_Dot(TempArray.R[i], i, 0x0000);
//記錄舊點
TempArray.L[i] = in_Array->L[i];
TempArray.M[i] = in_Array->M[i];
TempArray.R[i] = in_Array->R[i];
//顯示新點
TFTSPI_Draw_Dot(in_Array->M[i], i, u16YELLOW);
TFTSPI_Draw_Dot(in_Array->L[i], i, u16YELLOW);
TFTSPI_Draw_Dot(in_Array->R[i], i, u16YELLOW);
}
}
void S_DRW_Big(RunWay_* in_Array)
{
u8 i, j, k, kk;
for (i = 0; i < 60; i++)
{
for (k = 0; k < 2; k++)
{
kk = 2 * i + k;
if (kk % 10 == 0) //畫線标記 刻度
{
TFTSPI_Draw_Dot(0, kk, u16RED);
TFTSPI_Draw_Dot(1, kk, u16RED);
TFTSPI_Draw_Dot(2, kk, u16RED);
}
//抹上次點
TFTSPI_Draw_Dot(TempArray.M[i] * 2, kk, 0x0000);
TFTSPI_Draw_Dot(TempArray.L[i] * 2, kk, 0x0000);
TFTSPI_Draw_Dot(TempArray.R[i] * 2, kk, 0x0000);
//顯示新點
TFTSPI_Draw_Dot(in_Array->M[i] * 2, kk, u16CYAN);
TFTSPI_Draw_Dot(in_Array->L[i] * 2, kk, u16ORANGE);
TFTSPI_Draw_Dot(in_Array->R[i] * 2, kk, u16GREEN);
}
//記錄舊點
TempArray.L[i] = in_Array->L[i];
TempArray.M[i] = in_Array->M[i];
TempArray.R[i] = in_Array->R[i];
}
}