位置: IT常识 - 正文

【Keras+计算机视觉+Tensorflow】OCR文字识别实战(附源码和数据集 超详细必看)(计算机视觉:一种现代方法)

编辑:rootadmin
【Keras+计算机视觉+Tensorflow】OCR文字识别实战(附源码和数据集 超详细必看)

推荐整理分享【Keras+计算机视觉+Tensorflow】OCR文字识别实战(附源码和数据集 超详细必看)(计算机视觉:一种现代方法),希望有所帮助,仅作参考,欢迎阅读内容。

文章相关热门搜索词:计算机的视频,计算机视觉:一种现代方法,计算机视频解释,计算机的视频,计算机视觉 视频处理,计算机视频解释,计算机视觉百度百科,计算机视觉:一种现代方法,内容如对您有帮助,希望把文章链接给更多的朋友!

需要源码和数据集请点赞关注收藏后评论区留言私信~~~

一、OCR文字识别简介

利用计算机自动识别字符的技术,是模式识别应用的一个重要领域。人们在生产和生活中,要处理大量的文字、报表和文本。为了减轻人们的劳动,提高处理效率,从上世纪50年代起就开始探讨文字识别方法,并研制出光学字符识别器。

OCR(Optical Character Recognition)图像文字识别是人工智能的重要分支,赋予计算机人眼的功能,使其可以看图识字,图像文字识别系统流程一般分为图像采集、文字检测、文字识别以及结果输出四部分。

 二、OCR文字识别项目实战1:数据集简介

MSRA-TD500该数据集共包含500 张自然场景图像,其分辨率在1296 ´ 864至920 ´ 1280 之间,涵盖了室内商场、标识牌、室外街道、广告牌等大多数场,文本包含中文和英文,有着不同的字体、大小和倾斜方向,部分数据集图像如下图所示。

 数据集项目结构如下 分为训练集和测试集

2:项目结构【Keras+计算机视觉+Tensorflow】OCR文字识别实战(附源码和数据集 超详细必看)(计算机视觉:一种现代方法)

整体项目结构如下 上面是一些算法和模型比如CRAFT CRNN的定义,下面是测试代码

 CRAFT算法实现文本行的检测如图下图所示。首先将完整的文字区域输入CRAFT文字检测网络,得到字符级的文字得分结果热图(Text Score)和字符级文本连接得分热图(Link Score),最后根据连通域得到每个文本行的位置

3:效果展示 

开始运行代码

输出运行结果 可以放入不同图片进行测试 

 

 

 

 

三、代码 

部分代码如下 需要全部代码和数据集请点赞关注收藏后评论区留言私信~~~  

"""This script demonstrates how to train the modelon the SynthText90 using multiple GPUs."""# pylint: disable=invalid-nameimport datetimeimport argparseimport mathimport randomimport stringimport functoolsimport itertoolsimport osimport tarfileimport urllib.requestimport numpy as npimport cv2import imgaugimport tqdmimport tensorflow as tfimport keras_ocr# pylint: disable=redefined-outer-namedef get_filepaths(data_path, split): """Get the list of filepaths for a given split (train, val, or test).""" with open(os.path.join(data_path, f'mnt/ramdisk/max/90kDICT32px/annotation_{split}.txt'), 'r') as text_file: filepaths = [ os.path.join(data_path, 'mnt/ramdisk/max/90kDICT32px', line.split(' ')[0][2:]) for line in text_file.readlines() ] return filepaths# pylint: disable=redefined-outer-namedef download_extract_and_process_dataset(data_path): """Download and extract the synthtext90 dataset.""" archive_filepath = os.path.join(data_path, 'mjsynth.tar.gz') extraction_directory = os.path.join(data_path, 'mnt') if not os.path.isfile(archive_filepath) and not os.path.isdir(extraction_directory): print('Downloading the dataset.') urllib.request.urlretrieve("https://www.robots.ox.ac.uk/~vgg/data/text/mjsynth.tar.gz", archive_filepath) if not os.path.isdir(extraction_directory): print('Extracting files.') with tarfile.open(os.path.join(data_path, 'mjsynth.tar.gz')) as tfile: tfile.extractall(data_path)def get_image_generator(filepaths, augmenter, width, height): """Get an image generator for a list of SynthText90 filepaths.""" filepaths = filepaths.copy() for filepath in itertools.cycle(filepaths): text = filepath.split(os.sep)[-1].split('_')[1].lower() image = cv2.imread(filepath) if image is None: print(f'An error occurred reading: {filepath}') image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) image = keras_ocr.tools.fit(image, width=width, height=height, cval=np.random.randint(low=0, high=255, size=3).astype('uint8')) if augmenter is not None: image = augmenter.augment_image(image) if filepath == filepaths[-1]: random.shuffle(filepaths) yield image, textif __name__ == '__main__': parser = argparse.ArgumentParser(description='Process some integers.') parser.add_argument('--model_id', default='recognizer', help='The name to use for saving model checkpoints.') parser.add_argument( '--data_path', default='.', help='The path to the directory containing the dataset and where we will put our logs.') parser.add_argument( '--logs_path', default='./logs', help=( 'The path to where logs and checkpoints should be stored. ' 'If a checkpoint matching "model_id" is found, training will resume from that point.')) parser.add_argument('--batch_size', default=16, help='The training batch size to use.') parser.add_argument('--no-file-verification', dest='verify_files', action='store_false') parser.set_defaults(verify_files=True) args = parser.parse_args() weights_path = os.path.join(args.logs_path, args.model_id + '.h5') csv_path = os.path.join(args.logs_path, args.model_id + '.csv') download_extract_and_process_dataset(args.data_path) with tf.distribute.MirroredStrategy().scope(): recognizer = keras_ocr.recognition.Recognizer(alphabet=string.digits + string.ascii_lowercase, height=31, width=200, stn=False, optimizer=tf.keras.optimizers.RMSprop(), weights=None) if os.path.isfile(weights_path): print('Loading saved weights and creating new version.') dt_string = datetime.datetime.now().isoformat() weights_path = os.path.join(args.logs_path, args.model_id + '_' + dt_string + '.h5') csv_path = os.path.join(args.logs_path, args.model_id + '_' + dt_string + '.csv') recognizer.model.load_weights(weights_path) augmenter = imgaug.augmenters.Sequential([ imgaug.augmenters.Multiply((0.9, 1.1)), imgaug.augmenters.GammaContrast(gamma=(0.5, 3.0)), imgaug.augmenters.Invert(0.25, per_channel=0.5) ]) os.makedirs(args.logs_path, exist_ok=True) training_filepaths, validation_filepaths = [ get_filepaths(data_path=args.data_path, split=split) for split in ['train', 'val'] ] if args.verify_files: assert all( os.path.isfile(filepath) for filepath in tqdm.tqdm(training_filepaths + validation_filepaths, desc='Checking filepaths.')), 'Some files appear to be missing.' (training_image_generator, training_steps), (validation_image_generator, validation_steps) = [ (get_image_generator( filepaths=filepaths, augmenter=augmenter, width=recognizer.model.input_shape[2], height=recognizer.model.input_shape[1], ), math.ceil(len(filepaths) / args.batch_size)) for filepaths, augmenter in [(training_filepaths, augmenter), (validation_filepaths, None)] ] training_generator, validation_generator = [ tf.data.Dataset.from_generator( functools.partial(recognizer.get_batch_generator, image_generator=image_generator, batch_size=args.batch_size), output_types=((tf.float32, tf.int64, tf.float64, tf.int64), tf.float64), output_shapes=((tf.TensorShape([None, 31, 200, 1]), tf.TensorShape([None, recognizer.training_model.input_shape[1][1]]), tf.TensorShape([None, 1]), tf.TensorShape([None, 1])), tf.TensorShape([None, 1]))) for image_generator in [training_image_generator, validation_image_generator] ] callbacks = [ tf.keras.callbacks.EarlyStopping(monitor='val_loss', min_delta=0, patience=10, restore_best_weights=False), tf.keras.callbacks.ModelCheckpoint(weights_path, monitor='val_loss', save_best_only=True), tf.keras.callbacks.CSVLogger(csv_path) ] recognizer.training_model.fit( x=training_generator, steps_per_epoch=training_steps, validation_steps=validation_steps, validation_data=validation_generator, callbacks=callbacks, epochs=1000, )"""This script is what was used to generate thebackgrounds.zip and fonts.zip files."""# pylint: disable=invalid-name,redefined-outer-nameimport jsonimport urllib.requestimport urllib.parseimport concurrentimport shutilimport zipfileimport globimport osimport numpy as npimport tqdmimport cv2import keras_ocrif __name__ == '__main__': fonts_commit = 'a0726002eab4639ee96056a38cd35f6188011a81' fonts_sha256 = 'e447d23d24a5bbe8488200a058cd5b75b2acde525421c2e74dbfb90ceafce7bf' fonts_source_zip_filepath = keras_ocr.tools.download_and_verify( url=f'https://github.com/google/fonts/archive/{fonts_commit}.zip', cache_dir='.', sha256=fonts_sha256) shutil.rmtree('fonts-raw', ignore_errors=True) with zipfile.ZipFile(fonts_source_zip_filepath) as zfile: zfile.extractall(path='fonts-raw') retained_fonts = [] sha256s = [] basenames = [] # The blacklist includes fonts that, at least for the English alphabet, were found # to be illegible (e.g., thin fonts) or render in unexpected ways (e.g., mathematics # fonts). blacklist = [ 'AlmendraDisplay-Regular.ttf', 'RedactedScript-Bold.ttf', 'RedactedScript-Regular.ttf', 'Sevillana-Regular.ttf', 'Mplus1p-Thin.ttf', 'Stalemate-Regular.ttf', 'jsMath-cmsy10.ttf', 'Codystar-Regular.ttf', 'AdventPro-Thin.ttf', 'RoundedMplus1c-Thin.ttf', 'EncodeSans-Thin.ttf', 'AlegreyaSans-ThinItalic.ttf', 'AlegreyaSans-Thin.ttf', 'FiraSans-Thin.ttf', 'FiraSans-ThinItalic.ttf', 'WorkSans-Thin.ttf', 'Tomorrow-ThinItalic.ttf', 'Tomorrow-Thin.ttf', 'Italianno-Regular.ttf', 'IBMPlexSansCondensed-Thin.ttf', 'IBMPlexSansCondensed-ThinItalic.ttf', 'Lato-ExtraLightItalic.ttf', 'LibreBarcode128Text-Regular.ttf', 'LibreBarcode39-Regular.ttf', 'LibreBarcode39ExtendedText-Regular.ttf', 'EncodeSansExpanded-ExtraLight.ttf', 'Exo-Thin.ttf', 'Exo-ThinItalic.ttf', 'DrSugiyama-Regular.ttf', 'Taviraj-ThinItalic.ttf', 'SixCaps.ttf', 'IBMPlexSans-Thin.ttf', 'IBMPlexSans-ThinItalic.ttf', 'AdobeBlank-Regular.ttf', 'FiraSansExtraCondensed-ThinItalic.ttf', 'HeptaSlab[wght].ttf', 'Karla-Italic[wght].ttf', 'Karla[wght].ttf', 'RalewayDots-Regular.ttf', 'FiraSansCondensed-ThinItalic.ttf', 'jsMath-cmex10.ttf', 'LibreBarcode39Text-Regular.ttf', 'LibreBarcode39Extended-Regular.ttf', 'EricaOne-Regular.ttf', 'ArimaMadurai-Thin.ttf', 'IBMPlexSerif-ExtraLight.ttf', 'IBMPlexSerif-ExtraLightItalic.ttf', 'IBMPlexSerif-ThinItalic.ttf', 'IBMPlexSerif-Thin.ttf', 'Exo2-Thin.ttf', 'Exo2-ThinItalic.ttf', 'BungeeOutline-Regular.ttf', 'Redacted-Regular.ttf', 'JosefinSlab-ThinItalic.ttf', 'GothicA1-Thin.ttf', 'Kanit-ThinItalic.ttf', 'Kanit-Thin.ttf', 'AlegreyaSansSC-ThinItalic.ttf', 'AlegreyaSansSC-Thin.ttf', 'Chathura-Thin.ttf', 'Blinker-Thin.ttf', 'Italiana-Regular.ttf', 'Miama-Regular.ttf', 'Grenze-ThinItalic.ttf', 'LeagueScript-Regular.ttf', 'BigShouldersDisplay-Thin.ttf', 'YanoneKaffeesatz[wght].ttf', 'BungeeHairline-Regular.ttf', 'JosefinSans-Thin.ttf', 'JosefinSans-ThinItalic.ttf', 'Monofett.ttf', 'Raleway-ThinItalic.ttf', 'Raleway-Thin.ttf', 'JosefinSansStd-Light.ttf', 'LibreBarcode128-Regular.ttf' ] for filepath in tqdm.tqdm(sorted(glob.glob('fonts-raw/**/**/**/*.ttf')), desc='Filtering fonts.'): sha256 = keras_ocr.tools.sha256sum(filepath) basename = os.path.basename(filepath) # We check the sha256 and filenames because some of the fonts # in the repository are duplicated (see TRIVIA.md). if sha256 in sha256s or basename in basenames or basename in blacklist: continue sha256s.append(sha256) basenames.append(basename) retained_fonts.append(filepath) retained_font_families = set([filepath.split(os.sep)[-2] for filepath in retained_fonts]) added = [] with zipfile.ZipFile(file='fonts.zip', mode='w') as zfile: for font_family in tqdm.tqdm(retained_font_families, desc='Saving ZIP file.'): # We want to keep all the metadata files plus # the retained font files. And we don't want # to add the same file twice. files = [ input_filepath for input_filepath in glob.glob(f'fonts-raw/**/**/{font_family}/*') if input_filepath not in added and (input_filepath in retained_fonts or os.path.splitext(input_filepath)[1] != '.ttf') ] added.extend(files) for input_filepath in files: zfile.write(filename=input_filepath, arcname=os.path.join(*input_filepath.split(os.sep)[-2:])) print('Finished saving fonts file.') # pylint: disable=line-too-long url = ( 'https://commons.wikimedia.org/w/api.php?action=query&generator=categorymembers&gcmtype=file&format=json' '&gcmtitle=Category:Featured_pictures_on_Wikimedia_Commons&prop=imageinfo&gcmlimit=50&iiprop=url&iiurlwidth=1024' ) gcmcontinue = None max_responses = 300 responses = [] for responseCount in tqdm.tqdm(range(max_responses)): current_url = url if gcmcontinue is not None: current_url += f'&continue=gcmcontinue||&gcmcontinue={gcmcontinue}' with urllib.request.urlopen(url=current_url) as response: current = json.loads(response.read()) responses.append(current) gcmcontinue = None if 'continue' not in current else current['continue']['gcmcontinue'] if gcmcontinue is None: break print('Finished getting list of images.') # We want to avoid animated images as well as icon files. image_urls = [] for response in responses: image_urls.extend( [page['imageinfo'][0]['thumburl'] for page in response['query']['pages'].values()]) image_urls = [url for url in image_urls if url.lower().endswith('.jpg')] shutil.rmtree('backgrounds', ignore_errors=True) os.makedirs('backgrounds') assert len(image_urls) == len(set(image_urls)), 'Duplicates found!' with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor: futures = [ executor.submit(keras_ocr.tools.download_and_verify, url=url, cache_dir='./backgrounds', verbose=False) for url in image_urls ] for _ in tqdm.tqdm(concurrent.futures.as_completed(futures), total=len(futures)): pass for filepath in glob.glob('backgrounds/*.JPG'): os.rename(filepath, filepath.lower()) print('Filtering images by aspect ratio and maximum contiguous contour.') image_paths = np.array(sorted(glob.glob('backgrounds/*.jpg'))) def compute_metrics(filepath): image = keras_ocr.tools.read(filepath) aspect_ratio = image.shape[0] / image.shape[1] contour, _ = keras_ocr.tools.get_maximum_uniform_contour(image, fontsize=40) area = cv2.contourArea(contour) if contour is not None else 0 return aspect_ratio, area metrics = np.array([compute_metrics(filepath) for filepath in tqdm.tqdm(image_paths)]) filtered_paths = image_paths[(metrics[:, 0] < 3 / 2) & (metrics[:, 0] > 2 / 3) & (metrics[:, 1] > 1e6)] detector = keras_ocr.detection.Detector() paths_with_text = [ filepath for filepath in tqdm.tqdm(filtered_paths) if len( detector.detect( images=[keras_ocr.tools.read_and_fit(filepath, width=640, height=640)])[0]) > 0 ] filtered_paths = np.array([path for path in filtered_paths if path not in paths_with_text]) filtered_basenames = list(map(os.path.basename, filtered_paths)) basename_to_url = { os.path.basename(urllib.parse.urlparse(url).path).lower(): url for url in image_urls } filtered_urls = [basename_to_url[basename.lower()] for basename in filtered_basenames] assert len(filtered_urls) == len(filtered_paths) removed_paths = [filepath for filepath in image_paths if filepath not in filtered_paths] for filepath in removed_paths: os.remove(filepath) with open('backgrounds/urls.txt', 'w') as f: f.write('\n'.join(filtered_urls)) with zipfile.ZipFile(file='backgrounds.zip', mode='w') as zfile: for filepath in tqdm.tqdm(filtered_paths.tolist() + ['backgrounds/urls.txt'], desc='Saving ZIP file.'): zfile.write(filename=filepath, arcname=os.path.basename(filepath.lower()))

