The paper

In March 2018, I wrote an article titled “Xiaozhu’s Python Learning Journey — 18.Python Wechat Forwarding Small Universe Morning Post”, which started from manually forwarding other people’s morning post news, to writing scripts and automatic forwarding. After all, this is organized by others, which can not guarantee punctuality. In many cases, the morning paper becomes the afternoon paper, and then there are archiving problems. Every time I want to see the previous morning paper, I have to search the chat records on wechat. Tut, tut, TUt, I thought, or to organize their own morning papers. So secretly began to manually tidy up the morning paper, and then posted on the official account “Pat Boy” :

After all, their own finishing, not sure whether the officer can accept, have to find a few small fire juice to verify ~

Then, I sorted out the daily newspaper, set the template, change the date, throw into the children’s shoes group to see if they find, test two days later ~

Ok, I will send it, and then simply say the process of sending morning papers every day.

  • 1. Make the cover of the morning paper
  • 2. Browse Xinhua News Agency, I Dark Horse, China Business Weekly and other news sites to collect interesting news and copy the headlines.
  • 3. Paste the copied headlines into a text editor to make up 15 news items.
  • 4. Log in to wechat public platform, open yesterday’s article, copy and paste, and then fill in today’s content.

Then check that there is nothing wrong and publish it. In the beginning, it took nearly an hour to complete the task every day, so precious is the fishing time every day, to spend an hour on the task seems to be a bit of a loss. As a dedicated developer, we must find a way to optimize, and the best result is: fully automated morning post

Of course, it’s just a thought. Some readers may wonder why the title is 80%, so what is the remaining 20%?

A: 15% is news filtering, screening interesting news headlines, this step can be optimized, the simplest is to directly climb the hot search or comments of the headlines, but this is not soul, I prefer to train a robot, let him automatically to screen the headlines. But it is not these things, so to put aside for a while first cough up, still have to manually filter ~ the remaining 5% is to take the morning post published on, also can be automated, actually write a script automatically by selenium little point is good, but the individual feels significance is not big, but I also need to preview before I used to send, after confirm the content to send, After all, you can only change the title, not the content.

Here’s one form I want to achieve right now:

  • 1. Write scripts to generate wechat cover pictures in batches.
  • 2. Compile crawler to retrieve news regularly and save it in local database.
  • 3. Compile interfaces, including obtaining the news collected on the day and adding it to the news filtering pool.
  • 4. Write an APP for news screening, and quickly screen the news headlines of the day while taking the subway to work.
  • 5. Write a one-click script to generate a unified style of morning paper articles.
  • 6. Write a one-click script to generate the day’s news details page.
  • 7. Copy and paste the generated style article, fill in the title, publisher, add the URL of the day’s news details page in the reading original, and complete the publication.
  • 8. Write wechat robot, timing (tentatively 10 o ‘clock), pick up morning paper for text processing, and automatically forward to relevant groups.

Ok, the general route is like this, this section starts from making the morning paper cover map ** excellent (steal) turn (lazy) **.


1. The process of making cover art

Take a look at the cover of my daily newspaper. Here’s a sample:

Composition: Background image (900*383) + headline (52px) + secondary title (44px) and then zoom out here is the process for making this cover image:

  • 1. Usually idle browsing some wallpaper apps or sites, feel beautiful to save.
  • 2. Open Pixelmator Pro and create a new 900 x 383 template. Drag the image in and adjust the image size until the width of the image matches the width of the template.
  • 3. Then move the zoomed image until you like the position.
  • 4. Add headings and center them.
  • 5. Merge layers and crop.
  • 6. Export it as a JPG file.

To give you a feel for the process, I probably recorded a Gif demo, but it took much longer than that (7 or 8 minutes).

Every day as a machine to repeat such operations, stay oh ~ then, I unexpectedly insisted on 60+ days (┬ _ ┬); I really needed a script to get me out of the grind.

Readers may be interested in the source of the image, but I like to save the selection of images directly from the wallpaper APP, and of course I can also crawl to some wallpaper sites myself. In addition, I found that some long images can actually be cropped into multiple covers, like this one:

Split it in two. It looks good.

It’s like a collection card. It’s interesting.


2. Process of image extraction

First, extract the process of image processing:

  • Zoom: Zoom in and out of the image until it is 900px wide.
  • Picture clipping: first calculate how many pictures can be clipped, with the middle of the picture as the benchmark clipping, calculate the Y-axis offset, the coordinates of each picture.
  • Picture plus word: Add headings to cropped pictures in turn.

3. Prepare materials

Line, processing flow, said the use of the Python libraries, can be installed directly by PIP commands: (mainly use opencv for image processing, the pillow that PIL library)

pip install numpy
pip install opencv-python
pip install pillow
Copy the code

4. Zoom

Keeping the aspect ratio set to 900px, we use the imread() method provided by OpencV to get an image object and do the related operations. First get a wave of height and width

import cv2

img = cv2.imread('1.jpg')
(h, w) = img.shape[:2]
print(h, w)

# output result: 956 1080
Copy the code

If you want to display the image, you can call imshow() directly:

cv2.imshow('image', img)   The parameters are: window name (window can not be the same name), read the image.
Copy the code

In the above code, the window flashes by. You can call waitKey() to keep the window open

cv2.waitkey()   # if you want to keep the window open, you can leave the parameter blank or fill in 0; You can also specify a wait time in milliseconds
                # Within a period of time, wait for the user to press the button to trigger the shutdown. If the user does not press the button all the time, the shutdown will be automatically closed when the time is up.
Copy the code

If your image has a Chinese file name or a Chinese file path, calling imread will give you an error, such as:

img = cv2.imread('test. JPG')
cv2.imshow('img', img)
cv2.waitKey()
Copy the code

Running results:

The imwrite() method also cannot generate an image with a Chinese path. You can write two functions to solve this problem:

def cv_imread(file_path):
    cv_img = cv2.imdecode(np.fromfile(file_path, dtype=np.uint8), -1)
    return cv_img
    
def cv_imwrite(img, file_path):
    cv2.imencode('.jpg', img)[1].tofile(file_path)
Copy the code

Opencv resize() is used to adjust the image size, width and height tuple, and an optional parameter: Interpolation. By default, INTER_LINEAR interpolation is used for bilinear interpolation. INTER_NEAREST, INTER_AREA, INTER_CUBIC, INTER_LANCZOS4.

import cv2
import numpy as np


def cv_imread(file_path):
    cv_img = cv2.imdecode(np.fromfile(file_path, dtype=np.uint8), -1)
    return cv_img


def cv_imwrite(img, file_path):
    cv2.imencode('.jpg', img)[1].tofile(file_path)


img = cv_imread('test. JPG')
(h, w) = img.shape[:2]
print("Dimensions before scaling:", img.shape[:2])
res = cv2.resize(img, (900, round(h * (900 / w))))
print("Cut size:", res.shape[:2])
Copy the code

Running results:

Dimensions before scaling: (956, 1080) Dimensions after scaling: (797, 900)Copy the code

5. Picture cropping

After scaling, it is time to cut. First, calculate how many pictures can be cut into:

crop_pic_count = int(ch / 383)
print("Images can be cropped to: % D" % crop_pic_count)

The image can be cropped to 2
Copy the code

The next step is to crop the image, which can be cropped by using the image object [y-start: y-end, x-start: x-end]. Therefore, we need to calculate the corresponding coordinate orientation of each clipping area. Also, consider an offset here, clipping against the middle position, which feels better. Here’s a comparison of clipping with and without offset:

So, AGAIN, I prefer to add the offset, and it’s easy to calculate the offset, just take the height and mod 383, and divide by 2.

start_y = int(ch % 383 / 2)
Copy the code

Then according to the number of pictures that can be cut, calculate how to cut

for i in range(0, crop_pic_count):
    crop_img = res[383 * i + start_y: 383 * (i + 1) + start_y, 0:900]
    cv_imwrite(crop_img, 'Clipping chart %d.jpg' % (i+1))
Copy the code

Cropped picture:

6. Picture plus word

Opencv provides putText() to add text. The parameters are: image, text content, coordinates, font, size, color, and font thickness.

img = cv_imread('Clipping figure 0.jpg')
cv2.putText(img, 'Test', (50, 300), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (255, 255, 255), 2) cv2. Imshow ('image', img)
cv2.waitKey()
Copy the code

The running results are as follows:

Add success. Pretty easy, right? However, if the text you added is not letters or numbers, but Chinese, congratulations, black question mark ~

Opencv putText cannot print utF8 characters, so it cannot print Chinese characters on pictures. Two solutions:

  • Method 1: use another Freetype library to decode and transcode characters, but a bit cumbersome.
  • Method 2: Use the Text function of ImageDraw class in the Pillow library to draw Chinese. First, transfer from CV2 to PIL format, and then return to CV2 format for output after adding Chinese.

