【嵌入式C编程】keil图像旋转仿真

老爷们求求点个赞,我完全是自己想的,没有参考祖传代码。

一、题目描述与分析

题目:导入一幅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

当然,由于存储器是依次存储数据的,因此在使用该算法时候我们需要注意两个问题:

  1. 坐标与存储矩阵转换问题
  2. 数据区旋转溢出冲突问题
    首先,由于坐标区是存储为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 0x01000x00CD=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度的结果如下:
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值