创作不易 觉得有帮助请点赞关注收藏~~~

本文链接地址:https://www.jiuchutong.com/zhishi/295982.html 转载请保留说明!

上一篇:Vue的路由配置(Vue2和Vue3的路由配置)(vue路由的几种方式)

下一篇:圣米歇尔山,法国诺曼底 (© DaLiu/Getty Images)(圣米歇尔山法语介)

  • 现代励志故事:只有相信奇迹才能创造奇迹(现代励志故事200字)

    现代励志故事:只有相信奇迹才能创造奇迹(现代励志故事200字)

  • 支付宝授权管理在哪里(关闭支付宝授权管理)

    支付宝授权管理在哪里(关闭支付宝授权管理)

  • 抖音网络连接失败是什么原因呢(抖音网络连接失败怎么办)

    抖音网络连接失败是什么原因呢(抖音网络连接失败怎么办)

  • 小米9和小米8指纹版的区别(小米8 小米9)

    小米9和小米8指纹版的区别(小米8 小米9)

  • pop3用来接收还是发送邮件(采用pop3方式收发电子邮件时)

    pop3用来接收还是发送邮件(采用pop3方式收发电子邮件时)

  • 陌声互相关注以后可以免费聊天吗(陌声里面关注对方是不是就不用收费)

    陌声互相关注以后可以免费聊天吗(陌声里面关注对方是不是就不用收费)

  • 网页的元素包括(网页元素包括起始标记)

    网页的元素包括(网页元素包括起始标记)

  • qq群有考勤统计功能吗(qq群有考勤统计表吗)

    qq群有考勤统计功能吗(qq群有考勤统计表吗)

  • 华为p40是双卡还是单卡(华为p40双卡还可以插内存卡吗)

    华为p40是双卡还是单卡(华为p40双卡还可以插内存卡吗)

  • ppt怎么设置切换时间(PPT怎么设置切换动画)

    ppt怎么设置切换时间(PPT怎么设置切换动画)

  • vivos1多少w闪充(vivos1充电器是快充还是闪充)

    vivos1多少w闪充(vivos1充电器是快充还是闪充)

  • 快手极速版如何暂停视频(快手极速版如何注销账号?)

    快手极速版如何暂停视频(快手极速版如何注销账号?)

  • 红米k30可以插内存卡吗(redmi k30能不能插内存卡)

    红米k30可以插内存卡吗(redmi k30能不能插内存卡)

  • 有个碎片整理在哪里找(碎片整理一直停留在第一遍0%已合并)

    有个碎片整理在哪里找(碎片整理一直停留在第一遍0%已合并)

  • 华为mate30pro有没有膜(华为mate30pro有没有OTG功能)

    华为mate30pro有没有膜(华为mate30pro有没有OTG功能)

  • vivo手机无互联网连接(vivo手机无互联网连接是什么意思)

    vivo手机无互联网连接(vivo手机无互联网连接是什么意思)

  • 优活手环2怎么显示中文(优活手环怎么充电)

    优活手环2怎么显示中文(优活手环怎么充电)

  • vivo手电筒在哪里设置(vivo手电筒在哪里找到)

    vivo手电筒在哪里设置(vivo手电筒在哪里找到)

  • 华为nova5隐藏图标怎么弄(华为nova5隐藏相册怎么打开)

    华为nova5隐藏图标怎么弄(华为nova5隐藏相册怎么打开)

  • icloud空间没满却说满(icloud空间没满却说无法备份)

    icloud空间没满却说满(icloud空间没满却说无法备份)

  • 融合定位华为有什么用(华为融合定位是手机被定位了吗)

    融合定位华为有什么用(华为融合定位是手机被定位了吗)

  • 苹果无线耳机港版和国行的区别(苹果无线耳机港版多少钱)

    苹果无线耳机港版和国行的区别(苹果无线耳机港版多少钱)

  • b站弹幕发了看不到了(b站发出的弹幕)

    b站弹幕发了看不到了(b站发出的弹幕)

  • 2018年哪些固态硬盘值得选购?120g-240g固态硬盘推(2019年固态硬盘价格)

    2018年哪些固态硬盘值得选购?120g-240g固态硬盘推(2019年固态硬盘价格)

  • 如何在Excel中计算标准差(如何在Excel中计算平均值)

    如何在Excel中计算标准差(如何在Excel中计算平均值)

  • vue项目实现pc端和手机端屏幕自适应(vue开发pc前端网站)

    vue项目实现pc端和手机端屏幕自适应(vue开发pc前端网站)

  • 在Vue中使用高德地图(vue调用高德js)

    在Vue中使用高德地图(vue调用高德js)

  • 基于vue 实现 excel导出导入功能(vue操作excel)

    基于vue 实现 excel导出导入功能(vue操作excel)

  • 航天信息开票软件
  • 基本税种有哪些税种
  • 怎么确认债权
  • 特许权使用费收入
  • 过路费定额发票还能入账吗
  • 外贸出口免税政策
  • 年终奖12月份计提少了
  • 净资本和净资产区别
  • 房租收入如何缴税?
  • 分期付款外币采购形成的差异如何做会计处理?
  • 房产税细节
  • 预付账款怎么做凭证
  • 资产减值准备的会计科目
  • 施工企业营改增税费有哪些
  • 2021年7月1日执行
  • 缴纳印花税税会计分录怎么做
  • 其他收益期末余额在借方还是贷方
  • 外贸出口的进项可以抵扣吗
  • 小规模增值税本期免税额怎么计算
  • 合同税率16%调整到13%怎么算
  • 生产型企业进出口初申报流程
  • win8电脑一键还原怎么操作
  • c盘垃圾太多需要重装系统嘛
  • 被投资企业所在地什么意思
  • 固定资产占资产总额比例
  • ph是什么文件
  • mcshield.exe是什么进程
  • hhcol.exe
  • php数组转js数组
  • 事业单位成本核算制度
  • php 抽象类
  • java pdf生成工具
  • mksysb命令
  • testdisk安装方法
  • 怎样合理的运用网络
  • 企业的借款费用怎么入账
  • 生产车间领用材料的会计分录
  • 资产负债表的种类
  • 买手机手续费怎么算的
  • access2000是什么
  • win7安装软件时提示不能打开要写入的文件
  • sql server字符连接
  • 企业缴纳印花税时需要
  • 预提费用汇算清缴前取得发票
  • 自产农产品加工成产品销售怎么抵扣
  • 无形资产根据什么科目填列
  • 调整其他应付款作为收入
  • 计提以前年度应付股利的分录
  • 跨年红冲收入记在本年度
  • 手工发票可以抵进项税吗
  • 如何开展服务工作
  • 赠送货物金额为多少
  • 收到伙食费的会计处理
  • 旅游业餐饮业
  • win8旗舰版和专业版区别
  • linux做网卡绑定
  • 进程 cmd
  • 安装和配置eclipse集成开发环境
  • win8锁定屏幕
  • 轻松搞定琥珀甲教程
  • win7怎样禁用无线网络连接
  • windows8.1的图片
  • cocos studio
  • unity的设置在哪里
  • js动态改变div内容
  • 象棋软件编程
  • python嵌套列表生成
  • ar现实增强设备
  • 安卓返回按钮图标
  • 炉石传说用什么语言开发的
  • node解决跨域
  • 命令最常用的类型有
  • js domcontentloaded
  • 瀑布流软件
  • python文件与异常答案
  • jQuery处理XML文件的几种方法
  • 纳税人识别号是几位数
  • 哈尔滨工业大学录取分数线2023年
  • 福建税务局网上办税
  • 经纪代理服务税率1%
  • 免责声明:网站部分图片文字素材来源于网络,如有侵权,请及时告知,我们会第一时间删除,谢谢! 邮箱:opceo@qq.com

    鄂ICP备2023003026号

    网站地图: 企业信息 工商信息 财税知识 网络常识 编程技术

    友情链接: 武汉网站建设