Here we use method two, the parameters of the text function: start coordinate tuple, text content, font, color. The question is, how do I determine the starting coordinates of the text to draw?

Answer: if you want to program calculate, very troublesome, how to obtain the text width, since the picture size is fixed, the text length is constant, why not take a trick?

Just drag the text onto Pixelmator Pro and copy the coordinates

In addition, the font used by the author here is Apple-Jane, the normal font, you can download, remember to change the font file name to English file name, otherwise, the font will not be read. Ok, try the code:

img = cv_imread('Clipping figure 0.jpg')
Convert images from OpenCv format to PIL format
img_pil = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
# Load font (font filename, font size)
title_font = ImageFont.truetype('apple-simple.ttf', 52)
date_font = ImageFont.truetype('apple-simple.ttf'44),Draw the position of the text
title_pos = (236, 110)
date_pos = (338, 192)
# Draw content
title_content = u"Speed reading."
date_content = u"Issue 190111"
# draw
draw = ImageDraw.Draw(img_pil)
draw.text(title_pos, title_content, font=title_font, fill=(255, 255, 255))
draw.text(date_pos, date_content, font=date_font, fill=(255, 255, 255))
img_open_cv = cv2.cvtColor(np.asarray(img_pil), cv2.COLOR_RGB2BGR)
cv_imwrite(img_open_cv, "After adding the word. JPG")
Copy the code

Then look at the output image:

TSK TSK, perfect, to this automatic cut to generate a morning cover of the script is complete, next we come to complete and improve our program.

7. Complete the code

Is to add a loop, some small logic, relatively simple, annotations are also relatively clear, not nagging nagging, directly on the complete generation bar:

# -*- coding: utf-8 -*-
import os
import cv2
import numpy as np
import time
from PIL import Image, ImageDraw, ImageFont
from datetime import datetime, timedelta
import shutil

pic_source_dir = os.path.join(os.getcwd(), "news_pic_source\\")  # Original path
pic_crop_dir = os.path.join(os.getcwd(), "news_pic_crop\\")  # Clipped image path
pic_font_dir = os.path.join(os.getcwd(), "news_pic_font\\")  # The image path after the word is added
start_date = "20190112"  The start date of the drawing image


# check whether the folder exists, create a new folder if it does not
def is_dir_existed(path, mkdir=True):
    if mkdir:
        if not os.path.exists(path):
            os.makedirs(path)
    else:
        return os.path.exists(path)


# opencV reading Chinese pathnames will be garbled
def cv_imread(file_path):
    cv_img = cv2.imdecode(np.fromfile(file_path, dtype=np.uint8), -1)
    return cv_img


# opencv writing Chinese pathnames will be garbled
def cv_imwrite(img, file_path):
    cv2.imencode('.jpg', img)[1].tofile(file_path)


# Crop the original image to multiple smaller images (900*383)
def crop_little_pic(pic_path):
    img = cv_imread(pic_path)
    (sh, sw) = img.shape[:2]
    # Set the width of the image to 900 and the height to scale
    res = cv2.resize(img, (900, round(sh * (900 / sw))))
    Get the height and width of the zoom, and determine the number of images that can be cropped
    (ch, cw) = res.shape[:2]
    crop_pic_count = int(ch / 383)
    # Compute the Y offset
    start_y = int(ch % 383 / 2)
    How to crop according to the number of pictures
    for i in range(0, crop_pic_count):
        crop_img = res[383 * i + start_y: 383 * (i + 1) + start_y, 0:900]
        cv_imwrite(crop_img, os.path.join(pic_crop_dir, str(int(round(time.time() * 1000))) + '.jpg'))


# draw text
def draw_text(pic_path, date):
    img = cv_imread(pic_path)
    Convert images from OpenCv format to PIL format
    img_pil = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
    # Load font (font filename, font size)
    title_font = ImageFont.truetype('apple-simple.ttf', 52)
    date_font = ImageFont.truetype('apple-simple.ttf'44),Draw the position of the text
    title_pos = (236, 110)
    date_pos = (316, 192)
    # Draw content
    title_content = u"Speed reading."
    date_content = u"Phase % S" % date[2:]
    # draw
    draw = ImageDraw.Draw(img_pil)
    draw.text(title_pos, title_content, font=title_font, fill=(255, 255, 255))
    draw.text(date_pos, date_content, font=date_font, fill=(255, 255, 255))
    img_open_cv = cv2.cvtColor(np.asarray(img_pil), cv2.COLOR_RGB2BGR)
    cv_imwrite(img_open_cv, os.path.join(pic_font_dir, date[2:] + '.jpg'))


Get a list of file paths of a certain class
def fetch_file_path(path, file_type):
    file_list = []
    f = os.listdir(path)
    for i in f:
        if i.endswith(file_type):
            file_list.append(os.path.join(path, i))
    return file_list


Construct a list of build dates
def init_date_list(begin_date, count):
    d_list = []
    begin_date = datetime.strptime(begin_date, "%Y%m%d")
    end_date = datetime.strptime((datetime.now() + timedelta(days=count)).strftime("%Y%m%d"), "%Y%m%d")
    while begin_date <= end_date:
        date_str = begin_date.strftime("%Y%m%d")
        d_list.append(date_str)
        begin_date += timedelta(days=1)
    return d_list


if __name__ == '__main__':
    is_dir_existed(pic_source_dir)
    while True:
        choice = input(
            "%s\n Please enter the operation you want to do \n1. Crop picture \n2. picture plus word \n3. Empty clipping folder \n4. Empty add folder \n5. Exit program \n%s\n" % ('=' * 32, '=' * 32))
        if choice == '1':
            is_dir_existed(pic_crop_dir)
            pic_path_list = fetch_file_path(pic_source_dir, ".jpg")
            if len(pic_path_list) == 0:
                print("There are no pictures in the original picture folder, please add pictures first!")
            else:
                print("Start bulk cutting...")
                begin = datetime.now()
                for pic in pic_path_list:
                    crop_little_pic(pic)
                end = datetime.now()
                print("After batch cropping, generate pictures: % D, time: %s" % (len(fetch_file_path(pic_crop_dir, ".jpg")), (end - begin).seconds))
        elif choice == '2':
            is_dir_existed(pic_font_dir)
            crop_path_list = fetch_file_path(pic_crop_dir, ".jpg")
            date_list = init_date_list(start_date, len(crop_path_list) + 1)
            if len(crop_path_list) == 0:
                print("There is no picture in clipping folder, please turn it into clipping picture!")
            else:
                print("Start adding words in batches...")
                begin = datetime.now()
                print(len(crop_path_list), len(date_list))
                for i in range(len(crop_path_list)):
                    draw_text(crop_path_list[i], date_list[i])
                end = datetime.now()
                print("Batch adding finished, processing pictures: % D, time: %s" % (len(fetch_file_path(pic_font_dir, ".jpg")), (end - begin).seconds))
        elif choice == '3':
            if is_dir_existed(pic_crop_dir, False):
                shutil.rmtree(pic_crop_dir)
                print("Folder deleted successfully!")
            else:
                print("Folder does not exist, delete failed ~")
        elif choice == '4':
            if is_dir_existed(pic_font_dir, False):
                shutil.rmtree(pic_font_dir)
                print("Folder deleted successfully!")
            else:
                print("Folder does not exist, delete failed ~")
        elif choice == '5':
            exit("Exit program ~")
        else:
            print("Wrong serial number, please confirm and re-enter!!")
Copy the code

Before executing, prepare a wave of original images, here prepared:

Then run a wave of code and type 1,2 to crop and add:

Gee, 94 pieces of artwork created 243 cover figure, and it only took more than ten seconds, open the generated folder to see a wave:

It’s September, 2333, real life is too short, I use Python, there should be applause ~

In addition, a few days ago, I wrote a script to collect the first frame of a bunch of video and then process it. It is required by the students’ association to paste the core code

# Capture the first frame of the video
def fetch_video_first_frame(path_list):
    for mp4 in path_list:
        cap = cv2.VideoCapture(mp4)
        if cap.isOpened():
            ret, im = cap.read()
            cv2.imencode('.jpg', im)[1].tofile(
                os.path.join(pic_source_output_dir, mp4.split("\ \")[-1]).replace("mp4"."jpg"))
        cap.release()
Copy the code

Ok, that’s all for this section. If you have any questions, please leave them in the comments section


Tips: public at present just adhere to the morning post, slowly improve, a little guilty, only dare to post a small picture, want to see the morning post can pay attention to ~