老爷们求求点个赞,我完全是自己想的,没有参考祖传代码。
一、题目描述与分析
题目:导入一幅128X128的8bit灰度图像,请在ARM 处理器上编程,使图像顺时针旋转45度,并导出图像;
分析:主要可以把问题分为三个部分。即
1. 图像预处理:将选择的图像进行处理,以满足格式,并以一定的方法输入到嵌入式系统
2. 图像旋转:采用一定的算法对内存中的数据进行操作,以实现旋转操作
3. 图像输出与显示:对于输出的格式进行解析并可视化,以便于观察和分析效果
可以看到,其中第一部分和第三部分都是在嵌入式系统外进行操作的,这里选择采用python脚本进行处理,因为python具有强大的numpy库方便对文件以及图像矩阵进行大量处理,此外还有PIL库方便图像显示。而在MCU S3C2410A上采用C语言进行编写。
需要解决的问题:综合以上分析,不难发现,本实验主要着重解决以下几个关键问题:
1. 如何在keil仿真环境中导入导出数据?软件如何操作,指令是什么?
2. Keil支持的输入输出文件格式是什么,如何编码和解码?
3. 图像旋转的算法是什么?是否可以移植现成的库,例如OpenCv?
下面,将着重解决以上问题并实现最终目标。
二、软件操作与问题探究
OpenCV的由于包含了各种图像识别子库,其大小大约90M,因此首先放弃移植调用OpenCv的想法。使用经典的旋转算法进行图像旋转。
经典的图像算法就是在笛卡尔坐标系为中心进行矩阵变换,公式如下
此公式参考自:
https://blog.csdn.net/fengye2two/article/details/83148607?utm_medium=distribute.pc_relevant.none-task-blog-2defaultbaidujs_baidulandingword~default-0-83148607-blog-111402473.t0_edu_mlt&spm=1001.2101.3001.4242.1&utm_relevant_index=3
当然,由于存储器是依次存储数据的,因此在使用该算法时候我们需要注意两个问题:
- 坐标与存储矩阵转换问题
- 数据区旋转溢出冲突问题
首先,由于坐标区是存储为unsigned char类型的二维数组,在存储空间中连续存放。假定指向其起始地址的指针为p,因此第i行第j列的元素可以表示为*(p+128i+j)
其次,由于存储区域是连续的,因此如果旋转算法计算出的点旋转到了图像外面,(例如第1行第129列),则如果使用原来的公式(p+1281+129)就和(p+128*2+1)即第2行第2列的元素是同一个元素。如果直接写入会导致第2行第2列的元素位置像素颜色错误。因此需要采用函数来判断是否溢出,如果溢出就不更新相应的存储内容。
解决以上问题后,下面简述软件的操作问题。
在debug模式下,keil支持在command控制台里为内存批量导入导出数据。
指令为load filename.hex和save filename.hex addr1,addr2
导出的格式遵循H.converter格式,但是H.converter不支持windows10,因此我们需要解析其hex格式的编码方式。随便打开一个hex文件,其部分如下图,
编码格式如下
我会在第三节的dumpfile.py和display.py中根据该编码格式对其进行数据编码和解码以实现hex文件和图像之间的相互转换。
其中校验和的计算如下:主要思想是计算字节在16进制模下的和,并求补。
0
x
10
+
0
x
24
+
0
x
62
+
0
x
00
+
0
x
46
+
0
x
4
C
+
0
x
55
+
0
x
49
+
0
x
44
+
0
x
20
+
0
x
50
+
0
x
52
+
0
x
4
F
+
0
x
46
+
0
x
49
+
0
x
4
C
+
0
x
45
+
0
x
00
+
0
x
46
+
0
x
4
C
=
0
x
04
C
D
0x10+0x24+0x62+0x00+0x46+0x4C+0x55+0x49+0x44+0x20+0x50+0x52+0x4F+0x46+0x49+0x4C+0x45+0x00+0x46+0x4C=0x04CD
0x10+0x24+0x62+0x00+0x46+0x4C+0x55+0x49+0x44+0x20+0x50+0x52+0x4F+0x46+0x49+0x4C+0x45+0x00+0x46+0x4C=0x04CD
0
x
4
C
D
%
0
x
0100
=
0
x
00
C
D
0x4CD \% 0x0100=0x00CD
0x4CD%0x0100=0x00CD
0
x
0100
−
0
x
00
C
D
=
0
x
33
0x0100-0x00CD=0x33
0x0100−0x00CD=0x33
这一部分有关keil输出hex文件格式的内容参考如下
https://blog.csdn.net/qq_31020665/article/details/106523756
在编译-连接main.c文件中,发现如果设置的ROM资源足时会导致link fail,报错为space不足。这是由于代码量较大导致空间不足,解决方式是在option-target中为其分配较为充足的资源。同时需要注意,由于循环操作会在堆中产生大量动态内存空间,因此使用malloc请求空间时需要留意在本轮循环中进行free,以防止RAM空间不足。
内存空间分配如下
分配完之后再进行编译-连接就不会再报错了。
在debug运行之前,需要在memory map中对我们使用到的存储图像的空间进行初始化申请,否则会出现无法进入main函数,跳入软件中断的情况。
由于128*128图像是灰度图,每个像素点共256种灰度,可以用一个字节八位数据表示,在程序中可以将其定义为unsigned char类型。总大小就是16k字节,占用地址空间共0x4000。因此我对其分配0x30004000-0x30007fff为输入图像存储空间,而0x30008000-0x3000cfff为输出图像存储空间。这样两个空间分离可以方便程序中进行旋转运算等操作,而不会影响原始数据。Memory map操作如下
然后通过之前所述的load指令装入处理好的图像hex数据:
可以看到数据刚好装到0x30007fff,而0x30008000没有数据,这是我们存放输出图像的起始位置。
然后选择main函数中的return0作为断点,点击运行,会发现从PIC_STORE_START(0x30008000)开始的数据存储内容开始发生变化,这表明程序已经在运行过程中开始将旋转后的图像数据进行存储。
当程序运行结束停在断点时,在command中输入保存指令
s
a
v
e
j
n
t
m
_
r
.
h
e
x
0
x
30008000
,
0
x
3000
c
f
f
f
save \quad jntm\_r.hex \quad 0x30008000,0x3000cfff
savejntm_r.hex0x30008000,0x3000cfff
就可以在当前文件夹内找到该输出的hex文件,后续使用display.py文件对其进行解码与显示处理。
三、主要程序和解析
main.c程序
作用:实现图像旋转算法并且进行插值优化
输入:command命令load进0x30004000,0x30007fff的128*128灰度图像
输出:与输入相同格式,每个字节是unsigned char类型的旋转后的灰度图像
函数解析:
axis_convert:将矩阵坐标转换为以图像中心为原点的二维坐标
co_rotate:在二维坐标下进行旋转变换
axis_reconvert:将旋转完的坐标转回矩阵坐标,返回整形
judge:评估元素是否旋转出了图像,旋转出图像的元素点不进行更新,防止内存冲突
核心部分:旋转算法
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define PIC_START (unsigned char *)(0x30004000)//image store base address,every single char stands for a pixel
#define PIC_STORE_START (unsigned char *)(0x30008000)//END AT 0x3000CFFF,space to store output img
float *axis_convert(int j1,int i1);
float *co_rotate(float j2,float i2,float alpha);
int *axis_reconvert(float j3,float i3);
int judge(int j4,int i4);
int main() {
//unsigned char volatile *p_pic1=PIC_START;
//unsigned char volatile *p_pic2=PIC_STORE_START;
int *p3;
int i,j,j4,i4;
float *p1,*p2;
float j2,i2,j3,i3;
float rad;
rad=3.14*(120.0/180.0);
for(i=0;i<128;i++){//line
for(j=0;j<128;j++){//column img[i][j] corodinate(j,i)
p1=axis_convert(j,i);
j2=*p1;
i2=*(p1+1);
free(p1);//free space that p1 point to
p2=co_rotate(j2,i2,rad);
j3=*p2;
i3=*(p2+1);
free(p2);
p3=axis_reconvert(j3,i3);
j4=*p3;
i4=*(p3+1);
free(p3);
if(judge(j4,i4)){
*(PIC_STORE_START+128*i4+j4)=*(PIC_START+128*i+j);
}
//*(p_pic1)=(char)(2*i+j);//*(PIC_START+128*i+j) equal to *(p_pic)
//p_pic1++;
}
}
//insert program
for(i=1;i<127;i++){
for(j=1;j<127;j++){//pic[i][j] (j,i)
if(*(PIC_STORE_START+128*i+j)==0){
*(PIC_STORE_START+128*i+j)=(int)(*(PIC_STORE_START+128*(i-1)+j)+
*(PIC_STORE_START+128*(i+1)+j)+
*(PIC_STORE_START+128*i+(j-1))+
*(PIC_STORE_START+128*i+(j+1)))/4;
}
}
}
return 0;
}
float *axis_convert(int j1,int i1){
float *p;
p=(float *)(malloc(sizeof(float)*2));
*p=j1-63.5;
*(p+1)=63.5-i1;
return p;
}
int *axis_reconvert(float j3,float i3){
int *p;
p=(int *)(malloc(sizeof(int)*2));
*p=(int)(j3+64);//(int) will discard its tail
*(p+1)=(int)(64-i3);
return p;
}
float *co_rotate(float j2,float i2,float alpha){//alpha in rad
float *p;
p=(float *)(malloc(sizeof(float)*2));
*p=cos(alpha)*j2+sin(alpha)*i2;
*(p+1)=-sin(alpha)*j2+cos(alpha)*i2;
return p;
}
int judge(int j4,int i4){
if(0<=j4<=127 && 0<=i4<=127){
return 1;
}
else{
return 0;//rotate out
}
}
}
dumpfile.py程序
作用:将选择的图像进行大小调整、灰度化和裁剪,并且按照H.converter的格式将每个灰度像素字节按次序存入hex文本文件
输入:任意大小名字为filename的图像
输出:遵循H.converter格式,可以被keil command“load”指令识别的hex文件
核心部分:计算校验和
#将图像调整为128*128灰度图并转出为hex文件
#输出"jntm.hex",并在嵌入式系统中旋转,输出jntm_r.hex到本文件夹中
from fileinput import filename
import os
import imghdr
import matplotlib.pyplot as plt
from PIL import Image
import numpy as np
def cal_verisum(line_str):#输入字符串,计算校验和,并返回完整行(含换行符)
lst20=[]
for i in range(20):
lst20.append(line_str[0+2*i:2+2*i])
sum=0
for hex_num in lst20:
sum+=int(hex_num,base=16)###
return ':'+line_str+hex_2((256-sum%256)%256)+'\n'#问题出在hex函数没有补0对其
def hex_2(num):#2位hex
if(num>=16):
return (hex(num)[2:]).upper()
else:
return ('0'+hex(num)[2:]).upper()
def hex_4(num):#4位hex
return (hex(num)[2:]).upper()
line_start=":020000043000CA\n"
line_end=":00000001FF\n"
image_name="jntm.jpeg"
file_name="jntm.txt"
#图片操作
img1=Image.open(image_name)
img1=img1.convert("L")
img1=img1.crop([120,0,480,360])
img1=img1.resize((128,128))
plt.imshow(img1,cmap='gray')
plt.show()
img_matrix=np.array(img1)
###文件打开等操作
Note=open(file_name,mode='w')
Note.write(line_start)#写入第一行
strADDR=16384#0x4000
for i in range(128):#每行需要切分成16*8份
pic_line=list(img_matrix[i])
pic_line=list(map(hex_2,pic_line))#转为十六进制大写
line_str=""
j=0
for item in pic_line:
line_str+=item
j+=1
#print(j,line_str)
if(j>=16):
print(hex_4(strADDR))
print(line_str)
line_str=cal_verisum("10"+hex_4(strADDR)+"00"+line_str)#计算校验和
Note.write(line_str) #输出
j=0
strADDR+=16
line_str=""
Note.write(line_end)
####文件保存操作
Note.close()
#生成hex文件
ext=os.path.splitext(file_name)
dump_file=ext[0]+'.hex'
os.rename(os.path.join('./',file_name),os.path.join('./',dump_file))
display.py程序
作用:将输出的hex文件转换为图像并进行显示
输入:keil command save命令输出的hex文件
输出:遵循H.converter格式,解码hex文件并进行图像显示
核心部分:解码hex文件并转换为numpy类型进行图像显示
接收嵌入式输出"jntm_r.hex"并将其解码输出显示图像
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
import os
file_name=r"jntm_r.hex"
Start_flag=9#parameter
End_flag=-3
Line_size=16
def pic_line(line_str):#transform line to list in decemial
line_dec=[]
for i in range(Line_size):
line_dec.append(int(line_str[0+2*i:2+2*i],base=16))
return line_dec
#修改hex文件名为txt
ext=os.path.splitext(file_name)
rename_file=ext[0]+'.txt'
os.rename(os.path.join('./',file_name),os.path.join('./',rename_file))
f=open(rename_file,"r")
_=f.readline()#read line 1
matrix_pic=[]
for j in range(128):
line_dec128=[]
for i in range(8):
line_str=f.readline()#read a new line
line_dec16=pic_line(line_str[Start_flag:End_flag])#line crop
line_dec128+=line_dec16
matrix_pic.append(line_dec128)
numpy_pic=np.array(matrix_pic)
print(numpy_pic)
plt.imshow(numpy_pic,cmap='gray')#
plt.show()
四、结果展示与分析
首先,选取的彩色示例图片如下
在dumfile.py文件中,对其进行了裁剪和灰度化,得到如下图像
然后生成hex文件,导入到keil中,进行旋转处理,并导出,使用display.py进行显示。由于旋转过程中坐标有取整函数,因此会产生一些空隙,如下图所示。
因此,在main.c函数中加入了简单的插值函数,即某一点若值为0,则把其上下左右四个点的均值作为其灰度值,并进行刷新。
改进的main.c得到的结果如下,分别得到旋转45度,90度和120度的结果如下: